diff --git a/src/public/javascripts/dialogs/attributes.js b/src/public/javascripts/dialogs/attributes.js index 1d477b815..5f4480c06 100644 --- a/src/public/javascripts/dialogs/attributes.js +++ b/src/public/javascripts/dialogs/attributes.js @@ -167,7 +167,7 @@ function AttributesModel() { infoService.showMessage("Attributes have been saved."); - const ctx = noteDetailService.getActiveContext(); + const ctx = noteDetailService.getActiveTabContext(); ctx.attributes.refreshAttributes(); diff --git a/src/public/javascripts/services/hoisted_note.js b/src/public/javascripts/services/hoisted_note.js index ebeb26351..ff7458911 100644 --- a/src/public/javascripts/services/hoisted_note.js +++ b/src/public/javascripts/services/hoisted_note.js @@ -16,12 +16,12 @@ async function getHoistedNoteId() { } async function setHoistedNoteId(noteId) { - hoistedNoteId = noteId; - if (noteId !== 'root') { await noteDetailService.filterTabs(noteId); } + hoistedNoteId = noteId; + await server.put('options/hoistedNoteId/' + noteId); await tree.reload(); diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 0272c6eee..a7ec0cd77 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -19,7 +19,7 @@ let detailLoadedListeners = []; /** @return {NoteFull} */ function getActiveNote() { - const activeContext = getActiveContext(); + const activeContext = getActiveTabContext(); return activeContext ? activeContext.note : null; } @@ -38,7 +38,7 @@ function getActiveNoteType() { async function reload() { // no saving here - await loadNoteDetail(getActiveNoteId()); + await loadNoteDetail(getActiveTabContext().notePath); } async function reloadAllTabs() { @@ -46,12 +46,11 @@ async function reloadAllTabs() { const note = await loadNote(tabContext.note.noteId); await loadNoteDetailToContext(tabContext, note, tabContext.notePath); - } } -async function openInTab(noteId) { - await loadNoteDetail(noteId, { newTab: true }); +async function openInTab(notePath) { + await loadNoteDetail(notePath, { newTab: true }); } async function switchToNote(notePath) { @@ -63,11 +62,11 @@ async function switchToNote(notePath) { } function getActiveNoteContent() { - return getActiveContext().getComponent().getContent(); + return getActiveTabContext().getComponent().getContent(); } function onNoteChange(func) { - return getActiveContext().getComponent().onNoteChange(func); + return getActiveTabContext().getComponent().onNoteChange(func); } async function saveNotesIfChanged() { @@ -83,11 +82,15 @@ async function saveNotesIfChanged() { let tabContexts = []; function getActiveComponent() { - return getActiveContext().getComponent(); + return getActiveTabContext().getComponent(); +} + +function getTabContexts() { + return tabContexts; } /** @returns {TabContext} */ -function getActiveContext() { +function getActiveTabContext() { for (const ctx of tabContexts) { if (ctx.$tabContent.is(":visible")) { return ctx; @@ -110,7 +113,7 @@ async function showTab(tabId) { treeService.clearSelectedNodes(); - const newActiveTabContext = getActiveContext(); + const newActiveTabContext = getActiveTabContext(); const newActiveNode = await treeService.getNodeFromPath(newActiveTabContext.notePath); if (newActiveNode && newActiveNode.isVisible()) { @@ -181,6 +184,8 @@ async function loadNoteDetail(notePath, options = {}) { const newTab = !!options.newTab; const activate = !!options.activate; + notePath = await treeService.resolveNotePath(notePath); + const noteId = treeUtils.getNoteIdFromNotePath(notePath); const loadedNote = await loadNote(noteId); let ctx; @@ -191,7 +196,7 @@ async function loadNoteDetail(notePath, options = {}) { tabContexts.push(ctx); } else { - ctx = getActiveContext(); + ctx = getActiveTabContext(); } // we will try to render the new note only if it's still the active one in the tree @@ -219,23 +224,27 @@ async function loadNote(noteId) { async function filterTabs(noteId) { for (const tc of tabContexts) { - tabRow.removeTab(tc.tab); + if (tc.notePath && !tc.notePath.split("/").includes(noteId)) { + await tabRow.removeTab(tc.tab); + } } - await loadNoteDetail(noteId, { - newTab: true, - activate: true - }); + if (tabContexts.length === 0) { + await loadNoteDetail(noteId, { + newTab: true, + activate: true + }); + } await saveOpenTabs(); } function focusOnTitle() { - getActiveContext().$noteTitle.focus(); + getActiveTabContext().$noteTitle.focus(); } function focusAndSelectTitle() { - getActiveContext().$noteTitle.focus().select(); + getActiveTabContext().$noteTitle.focus().select(); } /** @@ -284,22 +293,21 @@ $tabContentsContainer.on("drop", e => { }); }); -tabRow.el.addEventListener('activeTabChange', ({ detail }) => { +tabRow.addListener('activeTabChange', async ({ detail }) => { const tabId = detail.tabEl.getAttribute('data-tab-id'); - showTab(tabId); + await showTab(tabId); console.log(`Activated tab ${tabId}`); }); -tabRow.el.addEventListener('tabRemove', async ({ detail }) => { +tabRow.addListener('tabRemove', async ({ detail }) => { const tabId = parseInt(detail.tabEl.getAttribute('data-tab-id')); - await saveNotesIfChanged(); - const tabContentToDelete = tabContexts.find(nc => nc.tabId === tabId); if (tabContentToDelete) { + await tabContentToDelete.saveNoteIfChanged(); tabContentToDelete.$tabContent.remove(); } @@ -352,10 +360,9 @@ if (utils.isElectron()) { }); } -tabRow.el.addEventListener('activeTabChange', openTabsChanged); -tabRow.el.addEventListener('tabAdd', openTabsChanged); -tabRow.el.addEventListener('tabRemove', openTabsChanged); -tabRow.el.addEventListener('tabReorder', openTabsChanged); +tabRow.addListener('activeTabChange', openTabsChanged); +tabRow.addListener('tabRemove', openTabsChanged); +tabRow.addListener('tabReorder', openTabsChanged); let tabsChangedTaskId = null; @@ -417,7 +424,8 @@ export default { saveNotesIfChanged, onNoteChange, addDetailLoadedListener, - getActiveContext, + getTabContexts, + getActiveTabContext, getActiveComponent, clearOpenTabsTask, filterTabs diff --git a/src/public/javascripts/services/protected_session.js b/src/public/javascripts/services/protected_session.js index be94c4637..919083241 100644 --- a/src/public/javascripts/services/protected_session.js +++ b/src/public/javascripts/services/protected_session.js @@ -79,7 +79,7 @@ async function protectNoteAndSendToServer() { const note = noteDetailService.getActiveNote(); note.isProtected = true; - await noteDetailService.getActiveContext().saveNote(); + await noteDetailService.getActiveTabContext().saveNote(); treeService.setProtected(note.noteId, note.isProtected); @@ -106,7 +106,7 @@ async function unprotectNoteAndSendToServer() { activeNote.isProtected = false; - await noteDetailService.getActiveContext().saveNote(); + await noteDetailService.getActiveTabContext().saveNote(); treeService.setProtected(activeNote.noteId, activeNote.isProtected); diff --git a/src/public/javascripts/services/tab_row.js b/src/public/javascripts/services/tab_row.js index 99b4446e7..d89fc937d 100644 --- a/src/public/javascripts/services/tab_row.js +++ b/src/public/javascripts/services/tab_row.js @@ -19,8 +19,6 @@ const TAB_SIZE_SMALL = 84; const TAB_SIZE_SMALLER = 60; const TAB_SIZE_MINI = 48; -const noop = _ => {}; - const closest = (value, array) => { let closest = Infinity; let closestIndex = -1; @@ -53,7 +51,8 @@ let instanceId = 0; class TabRow { constructor() { - this.draggabillies = [] + this.draggabillies = []; + this.eventListeners = {}; } init(el) { @@ -71,8 +70,15 @@ class TabRow { this.setVisibility(); } - emit(eventName, data) { - this.el.dispatchEvent(new CustomEvent(eventName, { detail: data })); + addListener(eventName, callback) { + this.eventListeners[eventName] = this.eventListeners[eventName] || []; + this.eventListeners[eventName].push(callback); + } + + async emit(eventName, data) { + for (const listener of this.eventListeners[eventName]) { + await listener({ detail: data }); + } } setupCustomProperties() { @@ -194,7 +200,6 @@ class TabRow { this.setVisibility(); this.setTabCloseEventListener(tabEl); this.updateTab(tabEl, tabProperties); - this.emit('tabAdd', { tabEl }); if (!background) this.setCurrentTab(tabEl); this.cleanUpPreviouslyDraggedTabs(); this.layoutTabs(); @@ -255,34 +260,34 @@ class TabRow { return !!this.activeTabEl; } - setCurrentTab(tabEl) { + async setCurrentTab(tabEl) { const activeTabEl = this.activeTabEl; if (activeTabEl === tabEl) return; if (activeTabEl) activeTabEl.removeAttribute('active'); tabEl.setAttribute('active', ''); - this.emit('activeTabChange', { tabEl }); + await this.emit('activeTabChange', { tabEl }); } - removeTab(tabEl) { + async removeTab(tabEl) { if (tabEl === this.activeTabEl) { if (tabEl.nextElementSibling) { - this.setCurrentTab(tabEl.nextElementSibling) + await this.setCurrentTab(tabEl.nextElementSibling) } else if (tabEl.previousElementSibling) { - this.setCurrentTab(tabEl.previousElementSibling) + await this.setCurrentTab(tabEl.previousElementSibling) } } tabEl.parentNode.removeChild(tabEl); - this.emit('tabRemove', { tabEl }); + await this.emit('tabRemove', { tabEl }); this.cleanUpPreviouslyDraggedTabs(); this.layoutTabs(); this.setupDraggabilly(); this.setVisibility(); } - removeAllTabsExceptForThis(remainingTabEl) { + async removeAllTabsExceptForThis(remainingTabEl) { for (const tabEl of this.tabEls) { if (remainingTabEl !== tabEl) { - this.removeTab(tabEl); + await this.removeTab(tabEl); } } } @@ -310,7 +315,7 @@ class TabRow { this.draggabillyDragging.element.style.transform = ''; this.draggabillyDragging.dragEnd(); this.draggabillyDragging.isDragging = false; - this.draggabillyDragging.positionDrag = noop; // Prevent Draggabilly from updating tabEl.style.transform in later frames + this.draggabillyDragging.positionDrag = _ => {}; // Prevent Draggabilly from updating tabEl.style.transform in later frames this.draggabillyDragging.destroy(); this.draggabillyDragging = null; } @@ -380,13 +385,13 @@ class TabRow { }) } - animateTabMove(tabEl, originIndex, destinationIndex) { + async animateTabMove(tabEl, originIndex, destinationIndex) { if (destinationIndex < originIndex) { tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex]); } else { tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex + 1]); } - this.emit('tabReorder', { tabEl, originIndex, destinationIndex }); + await this.emit('tabReorder', { tabEl, originIndex, destinationIndex }); this.layoutTabs(); } } diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index a6d6b3813..ea8d04fc1 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -165,6 +165,16 @@ async function activateNote(notePath, noteLoadedListener) { return node; } +/** + * Accepts notePath which might or might not be valid and returns an existing path as close to the original + * notePath as possible. + */ +async function resolveNotePath(notePath) { + const runPath = await getRunPath(notePath); + + return runPath.join("/"); +} + /** * Accepts notePath and tries to resolve it. Part of the path might not be valid because of note moving (which causes * path change) or other corruption, in that case this will try to get some other valid path to the correct note. @@ -358,6 +368,11 @@ function clearSelectedNodes() { } async function treeInitialized() { + if (noteDetailService.getTabContexts().length > 0) { + // this is just tree reload - tabs are already in place + return; + } + let openTabs = []; try { @@ -571,7 +586,7 @@ async function collapseTree(node = null) { } async function scrollToActiveNote() { - const activeContext = noteDetailService.getActiveContext(); + const activeContext = noteDetailService.getActiveTabContext(); if (activeContext) { const node = await expandToNote(activeContext.notePath); @@ -864,5 +879,6 @@ export default { reloadNote, loadTreeCache, expandToNote, - getNodeFromPath + getNodeFromPath, + resolveNotePath }; \ No newline at end of file diff --git a/src/public/javascripts/services/tree_context_menu.js b/src/public/javascripts/services/tree_context_menu.js index 3740f4836..608aa2657 100644 --- a/src/public/javascripts/services/tree_context_menu.js +++ b/src/public/javascripts/services/tree_context_menu.js @@ -78,7 +78,9 @@ class TreeContextMenu { async selectContextMenuItem(event, cmd) { if (cmd === 'openInTab') { - noteDetailService.openInTab(this.node.data.noteId); + const notePath = treeUtils.getNotePath(this.node); + + noteDetailService.openInTab(notePath); } else if (cmd.startsWith("insertNoteAfter")) { const parentNoteId = this.node.data.parentNoteId;