mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	navigation state is more nicely and completely serialized into URL
This commit is contained in:
		| @@ -155,14 +155,14 @@ $(window).on('beforeunload', () => { | |||||||
|  |  | ||||||
| $(window).on('hashchange', function() { | $(window).on('hashchange', function() { | ||||||
|     if (treeService.isNotePathInAddress()) { |     if (treeService.isNotePathInAddress()) { | ||||||
|         const [notePath, ntxId] = treeService.getHashValueFromAddress(); |         const {notePath, ntxId, viewScope} = treeService.parseNavigationStateFromAddress(); | ||||||
|  |  | ||||||
|         if (!notePath && !ntxId) { |         if (!notePath && !ntxId) { | ||||||
|             console.log(`Invalid hash value "${document.location.hash}", ignoring.`); |             console.log(`Invalid hash value "${document.location.hash}", ignoring.`); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         appContext.tabManager.switchToNoteContext(ntxId, notePath); |         appContext.tabManager.switchToNoteContext(ntxId, notePath, viewScope); | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import toastService from "../services/toast.js"; | |||||||
| import ws from "../services/ws.js"; | import ws from "../services/ws.js"; | ||||||
| import bundleService from "../services/bundle.js"; | import bundleService from "../services/bundle.js"; | ||||||
| import froca from "../services/froca.js"; | import froca from "../services/froca.js"; | ||||||
|  | import linkService from "../services/link.js"; | ||||||
|  |  | ||||||
| export default class Entrypoints extends Component { | export default class Entrypoints extends Component { | ||||||
|     constructor() { |     constructor() { | ||||||
| @@ -136,17 +137,15 @@ export default class Entrypoints extends Component { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async openInWindowCommand({notePath, hoistedNoteId, viewScope}) { |     async openInWindowCommand({notePath, hoistedNoteId, viewScope}) { | ||||||
|         if (!hoistedNoteId) { |         const extraWindowHash = linkService.calculateHash({notePath, hoistedNoteId, viewScope}); | ||||||
|             hoistedNoteId = 'root'; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (utils.isElectron()) { |         if (utils.isElectron()) { | ||||||
|             const {ipcRenderer} = utils.dynamicRequire('electron'); |             const {ipcRenderer} = utils.dynamicRequire('electron'); | ||||||
|  |  | ||||||
|             ipcRenderer.send('create-extra-window', {notePath, hoistedNoteId, viewScope}); |             ipcRenderer.send('create-extra-window', { extraWindowHash }); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1&extraHoistedNoteId=${hoistedNoteId}&extraViewScope=${JSON.stringify(viewScope)}#${notePath}`; |             const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1${extraWindowHash}`; | ||||||
|  |  | ||||||
|             window.open(url, '', 'width=1000,height=800'); |             window.open(url, '', 'width=1000,height=800'); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -12,13 +12,17 @@ class NoteContext extends Component { | |||||||
|     constructor(ntxId = null, hoistedNoteId = 'root', mainNtxId = null) { |     constructor(ntxId = null, hoistedNoteId = 'root', mainNtxId = null) { | ||||||
|         super(); |         super(); | ||||||
|  |  | ||||||
|         this.ntxId = ntxId || utils.randomString(4); |         this.ntxId = ntxId || this.constructor.generateNtxId(); | ||||||
|         this.hoistedNoteId = hoistedNoteId; |         this.hoistedNoteId = hoistedNoteId; | ||||||
|         this.mainNtxId = mainNtxId; |         this.mainNtxId = mainNtxId; | ||||||
|  |  | ||||||
|         this.resetViewScope(); |         this.resetViewScope(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     static generateNtxId() { | ||||||
|  |         return utils.randomString(6); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     setEmpty() { |     setEmpty() { | ||||||
|         this.notePath = null; |         this.notePath = null; | ||||||
|         this.noteId = null; |         this.noteId = null; | ||||||
| @@ -57,9 +61,8 @@ class NoteContext extends Component { | |||||||
|         utils.closeActiveDialog(); |         utils.closeActiveDialog(); | ||||||
|  |  | ||||||
|         this.notePath = resolvedNotePath; |         this.notePath = resolvedNotePath; | ||||||
|         ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath)); |  | ||||||
|  |  | ||||||
|         this.viewScope = opts.viewScope; |         this.viewScope = opts.viewScope; | ||||||
|  |         ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath)); | ||||||
|  |  | ||||||
|         this.saveToRecentNotes(resolvedNotePath); |         this.saveToRecentNotes(resolvedNotePath); | ||||||
|  |  | ||||||
| @@ -298,6 +301,29 @@ class NoteContext extends Component { | |||||||
|         // this is reset after navigating to a different note |         // this is reset after navigating to a different note | ||||||
|         this.viewScope = {}; |         this.viewScope = {}; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async getNavigationTitle() { | ||||||
|  |         if (!this.note) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const { note, viewScope } = this; | ||||||
|  |  | ||||||
|  |         let title = viewScope.viewMode === 'default' | ||||||
|  |             ? note.title | ||||||
|  |             : `${note.title}: ${viewScope.viewMode}`; | ||||||
|  |  | ||||||
|  |         if (viewScope.attachmentId) { | ||||||
|  |             // assuming the attachment has been already loaded | ||||||
|  |             const attachment = await note.getAttachmentById(viewScope.attachmentId); | ||||||
|  |  | ||||||
|  |             if (attachment) { | ||||||
|  |                 title += `: ${attachment.title}`; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return title; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export default NoteContext; | export default NoteContext; | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import utils from "../services/utils.js"; | |||||||
| import NoteContext from "./note_context.js"; | import NoteContext from "./note_context.js"; | ||||||
| import appContext from "./app_context.js"; | import appContext from "./app_context.js"; | ||||||
| import Mutex from "../utils/mutex.js"; | import Mutex from "../utils/mutex.js"; | ||||||
|  | import linkService from "../services/link.js"; | ||||||
|  |  | ||||||
| export default class TabManager extends Component { | export default class TabManager extends Component { | ||||||
|     constructor() { |     constructor() { | ||||||
| @@ -53,45 +54,44 @@ export default class TabManager extends Component { | |||||||
|                 ? (options.getJson('openTabs') || []) |                 ? (options.getJson('openTabs') || []) | ||||||
|                 : []; |                 : []; | ||||||
|  |  | ||||||
|             let filteredTabs = []; |  | ||||||
|  |  | ||||||
|             // preload all notes at once |             // preload all notes at once | ||||||
|             await froca.getNotes([ |             await froca.getNotes([ | ||||||
|                     ...tabsToOpen.map(tab => treeService.getNoteIdFromNotePath(tab.notePath)), |                     ...tabsToOpen.map(tab => treeService.getNoteIdFromNotePath(tab.notePath)), | ||||||
|                     ...tabsToOpen.map(tab => tab.hoistedNoteId), |                     ...tabsToOpen.map(tab => tab.hoistedNoteId), | ||||||
|             ], true); |             ], true); | ||||||
|  |  | ||||||
|             for (const openTab of tabsToOpen) { |             const filteredTabs = tabsToOpen.filter(openTab => { | ||||||
|                 if (openTab.notePath && !(treeService.getNoteIdFromNotePath(openTab.notePath) in froca.notes)) { |                 if (utils.isMobile()) { // mobile frontend doesn't have tabs so show only the active tab | ||||||
|  |                     return !!openTab.active; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 const noteId = treeService.getNoteIdFromNotePath(openTab.notePath); | ||||||
|  |                 if (!(noteId in froca.notes)) { | ||||||
|                     // note doesn't exist so don't try to open tab for it |                     // note doesn't exist so don't try to open tab for it | ||||||
|                     continue; |                     return false; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (!(openTab.hoistedNoteId in froca.notes)) { |                 if (!(openTab.hoistedNoteId in froca.notes)) { | ||||||
|                     openTab.hoistedNoteId = 'root'; |                     openTab.hoistedNoteId = 'root'; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 filteredTabs.push(openTab); |                 return true; | ||||||
|             } |             }); | ||||||
|  |  | ||||||
|             if (utils.isMobile()) { |  | ||||||
|                 // mobile frontend doesn't have tabs so show only the active tab |  | ||||||
|                 filteredTabs = filteredTabs.filter(tab => tab.active); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // resolve before opened tabs can change this |             // resolve before opened tabs can change this | ||||||
|             const [notePathInUrl, ntxIdInUrl] = treeService.getHashValueFromAddress(); |             const parsedFromUrl = treeService.parseNavigationStateFromAddress(); | ||||||
|  |  | ||||||
|             if (filteredTabs.length === 0) { |             if (filteredTabs.length === 0) { | ||||||
|                 filteredTabs.push({ |                 parsedFromUrl.ntxId = parsedFromUrl.ntxId || NoteContext.generateNtxId(); // generate already here, so that we later know which one to activate | ||||||
|                     notePath: notePathInUrl || 'root', |  | ||||||
|                     active: true, |  | ||||||
|                     hoistedNoteId: glob.extraHoistedNoteId || 'root', |  | ||||||
|                     viewScope: glob.extraViewScope || {} |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (!filteredTabs.find(tab => tab.active)) { |                 filteredTabs.push({ | ||||||
|  |                     notePath: parsedFromUrl.notePath || 'root', | ||||||
|  |                     ntxId: parsedFromUrl.ntxId, | ||||||
|  |                     active: true, | ||||||
|  |                     hoistedNoteId: parsedFromUrl.hoistedNoteId || 'root', | ||||||
|  |                     viewScope: parsedFromUrl.viewScope || {} | ||||||
|  |                 }); | ||||||
|  |             } else if (!filteredTabs.find(tab => tab.active)) { | ||||||
|                 filteredTabs[0].active = true; |                 filteredTabs[0].active = true; | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -109,8 +109,13 @@ export default class TabManager extends Component { | |||||||
|  |  | ||||||
|             // if there's notePath in the URL, make sure it's open and active |             // if there's notePath in the URL, make sure it's open and active | ||||||
|             // (useful, for e.g. opening clipped notes from clipper or opening link in an extra window) |             // (useful, for e.g. opening clipped notes from clipper or opening link in an extra window) | ||||||
|             if (notePathInUrl) { |             if (parsedFromUrl.notePath) { | ||||||
|                 await appContext.tabManager.switchToNoteContext(ntxIdInUrl, notePathInUrl); |                 await appContext.tabManager.switchToNoteContext( | ||||||
|  |                     parsedFromUrl.ntxId, | ||||||
|  |                     parsedFromUrl.notePath, | ||||||
|  |                     parsedFromUrl.viewScope, | ||||||
|  |                     parsedFromUrl.hoistedNoteId | ||||||
|  |                 ); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (e) { |         catch (e) { | ||||||
| @@ -123,28 +128,41 @@ export default class TabManager extends Component { | |||||||
|  |  | ||||||
|     noteSwitchedEvent({noteContext}) { |     noteSwitchedEvent({noteContext}) { | ||||||
|         if (noteContext.isActive()) { |         if (noteContext.isActive()) { | ||||||
|             this.setCurrentNotePathToHash(); |             this.setCurrentNavigationStateToHash(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.tabsUpdate.scheduleUpdate(); |         this.tabsUpdate.scheduleUpdate(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     setCurrentNotePathToHash() { |     setCurrentNavigationStateToHash() { | ||||||
|         const activeNoteContext = this.getActiveContext(); |         const calculatedHash = this.calculateHash(); | ||||||
|  |  | ||||||
|         if (window.history.length === 0 // first history entry |  | ||||||
|             || (activeNoteContext && activeNoteContext.notePath !== treeService.getHashValueFromAddress()[0])) { |  | ||||||
|             const url = `#${activeNoteContext.notePath || ""}-${activeNoteContext.ntxId}`; |  | ||||||
|  |  | ||||||
|  |         // update if it's the first history entry or there has been a change | ||||||
|  |         if (window.history.length === 0 || calculatedHash !== window.location?.hash) { | ||||||
|             // using pushState instead of directly modifying document.location because it does not trigger hashchange |             // using pushState instead of directly modifying document.location because it does not trigger hashchange | ||||||
|             window.history.pushState(null, "", url); |             window.history.pushState(null, "", calculatedHash); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         const activeNoteContext = this.getActiveContext(); | ||||||
|         this.updateDocumentTitle(activeNoteContext); |         this.updateDocumentTitle(activeNoteContext); | ||||||
|  |  | ||||||
|         this.triggerEvent('activeNoteChanged'); // trigger this even in on popstate event |         this.triggerEvent('activeNoteChanged'); // trigger this even in on popstate event | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     calculateHash() { | ||||||
|  |         const activeNoteContext = this.getActiveContext(); | ||||||
|  |         if (!activeNoteContext) { | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return linkService.calculateHash({ | ||||||
|  |             notePath: activeNoteContext.notePath, | ||||||
|  |             ntxId: activeNoteContext.ntxId, | ||||||
|  |             hoistedNoteId: activeNoteContext.hoistedNoteId, | ||||||
|  |             viewScope: activeNoteContext.viewScope | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** @returns {NoteContext[]} */ |     /** @returns {NoteContext[]} */ | ||||||
|     getNoteContexts() { |     getNoteContexts() { | ||||||
|         return this.noteContexts; |         return this.noteContexts; | ||||||
| @@ -212,14 +230,18 @@ export default class TabManager extends Component { | |||||||
|         return activeNote ? activeNote.mime : null; |         return activeNote ? activeNote.mime : null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async switchToNoteContext(ntxId, notePath) { |     async switchToNoteContext(ntxId, notePath, viewScope = {}, hoistedNoteId = null) { | ||||||
|         const noteContext = this.noteContexts.find(nc => nc.ntxId === ntxId) |         const noteContext = this.noteContexts.find(nc => nc.ntxId === ntxId) | ||||||
|             || await this.openEmptyTab(); |             || await this.openEmptyTab(); | ||||||
|  |  | ||||||
|         await this.activateNoteContext(noteContext.ntxId); |         await this.activateNoteContext(noteContext.ntxId); | ||||||
|  |  | ||||||
|  |         if (hoistedNoteId) { | ||||||
|  |             await noteContext.setHoistedNoteId(hoistedNoteId); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (notePath) { |         if (notePath) { | ||||||
|             await noteContext.setNote(notePath); |             await noteContext.setNote(notePath, { viewScope }); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -347,7 +369,7 @@ export default class TabManager extends Component { | |||||||
|  |  | ||||||
|         this.tabsUpdate.scheduleUpdate(); |         this.tabsUpdate.scheduleUpdate(); | ||||||
|  |  | ||||||
|         this.setCurrentNotePathToHash(); |         this.setCurrentNavigationStateToHash(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -564,21 +586,21 @@ export default class TabManager extends Component { | |||||||
|         this.tabsUpdate.scheduleUpdate(); |         this.tabsUpdate.scheduleUpdate(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     updateDocumentTitle(activeNoteContext) { |     async updateDocumentTitle(activeNoteContext) { | ||||||
|         const titleFragments = [ |         const titleFragments = [ | ||||||
|             // it helps to navigate in history if note title is included in the title |             // it helps to navigate in history if note title is included in the title | ||||||
|             activeNoteContext.note?.title, |             await activeNoteContext.getNavigationTitle(), | ||||||
|             "Trilium Notes" |             "Trilium Notes" | ||||||
|         ].filter(Boolean); |         ].filter(Boolean); | ||||||
|  |  | ||||||
|         document.title = titleFragments.join(" - "); |         document.title = titleFragments.join(" - "); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     entitiesReloadedEvent({loadResults}) { |     async entitiesReloadedEvent({loadResults}) { | ||||||
|         const activeContext = this.getActiveContext(); |         const activeContext = this.getActiveContext(); | ||||||
|  |  | ||||||
|         if (activeContext && loadResults.isNoteReloaded(activeContext.noteId)) { |         if (activeContext && loadResults.isNoteReloaded(activeContext.noteId)) { | ||||||
|             this.updateDocumentTitle(activeContext); |             await this.updateDocumentTitle(activeContext); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -99,6 +99,37 @@ function parseNotePathAndScope($link) { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function calculateHash({notePath, ntxId, hoistedNoteId, viewScope = {}}) { | ||||||
|  |     notePath = notePath || ""; | ||||||
|  |     const params = [ | ||||||
|  |         ntxId ? { ntxId: ntxId } : null, | ||||||
|  |         (hoistedNoteId && hoistedNoteId !== 'root') ? { hoistedNoteId: hoistedNoteId } : null, | ||||||
|  |         viewScope.viewMode !== 'default' ? { viewMode: viewScope.viewMode } : null, | ||||||
|  |         viewScope.attachmentId ? { attachmentId: viewScope.attachmentId } : null | ||||||
|  |     ].filter(p => !!p); | ||||||
|  |  | ||||||
|  |     const paramStr = params.map(pair => { | ||||||
|  |         const name = Object.keys(pair)[0]; | ||||||
|  |         const value = pair[name]; | ||||||
|  |  | ||||||
|  |         return `${encodeURIComponent(name)}=${encodeURIComponent(value)}`; | ||||||
|  |     }).join("&"); | ||||||
|  |  | ||||||
|  |     if (!notePath && !paramStr) { | ||||||
|  |         return ""; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let hash = `#${notePath}`; | ||||||
|  |  | ||||||
|  |     if (paramStr) { | ||||||
|  |         hash += `?${paramStr}`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     console.log(hash); | ||||||
|  |  | ||||||
|  |     return hash; | ||||||
|  | } | ||||||
|  |  | ||||||
| function goToLink(evt) { | function goToLink(evt) { | ||||||
|     const $link = $(evt.target).closest("a,.block-link"); |     const $link = $(evt.target).closest("a,.block-link"); | ||||||
|     const hrefLink = $link.attr('href'); |     const hrefLink = $link.attr('href'); | ||||||
| @@ -223,5 +254,6 @@ export default { | |||||||
|     createNoteLink, |     createNoteLink, | ||||||
|     goToLink, |     goToLink, | ||||||
|     loadReferenceLinkTitle, |     loadReferenceLinkTitle, | ||||||
|     parseNotePathAndScope |     parseNotePathAndScope, | ||||||
|  |     calculateHash | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -23,8 +23,8 @@ async function resolveNotePath(notePath, hoistedNoteId = 'root') { | |||||||
| async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logErrors = true) { | async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logErrors = true) { | ||||||
|     utils.assertArguments(notePath); |     utils.assertArguments(notePath); | ||||||
|  |  | ||||||
|     // we might get notePath with the ntxId suffix, remove it if present |     // we might get notePath with the params suffix, remove it if present | ||||||
|     notePath = notePath.split("-")[0].trim(); |     notePath = notePath.split("?")[0].trim(); | ||||||
|  |  | ||||||
|     if (notePath.length === 0) { |     if (notePath.length === 0) { | ||||||
|         return; |         return; | ||||||
| @@ -159,8 +159,8 @@ function getNoteIdFromNotePath(notePath) { | |||||||
|  |  | ||||||
|     const lastSegment = path[path.length - 1]; |     const lastSegment = path[path.length - 1]; | ||||||
|  |  | ||||||
|     // path could have also ntxId suffix |     // path could have also params suffix | ||||||
|     return lastSegment.split("-")[0]; |     return lastSegment.split("?")[0]; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getBranchIdFromNotePath(notePath) { | async function getBranchIdFromNotePath(notePath) { | ||||||
| @@ -185,8 +185,8 @@ function getNoteIdAndParentIdFromNotePath(notePath) { | |||||||
|  |  | ||||||
|         const lastSegment = path[path.length - 1]; |         const lastSegment = path[path.length - 1]; | ||||||
|  |  | ||||||
|         // path could have also ntxId suffix |         // path could have also params suffix | ||||||
|         noteId = lastSegment.split("-")[0]; |         noteId = lastSegment.split("?")[0]; | ||||||
|  |  | ||||||
|         if (path.length > 1) { |         if (path.length > 1) { | ||||||
|             parentNoteId = path[path.length - 2]; |             parentNoteId = path[path.length - 2]; | ||||||
| @@ -297,14 +297,44 @@ async function getNoteTitleWithPathAsSuffix(notePath) { | |||||||
|     return $titleWithPath; |     return $titleWithPath; | ||||||
| } | } | ||||||
|  |  | ||||||
| function getHashValueFromAddress() { | function parseNavigationStateFromAddress() { | ||||||
|     const str = document.location.hash ? document.location.hash.substr(1) : ""; // strip initial # |     const str = document.location.hash?.substr(1) || ""; // strip initial # | ||||||
|  |  | ||||||
|     return str.split("-"); |     const [notePath, paramString] = str.split("?"); | ||||||
|  |     const viewScope = { | ||||||
|  |         viewMode: 'default' | ||||||
|  |     }; | ||||||
|  |     let ntxId = null; | ||||||
|  |     let hoistedNoteId = null; | ||||||
|  |  | ||||||
|  |     if (paramString) { | ||||||
|  |         for (const pair of paramString.split("&")) { | ||||||
|  |             let [name, value] = pair.split("="); | ||||||
|  |             name = decodeURIComponent(name); | ||||||
|  |             value = decodeURIComponent(value); | ||||||
|  |  | ||||||
|  |             if (name === 'ntxId') { | ||||||
|  |                 ntxId = value; | ||||||
|  |             } else if (name === 'hoistedNoteId') { | ||||||
|  |                 hoistedNoteId = value; | ||||||
|  |             } else if (['viewMode', 'attachmentId'].includes(name)) { | ||||||
|  |                 viewScope[name] = value; | ||||||
|  |             } else { | ||||||
|  |                 console.warn(`Unrecognized hash parameter '${name}'.`); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         notePath, | ||||||
|  |         ntxId, | ||||||
|  |         hoistedNoteId, | ||||||
|  |         viewScope | ||||||
|  |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| function isNotePathInAddress() { | function isNotePathInAddress() { | ||||||
|     const [notePath, ntxId] = getHashValueFromAddress(); |     const {notePath, ntxId} = parseNavigationStateFromAddress(); | ||||||
|  |  | ||||||
|     return notePath.startsWith("root") |     return notePath.startsWith("root") | ||||||
|         // empty string is for empty/uninitialized tab |         // empty string is for empty/uninitialized tab | ||||||
| @@ -338,7 +368,7 @@ export default { | |||||||
|     getNoteTitle, |     getNoteTitle, | ||||||
|     getNotePathTitle, |     getNotePathTitle, | ||||||
|     getNoteTitleWithPathAsSuffix, |     getNoteTitleWithPathAsSuffix, | ||||||
|     getHashValueFromAddress, |     parseNavigationStateFromAddress, | ||||||
|     isNotePathInAddress, |     isNotePathInAddress, | ||||||
|     parseNotePath, |     parseNotePath, | ||||||
|     isNotePathInHiddenSubtree |     isNotePathInHiddenSubtree | ||||||
|   | |||||||
| @@ -55,6 +55,7 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget { | |||||||
|         for (const idx in this.webContents.history) { |         for (const idx in this.webContents.history) { | ||||||
|             const url = this.webContents.history[idx]; |             const url = this.webContents.history[idx]; | ||||||
|             const [_, notePathWithTab] = url.split('#'); |             const [_, notePathWithTab] = url.split('#'); | ||||||
|  |             // broken: use treeService.parseNavigationStateFromAddress(); | ||||||
|             const [notePath, ntxId] = notePathWithTab.split('-'); |             const [notePath, ntxId] = notePathWithTab.split('-'); | ||||||
|  |  | ||||||
|             const title = await treeService.getNotePathTitle(notePath); |             const title = await treeService.getNotePathTitle(notePath); | ||||||
|   | |||||||
| @@ -70,37 +70,20 @@ export default class NoteTitleWidget extends NoteContextAwareWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async refreshWithNote(note) { |     async refreshWithNote(note) { | ||||||
|         this.$noteTitle.val(await this.getTitleText(note)); |         const isReadOnly = (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) | ||||||
|  |  | ||||||
|         this.$noteTitle.prop("readonly", |  | ||||||
|             (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) |  | ||||||
|             || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId) |             || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId) | ||||||
|             || this.noteContext.viewScope.viewMode !== 'default' |             || this.noteContext.viewScope.viewMode !== 'default'; | ||||||
|  |  | ||||||
|  |         this.$noteTitle.val( | ||||||
|  |             isReadOnly | ||||||
|  |                 ? await this.noteContext.getNavigationTitle() | ||||||
|  |                 : note.title | ||||||
|         ); |         ); | ||||||
|  |         this.$noteTitle.prop("readonly", isReadOnly); | ||||||
|  |  | ||||||
|         this.setProtectedStatus(note); |         this.setProtectedStatus(note); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @param {FNote} note */ |  | ||||||
|     async getTitleText(note) { |  | ||||||
|         const viewScope = this.noteContext.viewScope; |  | ||||||
|  |  | ||||||
|         let title = viewScope.viewMode === 'default' |  | ||||||
|             ? note.title |  | ||||||
|             : `${note.title}: ${viewScope.viewMode}`; |  | ||||||
|  |  | ||||||
|         if (viewScope.attachmentId) { |  | ||||||
|             // assuming the attachment has been already loaded |  | ||||||
|             const attachment = await note.getAttachmentById(viewScope.attachmentId); |  | ||||||
|  |  | ||||||
|             if (attachment) { |  | ||||||
|                 title += `: ${attachment.title}`; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return title; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** @param {FNote} note */ |     /** @param {FNote} note */ | ||||||
|     setProtectedStatus(note) { |     setProtectedStatus(note) { | ||||||
|         this.$noteTitle.toggleClass("protected", !!note.isProtected); |         this.$noteTitle.toggleClass("protected", !!note.isProtected); | ||||||
|   | |||||||
| @@ -618,7 +618,7 @@ export default class TabRowWidget extends BasicWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @param {NoteContext} noteContext */ |     /** @param {NoteContext} noteContext */ | ||||||
|     updateTab($tab, noteContext) { |     async updateTab($tab, noteContext) { | ||||||
|         if (!$tab.length) { |         if (!$tab.length) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -652,11 +652,7 @@ export default class TabRowWidget extends BasicWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const viewMode = noteContext.viewScope?.viewMode; |         const title = await noteContext.getNavigationTitle(); | ||||||
|         const title = (viewMode && viewMode !== 'default') |  | ||||||
|             ? `${viewMode}: ${note.title}` |  | ||||||
|             : note.title; |  | ||||||
|  |  | ||||||
|         this.updateTitle($tab, title); |         this.updateTitle($tab, title); | ||||||
|  |  | ||||||
|         $tab.addClass(note.getCssClass()); |         $tab.addClass(note.getCssClass()); | ||||||
|   | |||||||
| @@ -35,9 +35,6 @@ function index(req, res) { | |||||||
|         appCssNoteIds: getAppCssNoteIds(), |         appCssNoteIds: getAppCssNoteIds(), | ||||||
|         isDev: env.isDev(), |         isDev: env.isDev(), | ||||||
|         isMainWindow: !req.query.extraWindow, |         isMainWindow: !req.query.extraWindow, | ||||||
|         extraHoistedNoteId: req.query.extraHoistedNoteId, |  | ||||||
|         // make sure only valid JSON gets rendered |  | ||||||
|         extraViewScope: JSON.stringify(req.query.extraViewScope ? JSON.parse(req.query.extraViewScope) : {}), |  | ||||||
|         isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), |         isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), | ||||||
|         maxContentWidth: parseInt(options.maxContentWidth), |         maxContentWidth: parseInt(options.maxContentWidth), | ||||||
|         triliumVersion: packageJson.version, |         triliumVersion: packageJson.version, | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ let mainWindow; | |||||||
| /** @type {Electron.BrowserWindow} */ | /** @type {Electron.BrowserWindow} */ | ||||||
| let setupWindow; | let setupWindow; | ||||||
|  |  | ||||||
| async function createExtraWindow(notePath, hoistedNoteId = 'root', viewScope = {}) { | async function createExtraWindow(extraWindowHash) { | ||||||
|     const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled'); |     const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled'); | ||||||
|  |  | ||||||
|     const {BrowserWindow} = require('electron'); |     const {BrowserWindow} = require('electron'); | ||||||
| @@ -35,13 +35,13 @@ async function createExtraWindow(notePath, hoistedNoteId = 'root', viewScope = { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     win.setMenuBarVisibility(false); |     win.setMenuBarVisibility(false); | ||||||
|     win.loadURL(`http://127.0.0.1:${port}/?extraWindow=1&extraHoistedNoteId=${hoistedNoteId}&extraViewScope=${JSON.stringify(viewScope)}#${notePath}`); |     win.loadURL(`http://127.0.0.1:${port}/?extraWindow=1${extraWindowHash}`); | ||||||
|  |  | ||||||
|     configureWebContents(win.webContents, spellcheckEnabled); |     configureWebContents(win.webContents, spellcheckEnabled); | ||||||
| } | } | ||||||
|  |  | ||||||
| ipcMain.on('create-extra-window', (event, arg) => { | ipcMain.on('create-extra-window', (event, arg) => { | ||||||
|     createExtraWindow(arg.notePath, arg.hoistedNoteId, arg.viewScope); |     createExtraWindow(arg.extraWindowHash); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| async function createMainWindow(app) { | async function createMainWindow(app) { | ||||||
|   | |||||||
| @@ -32,8 +32,6 @@ | |||||||
|         isDev: <%= isDev %>, |         isDev: <%= isDev %>, | ||||||
|         appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>, |         appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>, | ||||||
|         isMainWindow: <%= isMainWindow %>, |         isMainWindow: <%= isMainWindow %>, | ||||||
|         extraHoistedNoteId: '<%= extraHoistedNoteId %>', |  | ||||||
|         extraViewScope: <%- extraViewScope %>, |  | ||||||
|         isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>, |         isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>, | ||||||
|         triliumVersion: "<%= triliumVersion %>", |         triliumVersion: "<%= triliumVersion %>", | ||||||
|         assetPath: "<%= assetPath %>", |         assetPath: "<%= assetPath %>", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user