trilium/src/public/javascripts/services/app_context.js

393 lines
12 KiB
JavaScript
Raw Normal View History

import GlobalButtonsWidget from "../widgets/global_buttons.js";
import SearchBoxWidget from "../widgets/search_box.js";
import SearchResultsWidget from "../widgets/search_results.js";
import NoteTreeWidget from "../widgets/note_tree.js";
2020-01-12 19:30:30 +08:00
import treeService from "./tree.js";
import noteDetailService from "./note_detail.js";
import TabContext from "./tab_context.js";
import server from "./server.js";
import keyboardActionService from "./keyboard_actions.js";
2020-01-15 04:52:18 +08:00
import TabRowWidget from "../widgets/tab_row.js";
2020-01-13 06:03:55 +08:00
import NoteTitleWidget from "../widgets/note_title.js";
2020-01-14 03:25:56 +08:00
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
2020-01-14 04:48:44 +08:00
import NoteDetailWidget from "../widgets/note_detail.js";
2020-01-15 03:27:40 +08:00
import TabCachingWidget from "../widgets/tab_caching_widget.js";
import NoteInfoWidget from "../widgets/note_info.js";
import NoteRevisionsWidget from "../widgets/note_revisions.js";
import LinkMapWidget from "../widgets/link_map.js";
import SimilarNotesWidget from "../widgets/similar_notes.js";
import WhatLinksHereWidget from "../widgets/what_links_here.js";
import AttributesWidget from "../widgets/attributes.js";
2020-01-16 02:40:17 +08:00
import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
import GlobalMenuWidget from "../widgets/global_menu.js";
2020-01-16 04:36:01 +08:00
import RowFlexContainer from "../widgets/row_flex_container.js";
2020-01-16 03:10:54 +08:00
import StandardTopWidget from "../widgets/standard_top_widget.js";
2020-01-16 04:36:01 +08:00
import treeCache from "./tree_cache.js";
import treeUtils from "./tree_utils.js";
import NotePathsWidget from "../widgets/note_paths.js";
import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
import ProtectedNoteSwitchWidget from "../widgets/protected_note_switch.js";
import NoteTypeWidget from "../widgets/note_type.js";
import NoteActionsWidget from "../widgets/note_actions.js";
import protectedSessionHolder from "./protected_session_holder.js";
2020-01-12 16:57:28 +08:00
class AppContext {
constructor() {
this.widgets = [];
2020-01-12 19:30:30 +08:00
/** @type {TabContext[]} */
this.tabContexts = [];
this.tabsChangedTaskId = null;
2020-01-13 02:05:09 +08:00
/** @type {TabRowWidget} */
this.tabRow = null;
}
showWidgets() {
this.tabRow = new TabRowWidget(this);
2020-01-13 03:15:05 +08:00
2020-01-16 03:10:54 +08:00
const topPaneWidgets = [
2020-01-16 04:36:01 +08:00
new RowFlexContainer(this, [
2020-01-16 03:10:54 +08:00
new GlobalMenuWidget(this),
this.tabRow,
new TitleBarButtonsWidget(this)
]),
new StandardTopWidget(this)
2020-01-16 02:40:17 +08:00
];
2020-01-16 03:10:54 +08:00
const $topPane = $("#top-pane");
for (const widget of topPaneWidgets) {
$topPane.append(widget.render());
2020-01-16 02:40:17 +08:00
}
const $leftPane = $("#left-pane");
2020-01-13 02:05:09 +08:00
this.noteTreeWidget = new NoteTreeWidget(this);
2020-01-14 04:48:44 +08:00
const leftPaneWidgets = [
2020-01-13 02:05:09 +08:00
new GlobalButtonsWidget(this),
new SearchBoxWidget(this),
new SearchResultsWidget(this),
this.noteTreeWidget
];
2020-01-14 04:48:44 +08:00
for (const widget of leftPaneWidgets) {
$leftPane.append(widget.render());
2020-01-13 02:05:09 +08:00
}
2020-01-13 06:03:55 +08:00
2020-01-14 04:48:44 +08:00
const $centerPane = $("#center-pane");
const centerPaneWidgets = [
new RowFlexContainer(this, [
new TabCachingWidget(this, () => new NotePathsWidget(this)),
new NoteTitleWidget(this),
new RunScriptButtonsWidget(this),
new ProtectedNoteSwitchWidget(this),
new NoteTypeWidget(this),
new NoteActionsWidget(this)
]),
2020-01-15 03:27:40 +08:00
new TabCachingWidget(this, () => new PromotedAttributesWidget(this)),
new TabCachingWidget(this, () => new NoteDetailWidget(this))
2020-01-14 04:48:44 +08:00
];
for (const widget of centerPaneWidgets) {
$centerPane.append(widget.render());
2020-01-14 04:48:44 +08:00
}
const $rightPane = $("#right-pane");
const rightPaneWidgets = [
new NoteInfoWidget(this),
new TabCachingWidget(this, () => new AttributesWidget(this)),
new TabCachingWidget(this, () => new LinkMapWidget(this)),
new TabCachingWidget(this, () => new NoteRevisionsWidget(this)),
new TabCachingWidget(this, () => new SimilarNotesWidget(this)),
new TabCachingWidget(this, () => new WhatLinksHereWidget(this)),
];
for (const widget of rightPaneWidgets) {
$rightPane.append(widget.render());
}
2020-01-14 04:48:44 +08:00
this.widgets = [
this.tabRow,
...leftPaneWidgets,
...centerPaneWidgets,
...rightPaneWidgets
2020-01-14 04:48:44 +08:00
];
}
trigger(name, data, sync = false) {
2020-01-13 06:03:55 +08:00
this.eventReceived(name, data);
2020-01-16 04:36:01 +08:00
for (const tabContext of this.tabContexts) {
tabContext.eventReceived(name, data, sync);
2020-01-16 04:36:01 +08:00
}
for (const widget of this.widgets) {
widget.eventReceived(name, data, sync);
}
}
async eventReceived(name, data, sync) {
2020-01-13 06:03:55 +08:00
const fun = this[name + 'Listener'];
if (typeof fun === 'function') {
await fun.call(this, data, sync);
2020-01-13 06:03:55 +08:00
}
}
2020-01-16 04:36:01 +08:00
activateNote(notePath) {
const activeTabContext = this.getActiveTabContext();
2020-01-19 01:01:16 +08:00
console.log("Setting activeTabContext to " + notePath);
2020-01-16 04:36:01 +08:00
activeTabContext.setNote(notePath);
this._setTitleBar();
this._setCurrentNotePathToHash();
}
_setCurrentNotePathToHash() {
const activeTabContext = this.getActiveTabContext();
if (activeTabContext && activeTabContext.notePath) {
document.location.hash = (activeTabContext.notePath || "") + "-" + activeTabContext.tabId;
}
}
noteTitleChangedListener() {
this._setTitleBar();
}
2020-01-16 04:36:01 +08:00
async _setTitleBar() {
document.title = "Trilium Notes";
const activeTabContext = this.getActiveTabContext();
if (activeTabContext && activeTabContext.notePath) {
const note = await treeCache.getNote(treeUtils.getNoteIdFromNotePath(activeTabContext.notePath));
// it helps navigating in history if note title is included in the title
document.title += " - " + note.title;
}
}
2020-01-12 19:30:30 +08:00
/** @return {TabContext[]} */
getTabContexts() {
return this.tabContexts;
}
/** @returns {TabContext} */
getActiveTabContext() {
2020-01-16 05:27:52 +08:00
const tabId = this.tabRow.activeTabId;
2020-01-12 19:30:30 +08:00
return this.tabContexts.find(tc => tc.tabId === tabId);
}
/** @returns {string|null} */
getActiveTabNotePath() {
const activeContext = this.getActiveTabContext();
return activeContext ? activeContext.notePath : null;
}
/** @return {NoteFull} */
getActiveTabNote() {
const activeContext = this.getActiveTabContext();
return activeContext ? activeContext.note : null;
}
/** @return {string|null} */
getActiveTabNoteId() {
const activeNote = this.getActiveTabNote();
return activeNote ? activeNote.noteId : null;
}
/** @return {string|null} */
getActiveTabNoteType() {
const activeNote = this.getActiveTabNote();
return activeNote ? activeNote.type : null;
}
async switchToTab(tabId, notePath) {
const tabContext = this.tabContexts.find(tc => tc.tabId === tabId);
if (!tabContext) {
await noteDetailService.loadNoteDetail(notePath, {
newTab: true,
activate: true
});
} else {
await tabContext.activate();
if (notePath && tabContext.notePath !== notePath) {
await treeService.activateNote(notePath);
}
}
}
2020-01-12 18:15:23 +08:00
/**
* @return {NoteTreeWidget}
*/
getMainNoteTree() {
return this.noteTreeWidget;
}
2020-01-12 19:30:30 +08:00
getTab(newTab, state) {
if (!this.getActiveTabContext() || newTab) {
// if it's a new tab explicitly by user then it's in background
2020-01-16 04:36:01 +08:00
const ctx = new TabContext(this, this.tabRow, state);
2020-01-12 19:30:30 +08:00
this.tabContexts.push(ctx);
return ctx;
} else {
return this.getActiveTabContext();
}
}
async reloadAllTabs() {
for (const tabContext of this.tabContexts) {
await this.reloadTab(tabContext);
}
}
async refreshTabs(sourceTabId, noteId) {
for (const tc of this.tabContexts) {
if (tc.noteId === noteId && tc.tabId !== sourceTabId) {
await this.reloadTab(tc);
}
}
}
async reloadTab(tc) {
if (tc.note) {
noteDetailService.reloadNote(tc);
}
}
async openEmptyTab() {
2020-01-16 04:36:01 +08:00
const ctx = new TabContext(this, this.tabRow);
2020-01-12 19:30:30 +08:00
this.tabContexts.push(ctx);
2020-01-13 02:05:09 +08:00
await this.tabRow.activateTab(ctx.$tab[0]);
2020-01-12 19:30:30 +08:00
}
async filterTabs(noteId) {
for (const tc of this.tabContexts) {
if (tc.notePath && !tc.notePath.split("/").includes(noteId)) {
2020-01-16 05:27:52 +08:00
this.tabRow.removeTab(tc.tabId);
}
}
if (this.tabContexts.length === 0) {
this.openEmptyTab()
}
await this.saveOpenTabs();
}
async saveOpenTabs() {
const openTabs = [];
2020-01-13 02:05:09 +08:00
for (const tabEl of this.tabRow.tabEls) {
const tabId = tabEl.getAttribute('data-tab-id');
const tabContext = appContext.getTabContexts().find(tc => tc.tabId === tabId);
if (tabContext) {
const tabState = tabContext.getTabState();
if (tabState) {
openTabs.push(tabState);
}
}
}
await server.put('options', {
openTabs: JSON.stringify(openTabs)
});
}
clearOpenTabsTask() {
if (this.tabsChangedTaskId) {
clearTimeout(this.tabsChangedTaskId);
}
}
openTabsChanged() {
// we don't want to send too many requests with tab changes so we always schedule task to do this in 1 seconds,
// but if there's any change in between, we cancel the old one and schedule new one
// so effectively we kind of wait until user stopped e.g. quickly switching tabs
this.clearOpenTabsTask();
this.tabsChangedTaskId = setTimeout(() => this.saveOpenTabs(), 1000);
}
2020-01-12 16:57:28 +08:00
2020-01-13 03:15:05 +08:00
async activateTab(tabContext) {
return this.tabRow.activateTab(tabContext.$tab[0]);
}
2020-01-13 02:05:09 +08:00
newTabListener() {
this.openEmptyTab();
}
2020-01-12 16:57:28 +08:00
2020-01-16 05:27:52 +08:00
async tabRemoveListener({tabId}) {
2020-01-13 02:05:09 +08:00
this.tabContexts.filter(nc => nc.tabId === tabId)
.forEach(tc => tc.remove());
2020-01-12 19:30:30 +08:00
2020-01-13 02:05:09 +08:00
this.tabContexts = this.tabContexts.filter(nc => nc.tabId !== tabId);
2020-01-12 19:30:30 +08:00
2020-01-13 02:05:09 +08:00
if (this.tabContexts.length === 0) {
this.openEmptyTab();
}
2020-01-12 19:30:30 +08:00
2020-01-13 02:05:09 +08:00
this.openTabsChanged();
2020-01-12 19:30:30 +08:00
}
2020-01-13 02:05:09 +08:00
tabReorderListener() {
this.openTabsChanged();
}
noteChangesSavedListener() {
const activeTabContext = this.getActiveTabContext();
if (!activeTabContext || !activeTabContext.note) {
return;
}
if (activeTabContext.note.isProtected && protectedSessionHolder.isProtectedSessionAvailable()) {
protectedSessionHolder.touchProtectedSession();
}
}
2020-01-13 02:05:09 +08:00
}
const appContext = new AppContext();
keyboardActionService.setGlobalActionHandler('OpenNewTab', () => {
appContext.openEmptyTab();
});
keyboardActionService.setGlobalActionHandler('CloseActiveTab', () => {
2020-01-13 02:05:09 +08:00
if (this.tabRow.activeTabEl) {
2020-01-16 05:27:52 +08:00
this.tabRow.removeTab(this.tabRow.activeTabId);
}
});
keyboardActionService.setGlobalActionHandler('ActivateNextTab', () => {
2020-01-13 02:05:09 +08:00
const nextTab = this.tabRow.nextTabEl;
if (nextTab) {
2020-01-13 02:05:09 +08:00
this.tabRow.activateTab(nextTab);
}
});
keyboardActionService.setGlobalActionHandler('ActivatePreviousTab', () => {
2020-01-13 02:05:09 +08:00
const prevTab = this.tabRow.previousTabEl;
if (prevTab) {
2020-01-13 02:05:09 +08:00
this.tabRow.activateTab(prevTab);
}
});
2020-01-12 16:57:28 +08:00
export default appContext;