diff --git a/src/public/javascripts/dialogs/attributes.js b/src/public/javascripts/dialogs/attributes.js index d97a0e048..939eb6316 100644 --- a/src/public/javascripts/dialogs/attributes.js +++ b/src/public/javascripts/dialogs/attributes.js @@ -174,8 +174,7 @@ function AttributesModel() { appContext.getActiveTabContext().attributes.refreshAttributes(); - // reload - noteDetailService.reload(); + // FIXME detail should be also reloaded appContext.trigger('reloadTree'); }; diff --git a/src/public/javascripts/mobile.js b/src/public/javascripts/mobile.js index 76b9910d4..0c0b091f7 100644 --- a/src/public/javascripts/mobile.js +++ b/src/public/javascripts/mobile.js @@ -62,7 +62,6 @@ async function showTree() { const notePath = await treeUtils.getNotePath(node); - noteDetailService.switchToNote(notePath); }, expand: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, true), collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false), diff --git a/src/public/javascripts/services/app_context.js b/src/public/javascripts/services/app_context.js index 31e86ad02..a9840cd9c 100644 --- a/src/public/javascripts/services/app_context.js +++ b/src/public/javascripts/services/app_context.js @@ -216,20 +216,11 @@ class AppContext { } async switchToTab(tabId, notePath) { - const tabContext = this.tabContexts.find(tc => tc.tabId === tabId); + const tabContext = this.tabContexts.find(tc => tc.tabId === tabId) + || this.openEmptyTab(); - if (!tabContext) { - await noteDetailService.loadNoteDetail(notePath, { - newTab: true, - activate: true - }); - } else { - await this.activateTab(tabContext.tabId); - - if (notePath && tabContext.notePath !== notePath) { - await tabContext.setNote(notePath); - } - } + this.activateTab(tabContext.tabId); + await tabContext.setNote(notePath); } /** @@ -252,18 +243,6 @@ class AppContext { } } - async reloadAllTabs() { - for (const tabContext of this.tabContexts) { - await this.reloadTab(tabContext); - } - } - - async reloadTab(tc) { - if (tc.note) { - noteDetailService.reloadNote(tc); - } - } - async openAndActivateEmptyTab() { const tabContext = this.openEmptyTab(); @@ -278,6 +257,20 @@ class AppContext { return tabContext; } + async activateOrOpenNote(noteId) { + for (const tabContext of this.getTabContexts()) { + if (tabContext.note && tabContext.note.noteId === noteId) { + await tabContext.activate(); + return; + } + } + + // if no tab with this note has been found we'll create new tab + + const tabContext = this.openEmptyTab(); + await tabContext.setNote(noteId); + } + async filterTabs(noteId) { for (const tc of this.tabContexts) { if (tc.notePath && !tc.notePath.split("/").includes(noteId)) { @@ -318,7 +311,7 @@ class AppContext { } } - openTabsChanged() { + openTabsChangedListener() { // 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 @@ -327,7 +320,7 @@ class AppContext { this.tabsChangedTaskId = setTimeout(() => this.saveOpenTabs(), 1000); } - async activateTab(tabId) { + activateTab(tabId) { this.activeTabId = tabId; this.trigger('activeTabChanged', { tabId: this.activeTabId }); @@ -364,11 +357,11 @@ class AppContext { this.tabContexts = this.tabContexts.filter(tc => tc.tabId === tabId); - this.openTabsChanged(); + this.openTabsChangedListener(); } tabReorderListener() { - this.openTabsChanged(); + this.openTabsChangedListener(); } noteChangesSavedListener() { diff --git a/src/public/javascripts/services/entrypoints.js b/src/public/javascripts/services/entrypoints.js index f02c5fe3f..b2a4b004f 100644 --- a/src/public/javascripts/services/entrypoints.js +++ b/src/public/javascripts/services/entrypoints.js @@ -78,7 +78,9 @@ export default class Entrypoints extends Component { await treeService.expandToNote(note.noteId); - await noteDetailService.openInTab(note.noteId, true); + const tabContext = appContext.openEmptyTab(); + appContext.activateTab(tabContext.tabId); + await tabContext.setNote(note.noteId); noteDetailService.focusAndSelectTitle(); } diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js index 97d8d328a..0e32ae09d 100644 --- a/src/public/javascripts/services/frontend_script_api.js +++ b/src/public/javascripts/services/frontend_script_api.js @@ -272,22 +272,6 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte */ this.refreshTree = treeService.reload; - /** - * Refresh active tab - * - * @method - * @returns {Promise} - */ - this.refreshActiveTab = noteDetailService.reload; - - /** - * Refresh current tab - * - * @method - * @returns {Promise} - */ - this.refreshAllTabs = appContext.reloadAllTabs; - /** * Create note link (jQuery object) for given note. * diff --git a/src/public/javascripts/services/link.js b/src/public/javascripts/services/link.js index a4eb5b803..8a21a4040 100644 --- a/src/public/javascripts/services/link.js +++ b/src/public/javascripts/services/link.js @@ -79,10 +79,13 @@ function goToLink(e) { if (notePath) { if ((e.which === 1 && e.ctrlKey) || e.which === 2) { - noteDetailService.openInTab(notePath, false); + const tabContext = appContext.openEmptyTab(); + appContext.activateTab(tabContext.tabId); + tabContext.setNote(notePath); } else if (e.which === 1) { - treeService.activateNote(notePath); + const activeTabContext = appContext.getActiveTabContext(); + activeTabContext.setNote(notePath) } else { return false; @@ -118,7 +121,9 @@ function newTabContextMenu(e) { }, selectContextMenuItem: (e, cmd) => { if (cmd === 'openNoteInNewTab') { - noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true }); + const tabContext = appContext.openEmptyTab(); + tabContext.setNote(notePath); + appContext.activateTab(tabContext.tabId); } } }); @@ -138,7 +143,9 @@ $(document).on('mousedown', '.note-detail-text a', function (e) { e.preventDefault(); if (notePath) { - noteDetailService.loadNoteDetail(notePath, {newTab: true}); + const tabContext = appContext.openEmptyTab(); + tabContext.setNote(notePath); + appContext.activateTab(tabContext.tabId); } else { const address = $link.attr('href'); diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 0a0ac58b2..0c573e89a 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -1,35 +1,9 @@ -import treeService from './tree.js'; -import TabContext from './tab_context.js'; import server from './server.js'; import ws from "./ws.js"; import treeCache from "./tree_cache.js"; import NoteFull from "../entities/note_full.js"; -import treeUtils from "./tree_utils.js"; -import tabRow from "../widgets/tab_row.js"; import appContext from "./app_context.js"; -let detailLoadedListeners = []; - -async function reload() { - // no saving here - - await loadNoteDetail(appContext.getActiveTabNotePath()); -} - -async function reloadNote(tabContext) { - await loadNoteDetailToContext(tabContext, tabContext.notePath); -} - -async function openInTab(notePath, activate) { - await loadNoteDetail(notePath, { newTab: true, activate }); -} - -async function switchToNote(notePath) { - await loadNoteDetail(notePath); - - appContext.openTabsChanged(); -} - function getActiveEditor() { const activeTabContext = appContext.getActiveTabContext(); @@ -41,73 +15,6 @@ function getActiveEditor() { } } -async function activateOrOpenNote(noteId) { - for (const tabContext of appContext.getTabContexts()) { - if (tabContext.note && tabContext.note.noteId === noteId) { - await tabContext.activate(); - return; - } - } - - // if no tab with this note has been found we'll create new tab - - await loadNoteDetail(noteId, { - newTab: true, - activate: true - }); -} - -/** - * @param {TabContext} ctx - * @param {string} notePath - */ -async function loadNoteDetailToContext(ctx, notePath) { - await ctx.setNote(notePath); - - appContext.openTabsChanged(); - - fireDetailLoaded(); -} - -async function loadNoteDetail(origNotePath, options = {}) { - const newTab = !!options.newTab; - const activate = !!options.activate; - - let notePath = await treeService.resolveNotePath(origNotePath); - - if (!notePath) { - console.error(`Cannot resolve note path ${origNotePath}`); - - // fallback to display something - notePath = 'root'; - } - - const noteId = treeUtils.getNoteIdFromNotePath(notePath); - const ctx = appContext.getTab(newTab, options.state); - - // we will try to render the new note only if it's still the active one in the tree - // this is useful when user quickly switches notes (by e.g. holding down arrow) so that we don't - // try to render all those loaded notes one after each other. This only guarantees that correct note - // will be displayed independent of timing - const currentTreeNode = appContext.getMainNoteTree().getActiveNode(); - if (!newTab && currentTreeNode && currentTreeNode.data.noteId !== noteId) { - return; - } - - const loadPromise = loadNoteDetailToContext(ctx, notePath).then(() => { - if (activate) { - return appContext.activateTab(ctx.tabId); - } - else { - return Promise.resolve(); - } - }); - - if (!options.async) { - await loadPromise; - } -} - async function loadNote(noteId) { const row = await server.get('notes/' + noteId); @@ -116,49 +23,12 @@ async function loadNote(noteId) { return new NoteFull(treeCache, row, noteShort); } -async function noteDeleted(noteId) { - for (const tc of appContext.getTabContexts()) { - // not removing active even if it contains deleted note since that one will move to another note (handled by deletion logic) - // and we would lose tab context state (e.g. sidebar visibility) - if (!tc.isActive() && tc.notePath && tc.notePath.split("/").includes(noteId)) { - tabRow.removeTab(tc.tabId); - } - } -} - function focusOnTitle() { - appContext.getActiveTabContext().$noteTitle.trigger('focus'); + appContext.trigger('focusOnTitle'); } function focusAndSelectTitle() { - appContext.getActiveTabContext() - .$noteTitle - .trigger('focus') - .trigger('select'); -} - -/** - * Since detail loading may take some time and user might just browse through the notes using UP-DOWN keys, - * we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on - * fancytree's activate() won't wait for the full load. - * - * This causes an issue where in some cases you want to do some action after detail is loaded. For this reason - * we provide the listeners here which will be triggered after the detail is loaded and if the loaded note - * is the one registered in the listener. - */ -function addDetailLoadedListener(noteId, callback) { - detailLoadedListeners.push({ noteId, callback }); -} - -function fireDetailLoaded() { - for (const {noteId, callback} of detailLoadedListeners) { - if (noteId === appContext.getActiveTabNoteId()) { - callback(); - } - } - - // all the listeners are one time only - detailLoadedListeners = []; + appContext.trigger('focusAndSelectTitle'); } ws.subscribeToOutsideSyncMessages(syncData => { @@ -199,17 +69,9 @@ $(window).on('beforeunload', () => { }); export default { - reload, - openInTab, - switchToNote, loadNote, - loadNoteDetail, focusOnTitle, focusAndSelectTitle, - addDetailLoadedListener, getActiveEditor, - activateOrOpenNote, - noteDeleted, - noteChanged, - reloadNote + noteChanged }; \ No newline at end of file diff --git a/src/public/javascripts/services/protected_session.js b/src/public/javascripts/services/protected_session.js index ed65da06f..b0226f0e2 100644 --- a/src/public/javascripts/services/protected_session.js +++ b/src/public/javascripts/services/protected_session.js @@ -109,17 +109,12 @@ async function unprotectNoteAndSendToServer() { await appContext.getActiveTabContext().saveNote(); treeService.setProtected(activeNote.noteId, activeNote.isProtected); - - await noteDetailService.reload(); } async function protectSubtree(noteId, protect) { await enterProtectedSession(); await server.put('notes/' + noteId + "/protect/" + (protect ? 1 : 0)); - - treeService.reload(); - noteDetailService.reload(); } function makeToast(message, protectingLabel, text) { diff --git a/src/public/javascripts/services/tab_context.js b/src/public/javascripts/services/tab_context.js index a1961a929..1de5f5c28 100644 --- a/src/public/javascripts/services/tab_context.js +++ b/src/public/javascripts/services/tab_context.js @@ -8,6 +8,7 @@ import appContext from "./app_context.js"; import treeUtils from "./tree_utils.js"; import noteDetailService from "./note_detail.js"; import Component from "../widgets/component.js"; +import treeService from "./tree.js"; let showSidebarInNewTab = true; @@ -35,7 +36,19 @@ class TabContext extends Component { this.trigger('tabOpened', {tabId: this.tabId}); } - async setNote(notePath) { + async setNote(inputNotePath) { + const notePath = await treeService.resolveNotePath(inputNotePath); + + if (!notePath) { + console.error(`Cannot resolve note path ${inputNotePath}`); + return; + } + + if (notePath === this.notePath) { + console.log(`Setting existing notePath ${notePath} so ignoring ...`); + return; + } + await this.trigger('beforeNoteSwitch', {tabId: this.tabId}, true); this.notePath = notePath; @@ -64,6 +77,7 @@ class TabContext extends Component { } this.trigger('tabNoteSwitched', {tabId: this.tabId}); + this.trigger('openTabsChanged'); } async remove() { @@ -113,7 +127,16 @@ class TabContext extends Component { } stateChanged() { - appContext.openTabsChanged(); + appContext.openTabsChangedListener(); + } + + noteDeletedListener({noteId}) { + if (this.note && noteId === this.note.noteId) { + this.note = null; + this.notePath = null; + + this.trigger('tabNoteSwitched', {tabId: this.tabId}); + } } } diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 6343005a2..ba2ae9b67 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -263,12 +263,12 @@ async function treeInitialized() { } for (const tab of filteredTabs) { - await noteDetailService.loadNoteDetail(tab.notePath, { - state: tab, - newTab: true, - activate: tab.active, - async: true // faster initial load - }); + const tabContext = appContext.openEmptyTab(); + tabContext.setNote(tab.notePath); + + if (tab.active) { + appContext.activateTab(tabContext.tabId); + } } // previous opening triggered task to save tab changes but these are bogus changes (this is init) @@ -446,7 +446,7 @@ ws.subscribeToMessages(message => { reload(); } else if (message.type === 'open-note') { - noteDetailService.activateOrOpenNote(message.noteId); + appContext.activateOrOpenNote(message.noteId); if (utils.isElectron()) { const currentWindow = require("electron").remote.getCurrentWindow(); diff --git a/src/public/javascripts/services/tree_context_menu.js b/src/public/javascripts/services/tree_context_menu.js index d425d7c96..90d3e18d5 100644 --- a/src/public/javascripts/services/tree_context_menu.js +++ b/src/public/javascripts/services/tree_context_menu.js @@ -104,7 +104,9 @@ class TreeContextMenu { const notePath = await treeUtils.getNotePath(this.node); if (cmd === 'openInTab') { - noteDetailService.openInTab(notePath, false); + const tabContext = appContext.openEmptyTab(); + appContext.activateTab(tabContext.tabId); + tabContext.setNote(notePath); } else if (cmd.startsWith("insertNoteAfter")) { const parentNoteId = this.node.data.parentNoteId; diff --git a/src/public/javascripts/widgets/note_title.js b/src/public/javascripts/widgets/note_title.js index 8ff63180f..1bd0a5729 100644 --- a/src/public/javascripts/widgets/note_title.js +++ b/src/public/javascripts/widgets/note_title.js @@ -4,6 +4,7 @@ import protectedSessionHolder from "../services/protected_session_holder.js"; import treeCache from "../services/tree_cache.js"; import server from "../services/server.js"; import SpacedUpdate from "../services/spaced_update.js"; +import appContext from "../services/app_context.js"; const TPL = `
@@ -107,4 +108,18 @@ export default class NoteTitleWidget extends TabAwareWidget { await this.spacedUpdate.updateNowIfNecessary(); } } + + focusOnTitleListener() { + if (this.tabContext && this.tabContext.isActive()) { + this.$noteTitle.trigger('focus'); + } + } + + focusAndSelectTitleListener() { + if (this.tabContext && this.tabContext.isActive()) { + this.$noteTitle + .trigger('focus') + .trigger('select'); + } + } } \ No newline at end of file diff --git a/src/public/javascripts/widgets/note_tree.js b/src/public/javascripts/widgets/note_tree.js index 46107f50f..9bec4b87b 100644 --- a/src/public/javascripts/widgets/note_tree.js +++ b/src/public/javascripts/widgets/note_tree.js @@ -53,7 +53,8 @@ export default class NoteTreeWidget extends TabAwareWidget { treeUtils.getNotePath(node).then(notePath => { if (notePath) { - noteDetailService.openInTab(notePath, false); + const tabContext = appContext.openEmptyTab(); + tabContext.setNote(notePath); } }); @@ -87,7 +88,9 @@ export default class NoteTreeWidget extends TabAwareWidget { node.setFocus(true); } else if (event.ctrlKey) { - noteDetailService.loadNoteDetail(node.data.noteId, { newTab: true }); + const tabContext = appContext.openEmptyTab(); + treeUtils.getNotePath(node).then(notePath => tabContext.setNote(notePath)); + appContext.activateTab(tabContext.tabId); } else { node.setActive(); diff --git a/src/public/javascripts/widgets/note_type.js b/src/public/javascripts/widgets/note_type.js index 22e588959..48ced5399 100644 --- a/src/public/javascripts/widgets/note_type.js +++ b/src/public/javascripts/widgets/note_type.js @@ -130,9 +130,6 @@ export default class NoteTypeWidget extends TabAwareWidget { await noteDetailService.reload(); - // for the note icon to be updated in the tree - await treeService.reload(); - this.update(); } diff --git a/src/public/javascripts/widgets/search_box.js b/src/public/javascripts/widgets/search_box.js index 4a25f3857..ed72890bd 100644 --- a/src/public/javascripts/widgets/search_box.js +++ b/src/public/javascripts/widgets/search_box.js @@ -85,7 +85,7 @@ export default class SearchBoxWidget extends BasicWidget { this.$saveSearchButton.on('click', () => this.saveSearch()); - this.$closeSearchButton.on('click', () => this.hideSearchListener()); + this.$closeSearchButton.on('click', () => this.trigger('hideSearch')); return this.$widget; } @@ -159,6 +159,8 @@ export default class SearchBoxWidget extends BasicWidget { this.resetSearchListener(); this.$searchBox.slideUp(); + + this.trigger('hideSearchResults'); } toggleSearchListener() { @@ -167,7 +169,6 @@ export default class SearchBoxWidget extends BasicWidget { } else { this.hideSearchListener(); - this.trigger('hideSearchResults'); } } diff --git a/src/public/javascripts/widgets/search_results.js b/src/public/javascripts/widgets/search_results.js index 8d0fa4492..fac675fed 100644 --- a/src/public/javascripts/widgets/search_results.js +++ b/src/public/javascripts/widgets/search_results.js @@ -34,6 +34,8 @@ export default class SearchResultsWidget extends BasicWidget { this.$searchResults = this.$widget; this.$searchResultsInner = this.$widget.find(".search-results-inner"); + this.toggle(false); + return this.$widget; } diff --git a/src/public/javascripts/widgets/tab_caching_widget.js b/src/public/javascripts/widgets/tab_caching_widget.js index 6b89abeec..75fd65f02 100644 --- a/src/public/javascripts/widgets/tab_caching_widget.js +++ b/src/public/javascripts/widgets/tab_caching_widget.js @@ -20,6 +20,10 @@ export default class TabCachingWidget extends TabAwareWidget { widget.toggle(false); } + if (!this.tabContext) { + return; + } + let widget = this.widgets[this.tabContext.tabId]; if (!widget) { @@ -31,8 +35,6 @@ export default class TabCachingWidget extends TabAwareWidget { } widget.toggle(true); - - return false; // stop propagation to children } tabRemovedListener({tabId}) { diff --git a/src/public/javascripts/widgets/type_widgets/relation_map.js b/src/public/javascripts/widgets/type_widgets/relation_map.js index 16354e6a8..ffd9362e6 100644 --- a/src/public/javascripts/widgets/type_widgets/relation_map.js +++ b/src/public/javascripts/widgets/type_widgets/relation_map.js @@ -7,6 +7,7 @@ import contextMenuWidget from "../../services/context_menu.js"; import toastService from "../../services/toast.js"; import attributeAutocompleteService from "../../services/attribute_autocomplete.js"; import TypeWidget from "./type_widget.js"; +import appContext from "../../services/app_context.js"; const uniDirectionalOverlays = [ [ "Arrow", { @@ -196,7 +197,8 @@ export default class RelationMapTypeWidget extends TypeWidget { const noteId = this.idToNoteId($noteBox.prop("id")); if (cmd === "open-in-new-tab") { - noteDetailService.openInTab(noteId, false); + const tabContext = appContext.openEmptyTab(); + tabContext.setNote(noteId); } else if (cmd === "remove") { const confirmDialog = await import('../../dialogs/confirm.js');