diff --git a/src/public/javascripts/desktop.js b/src/public/javascripts/desktop.js index e564e88d4..c925b55bb 100644 --- a/src/public/javascripts/desktop.js +++ b/src/public/javascripts/desktop.js @@ -135,7 +135,7 @@ macInit.init(); searchNotesService.init(); // should be in front of treeService since that one manipulates address bar hash -appContext.showWidgets(); +appContext.start(); noteTooltipService.setupGlobalTooltip(); diff --git a/src/public/javascripts/dialogs/jump_to_note.js b/src/public/javascripts/dialogs/jump_to_note.js index 751280635..4bbf329c5 100644 --- a/src/public/javascripts/dialogs/jump_to_note.js +++ b/src/public/javascripts/dialogs/jump_to_note.js @@ -1,7 +1,7 @@ -import treeService from '../services/tree.js'; import searchNotesService from '../services/search_notes.js'; import noteAutocompleteService from '../services/note_autocomplete.js'; import utils from "../services/utils.js"; +import appContext from "../services/app_context.js"; const $dialog = $("#jump-to-note-dialog"); const $autoComplete = $("#jump-to-note-autocomplete"); @@ -22,7 +22,7 @@ export async function showDialog() { return false; } - treeService.activateNote(suggestion.path); + appContext.getActiveTabContext().setNote(suggestion.path); }); noteAutocompleteService.showRecentNotes($autoComplete); diff --git a/src/public/javascripts/dialogs/recent_changes.js b/src/public/javascripts/dialogs/recent_changes.js index 51a65982a..6f230c8d4 100644 --- a/src/public/javascripts/dialogs/recent_changes.js +++ b/src/public/javascripts/dialogs/recent_changes.js @@ -3,6 +3,7 @@ import utils from '../services/utils.js'; import server from '../services/server.js'; import treeService from "../services/tree.js"; import treeCache from "../services/tree_cache.js"; +import appContext from "../services/app_context.js"; const $dialog = $("#recent-changes-dialog"); const $content = $("#recent-changes-content"); @@ -54,7 +55,7 @@ export async function showDialog() { await treeCache.reloadNotes([change.noteId]); - treeService.activateNote(change.noteId); + appContext.getActiveTabContext().setNote(change.noteId); } }); diff --git a/src/public/javascripts/services/app_context.js b/src/public/javascripts/services/app_context.js index 34cab859f..b3c3c164c 100644 --- a/src/public/javascripts/services/app_context.js +++ b/src/public/javascripts/services/app_context.js @@ -42,8 +42,13 @@ class AppContext { this.activeTabId = null; } - showWidgets() { + start() { + this.showWidgets(); + bundleService.executeStartupBundles(); + } + + showWidgets() { this.tabRow = new TabRowWidget(this); const topPaneWidgets = [ @@ -247,18 +252,22 @@ class AppContext { await tabContext.setNote(noteId); } - async filterTabs(noteId) { + hoistedNoteChangedListener({hoistedNoteId}) { + if (hoistedNoteId === 'root') { + return; + } + for (const tc of this.tabContexts) { - if (tc.notePath && !tc.notePath.split("/").includes(noteId)) { + if (tc.notePath && !tc.notePath.split("/").includes(hoistedNoteId)) { this.tabRow.removeTab(tc.tabId); } } if (this.tabContexts.length === 0) { - this.openAndActivateEmptyTab() + this.openAndActivateEmptyTab(); } - await this.saveOpenTabs(); + this.saveOpenTabs(); } async saveOpenTabs() { diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js index f054217b6..6366fd5ac 100644 --- a/src/public/javascripts/services/frontend_script_api.js +++ b/src/public/javascripts/services/frontend_script_api.js @@ -38,7 +38,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte this.tabContext = tabContext; /** @property {CollapsibleWidget} */ - this.StandardWidget = CollapsibleWidget; + this.CollapsibleWidget = CollapsibleWidget; /** * Activates note in the tree and in the note detail. @@ -47,14 +47,8 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte * @param {string} notePath (or noteId) * @returns {Promise} */ - this.activateNote = async (notePath, noteLoadedListener) => { - await treeService.activateNote(notePath, async () => { - await appContext.getMainNoteTree().scrollToActiveNoteListener(); - - if (noteLoadedListener) { - noteLoadedListener(); - } - }); + this.activateNote = async notePath => { + await appContext.getActiveTabContext().setNote(notePath); }; /** @@ -66,7 +60,8 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte this.activateNewNote = async notePath => { await ws.waitForMaxKnownSyncId(); - await treeService.activateNote(notePath, () => appContext.trigger('focusAndSelectTitle')); + await appContext.getActiveTabContext().setNote(notePath); + appContext.trigger('focusAndSelectTitle'); }; /** diff --git a/src/public/javascripts/services/hoisted_note.js b/src/public/javascripts/services/hoisted_note.js index aaac4b48f..d7d42ed9c 100644 --- a/src/public/javascripts/services/hoisted_note.js +++ b/src/public/javascripts/services/hoisted_note.js @@ -1,6 +1,7 @@ import optionsService from './options.js'; import server from "./server.js"; import appContext from "./app_context.js"; +import treeService from "./tree.js"; let hoistedNoteId = 'root'; @@ -19,15 +20,11 @@ async function getHoistedNoteId() { } async function setHoistedNoteId(noteId) { - if (noteId !== 'root') { - await appContext.filterTabs(noteId); - } - hoistedNoteId = noteId; await server.put('options/hoistedNoteId/' + noteId); - appContext.trigger('hoistedNoteChanged'); + appContext.trigger('hoistedNoteChanged', {hoistedNoteId}); } async function unhoist() { @@ -44,11 +41,38 @@ async function isRootNode(node) { || node.data.noteId === await getHoistedNoteId(); } +async function checkNoteAccess(notePath) { + // notePath argument can contain only noteId which is not good when hoisted since + // then we need to check the whole note path + const runNotePath = await treeService.getRunPath(notePath); + + if (!runNotePath) { + console.log("Cannot activate " + notePath); + return false; + } + + const hoistedNoteId = await getHoistedNoteId(); + + if (hoistedNoteId !== 'root' && !runNotePath.includes(hoistedNoteId)) { + const confirmDialog = await import('../dialogs/confirm.js'); + + if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?")) { + return false; + } + + // unhoist so we can activate the note + await unhoist(); + } + + return true; +} + export default { getHoistedNoteId, getHoistedNoteNoPromise, setHoistedNoteId, unhoist, isTopLevelNode, - isRootNode + isRootNode, + checkNoteAccess } \ No newline at end of file diff --git a/src/public/javascripts/services/import.js b/src/public/javascripts/services/import.js index 82ea93916..942387978 100644 --- a/src/public/javascripts/services/import.js +++ b/src/public/javascripts/services/import.js @@ -67,9 +67,7 @@ ws.subscribeToMessages(async message => { appContext.trigger('reloadNotes', {noteIds: [message.result.parentNoteId]}); if (message.result.importedNoteId) { - const node = await treeService.activateNote(message.result.importedNoteId); - - node.setExpanded(true); + await appContext.getActiveTabContext.setNote(message.result.importedNoteId); } } }); diff --git a/src/public/javascripts/services/keyboard_actions.js b/src/public/javascripts/services/keyboard_actions.js index e021b8b10..da798257a 100644 --- a/src/public/javascripts/services/keyboard_actions.js +++ b/src/public/javascripts/services/keyboard_actions.js @@ -26,7 +26,7 @@ server.get('keyboard-shortcuts-for-notes').then(shortcutForNotes => { utils.bindGlobalShortcut(shortcut, async () => { const treeService = (await import("./tree.js")).default; - treeService.activateNote(shortcutForNotes[shortcut]); + appContext.getActiveTabContext().setNote(shortcutForNotes[shortcut]); }); } }); diff --git a/src/public/javascripts/services/protected_session.js b/src/public/javascripts/services/protected_session.js index ea976fbe7..674a1f52c 100644 --- a/src/public/javascripts/services/protected_session.js +++ b/src/public/javascripts/services/protected_session.js @@ -79,8 +79,6 @@ async function protectNoteAndSendToServer() { note.isProtected = true; await appContext.getActiveTabContext().saveNote(); - - treeService.setProtected(note.noteId, note.isProtected); } async function unprotectNoteAndSendToServer() { @@ -104,8 +102,6 @@ async function unprotectNoteAndSendToServer() { activeNote.isProtected = false; await appContext.getActiveTabContext().saveNote(); - - treeService.setProtected(activeNote.noteId, activeNote.isProtected); } async function protectSubtree(noteId, protect) { diff --git a/src/public/javascripts/services/tab_context.js b/src/public/javascripts/services/tab_context.js index 987cde4ef..dbce42527 100644 --- a/src/public/javascripts/services/tab_context.js +++ b/src/public/javascripts/services/tab_context.js @@ -7,6 +7,7 @@ import appContext from "./app_context.js"; import treeService from "./tree.js"; import Component from "../widgets/component.js"; import treeCache from "./tree_cache.js"; +import hoistedNoteService from "./hoisted_note.js"; let showSidebarInNewTab = true; @@ -42,6 +43,10 @@ class TabContext extends Component { return; } + if (await hoistedNoteService.checkNoteAccess(notePath) === false) { + return; // note is outside of hoisted subtree and user chose not to unhoist + } + await this.trigger('beforeNoteSwitch', {tabId: this.tabId}, true); this.notePath = notePath; @@ -73,6 +78,7 @@ class TabContext extends Component { tabId: this.tabId, notePath: this.notePath }); + this.trigger('openTabsChanged'); } diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 5ec472573..c58aa50ef 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -10,73 +10,6 @@ import optionsService from "../services/options.js"; import bundle from "./bundle.js"; import appContext from "./app_context.js"; -let setFrontendAsLoaded; -const frontendLoaded = new Promise(resolve => { setFrontendAsLoaded = resolve; }); - -async function setPrefix(branchId, prefix) { - utils.assertArguments(branchId); - - const branch = treeCache.getBranch(branchId); - - branch.prefix = prefix; - - for (const node of await appContext.getMainNoteTree().getNodesByBranchId(branchId)) { - await setNodeTitleWithPrefix(node); - } -} - -async function setNodeTitleWithPrefix(node) { - const noteTitle = await getNoteTitle(node.data.noteId); - const branch = treeCache.getBranch(node.data.branchId); - - const prefix = branch.prefix; - - const title = (prefix ? (prefix + " - ") : "") + noteTitle; - - node.setTitle(utils.escapeHtml(title)); -} - -// FIXME: unused? -/** @return {FancytreeNode} */ -async function activateNote(notePath, noteLoadedListener) { - utils.assertArguments(notePath); - - // notePath argument can contain only noteId which is not good when hoisted since - // then we need to check the whole note path - const runNotePath = await getRunPath(notePath); - - if (!runNotePath) { - console.log("Cannot activate " + notePath); - return; - } - - const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); - - if (hoistedNoteId !== 'root' && !runNotePath.includes(hoistedNoteId)) { - const confirmDialog = await import('../dialogs/confirm.js'); - - if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree. Do you want to unhoist?")) { - return; - } - - // unhoist so we can activate the note - await hoistedNoteService.unhoist(); - } - - utils.closeActiveDialog(); - - const node = await appContext.getMainNoteTree().expandToNote(notePath); - - if (noteLoadedListener) { - // FIXME - noteDetailService.addDetailLoadedListener(node.data.noteId, noteLoadedListener); - } - - await node.setActive(true); - - return node; -} - /** * Accepts notePath which might or might not be valid and returns an existing path as close to the original * notePath as possible. @@ -274,8 +207,6 @@ async function treeInitialized() { // previous opening triggered task to save tab changes but these are bogus changes (this is init) // so we'll cancel it appContext.clearOpenTabsTask(); - - setFrontendAsLoaded(); } function isNotePathInAddress() { @@ -292,13 +223,6 @@ function getHashValueFromAddress() { return str.split("-"); } -function setProtected(noteId, isProtected) { - appContext.getMainNoteTree().getNodesByNoteId(noteId).map(node => { - node.data.isProtected = isProtected; - node.toggleClass("protected", isProtected); - }); -} - async function createNewTopLevelNote() { const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); @@ -580,12 +504,7 @@ async function getNotePathTitle(notePath) { return titlePath.join(' / '); } -frontendLoaded.then(bundle.executeStartupBundles); - export default { - setProtected, - activateNote, - setPrefix, createNote, sortAlphabetically, treeInitialized, @@ -594,7 +513,6 @@ export default { createNewTopLevelNote, duplicateNote, getRunPath, - setNodeTitleWithPrefix, getParentProtectedStatus, getNotePath, getNoteIdFromNotePath, diff --git a/src/public/javascripts/widgets/calendar.js b/src/public/javascripts/widgets/calendar.js index 298f41dd1..b2c7a8b53 100644 --- a/src/public/javascripts/widgets/calendar.js +++ b/src/public/javascripts/widgets/calendar.js @@ -4,6 +4,7 @@ import utils from "../services/utils.js"; import dateNoteService from "../services/date_notes.js"; import treeService from "../services/tree.js"; import server from "../services/server.js"; +import appContext from "../services/app_context.js"; const TPL = `
@@ -58,7 +59,7 @@ export default class CalendarWidget extends CollapsibleWidget { const note = await dateNoteService.getDateNote(date); if (note) { - treeService.activateNote(note.noteId); + appContext.getActiveTabContext().setNote(note.noteId); } else { alert("Cannot find day note"); diff --git a/src/public/javascripts/widgets/type_widgets/empty.js b/src/public/javascripts/widgets/type_widgets/empty.js index 0f401d476..7be599b4a 100644 --- a/src/public/javascripts/widgets/type_widgets/empty.js +++ b/src/public/javascripts/widgets/type_widgets/empty.js @@ -1,6 +1,7 @@ import noteAutocompleteService from '../../services/note_autocomplete.js'; import treeService from "../../services/tree.js"; import TypeWidget from "./type_widget.js"; +import appContext from "../../services/app_context.js"; const TPL = `
@@ -27,7 +28,7 @@ export default class EmptyTypeWidget extends TypeWidget { return false; } - treeService.activateNote(suggestion.path); + appContext.getActiveTabContext().setNote(suggestion.path); }); noteAutocompleteService.showRecentNotes(this.$autoComplete); diff --git a/src/public/javascripts/widgets/type_widgets/text.js b/src/public/javascripts/widgets/type_widgets/text.js index 80cf14da6..1c60d1b71 100644 --- a/src/public/javascripts/widgets/type_widgets/text.js +++ b/src/public/javascripts/widgets/type_widgets/text.js @@ -4,6 +4,7 @@ import noteAutocompleteService from '../../services/note_autocomplete.js'; import mimeTypesService from '../../services/mime_types.js'; import TypeWidget from "./type_widget.js"; import utils from "../../services/utils.js"; +import appContext from "../../services/app_context.js"; const ENABLE_INSPECTOR = false; @@ -90,7 +91,7 @@ export default class TextTypeWidget extends TypeWidget { if (match) { const noteId = match[1]; - treeService.activateNote(noteId); + appContext.getActiveTabContext().setNote(noteId); } else { window.open(src, '_blank');