2020-01-12 04:19:56 +08:00
|
|
|
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";
|
2020-01-12 19:48:17 +08:00
|
|
|
import server from "./server.js";
|
|
|
|
import keyboardActionService from "./keyboard_actions.js";
|
2020-01-13 02:05:09 +08:00
|
|
|
import TabRowWidget from "./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";
|
2020-01-12 04:19:56 +08:00
|
|
|
|
2020-01-12 16:57:28 +08:00
|
|
|
class AppContext {
|
2020-01-12 04:19:56 +08:00
|
|
|
constructor() {
|
|
|
|
this.widgets = [];
|
2020-01-12 19:30:30 +08:00
|
|
|
/** @type {TabContext[]} */
|
|
|
|
this.tabContexts = [];
|
2020-01-12 19:48:17 +08:00
|
|
|
this.tabsChangedTaskId = null;
|
2020-01-13 02:05:09 +08:00
|
|
|
/** @type {TabRowWidget} */
|
|
|
|
this.tabRow = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
showWidgets() {
|
|
|
|
const $leftPane = $("#left-pane");
|
|
|
|
|
|
|
|
this.tabRow = new TabRowWidget(this);
|
2020-01-13 03:15:05 +08:00
|
|
|
const contents = this.tabRow.render();
|
|
|
|
|
|
|
|
$("#global-menu-wrapper").after(contents);
|
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) {
|
2020-01-15 03:27:40 +08:00
|
|
|
widget.renderTo($leftPane);
|
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 = [
|
2020-01-15 03:27:40 +08:00
|
|
|
new TabCachingWidget(this, () => new NoteTitleWidget(this)),
|
|
|
|
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) {
|
2020-01-15 03:27:40 +08:00
|
|
|
widget.renderTo($centerPane);
|
2020-01-14 04:48:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
this.widgets = [
|
|
|
|
this.tabRow,
|
|
|
|
...leftPaneWidgets,
|
|
|
|
...centerPaneWidgets
|
|
|
|
];
|
2020-01-12 04:19:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
trigger(name, data) {
|
2020-01-13 06:03:55 +08:00
|
|
|
this.eventReceived(name, data);
|
|
|
|
|
2020-01-12 04:19:56 +08:00
|
|
|
for (const widget of this.widgets) {
|
|
|
|
widget.eventReceived(name, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-13 06:03:55 +08:00
|
|
|
eventReceived(name, data) {
|
|
|
|
const fun = this[name + 'Listener'];
|
|
|
|
|
|
|
|
if (typeof fun === 'function') {
|
|
|
|
fun.call(this, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-12 19:30:30 +08:00
|
|
|
/** @return {TabContext[]} */
|
|
|
|
getTabContexts() {
|
|
|
|
return this.tabContexts;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @returns {TabContext} */
|
|
|
|
getActiveTabContext() {
|
2020-01-13 02:05:09 +08:00
|
|
|
const activeTabEl = this.tabRow.activeTabEl;
|
2020-01-12 19:30:30 +08:00
|
|
|
|
|
|
|
if (!activeTabEl) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const tabId = activeTabEl.getAttribute('data-tab-id');
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async showTab(tabId) {
|
|
|
|
for (const ctx of this.tabContexts) {
|
|
|
|
if (ctx.tabId === tabId) {
|
|
|
|
await ctx.show();
|
|
|
|
} else {
|
|
|
|
ctx.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const oldActiveNode = this.getMainNoteTree().getActiveNode();
|
|
|
|
|
|
|
|
if (oldActiveNode) {
|
|
|
|
oldActiveNode.setActive(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
const newActiveTabContext = this.getActiveTabContext();
|
|
|
|
|
|
|
|
if (newActiveTabContext && newActiveTabContext.notePath) {
|
|
|
|
const newActiveNode = await this.getMainNoteTree().getNodeFromPath(newActiveTabContext.notePath);
|
|
|
|
|
|
|
|
if (newActiveNode) {
|
|
|
|
if (!newActiveNode.isVisible()) {
|
|
|
|
await this.getMainNoteTree().expandToNote(newActiveTabContext.notePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
newActiveNode.setActive(true, {noEvents: true});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-13 02:05:09 +08:00
|
|
|
const ctx = new TabContext(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-13 02:05:09 +08:00
|
|
|
const ctx = new TabContext(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
|
|
|
}
|
2020-01-12 19:48:17 +08:00
|
|
|
|
|
|
|
async filterTabs(noteId) {
|
|
|
|
for (const tc of this.tabContexts) {
|
|
|
|
if (tc.notePath && !tc.notePath.split("/").includes(noteId)) {
|
2020-01-13 02:05:09 +08:00
|
|
|
await this.tabRow.removeTab(tc.$tab[0]);
|
2020-01-12 19:48:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-01-12 19:48:17 +08:00
|
|
|
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-13 02:05:09 +08:00
|
|
|
async activeTabChangedListener({tabEl}) {
|
|
|
|
const tabId = tabEl.getAttribute('data-tab-id');
|
2020-01-12 19:30:30 +08:00
|
|
|
|
2020-01-13 02:05:09 +08:00
|
|
|
await this.showTab(tabId);
|
|
|
|
}
|
2020-01-12 19:30:30 +08:00
|
|
|
|
2020-01-13 02:05:09 +08:00
|
|
|
async tabRemoveListener({tabEl}) {
|
|
|
|
const tabId = tabEl.getAttribute('data-tab-id');
|
2020-01-12 19:30:30 +08:00
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const appContext = new AppContext();
|
2020-01-12 19:48:17 +08:00
|
|
|
|
|
|
|
keyboardActionService.setGlobalActionHandler('OpenNewTab', () => {
|
|
|
|
appContext.openEmptyTab();
|
|
|
|
});
|
|
|
|
|
|
|
|
keyboardActionService.setGlobalActionHandler('CloseActiveTab', () => {
|
2020-01-13 02:05:09 +08:00
|
|
|
if (this.tabRow.activeTabEl) {
|
|
|
|
this.tabRow.removeTab(this.tabRow.activeTabEl);
|
2020-01-12 19:48:17 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
keyboardActionService.setGlobalActionHandler('ActivateNextTab', () => {
|
2020-01-13 02:05:09 +08:00
|
|
|
const nextTab = this.tabRow.nextTabEl;
|
2020-01-12 19:48:17 +08:00
|
|
|
|
|
|
|
if (nextTab) {
|
2020-01-13 02:05:09 +08:00
|
|
|
this.tabRow.activateTab(nextTab);
|
2020-01-12 19:48:17 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
keyboardActionService.setGlobalActionHandler('ActivatePreviousTab', () => {
|
2020-01-13 02:05:09 +08:00
|
|
|
const prevTab = this.tabRow.previousTabEl;
|
2020-01-12 19:48:17 +08:00
|
|
|
|
|
|
|
if (prevTab) {
|
2020-01-13 02:05:09 +08:00
|
|
|
this.tabRow.activateTab(prevTab);
|
2020-01-12 19:48:17 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-01-12 16:57:28 +08:00
|
|
|
export default appContext;
|