mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Merge pull request #1320 from TriliumNext/porting_js
chore: port more js to ts
This commit is contained in:
		| @@ -22,7 +22,6 @@ import type LoadResults from "../services/load_results.js"; | |||||||
| import type { Attribute } from "../services/attribute_parser.js"; | import type { Attribute } from "../services/attribute_parser.js"; | ||||||
| import type NoteTreeWidget from "../widgets/note_tree.js"; | import type NoteTreeWidget from "../widgets/note_tree.js"; | ||||||
| import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; | import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; | ||||||
| import type { ContextMenuEvent } from "../menus/context_menu.js"; |  | ||||||
| import type TypeWidget from "../widgets/type_widgets/type_widget.js"; | import type TypeWidget from "../widgets/type_widgets/type_widget.js"; | ||||||
|  |  | ||||||
| interface Layout { | interface Layout { | ||||||
| @@ -56,8 +55,8 @@ export interface ContextMenuCommandData extends CommandData { | |||||||
| } | } | ||||||
|  |  | ||||||
| export interface NoteCommandData extends CommandData { | export interface NoteCommandData extends CommandData { | ||||||
|     notePath?: string; |     notePath?: string | null; | ||||||
|     hoistedNoteId?: string; |     hoistedNoteId?: string | null; | ||||||
|     viewScope?: ViewScope; |     viewScope?: ViewScope; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -326,7 +325,7 @@ type EventMappings = { | |||||||
|         ntxId: string | null; |         ntxId: string | null; | ||||||
|     }; |     }; | ||||||
|     contextsReopenedEvent: { |     contextsReopenedEvent: { | ||||||
|         mainNtxId: string; |         mainNtxId: string | null; | ||||||
|         tabPosition: number; |         tabPosition: number; | ||||||
|     }; |     }; | ||||||
|     noteDetailRefreshed: { |     noteDetailRefreshed: { | ||||||
| @@ -340,7 +339,7 @@ type EventMappings = { | |||||||
|     newNoteContextCreated: { |     newNoteContextCreated: { | ||||||
|         noteContext: NoteContext; |         noteContext: NoteContext; | ||||||
|     }; |     }; | ||||||
|     noteContextRemovedEvent: { |     noteContextRemoved: { | ||||||
|         ntxIds: string[]; |         ntxIds: string[]; | ||||||
|     }; |     }; | ||||||
|     exportSvg: { |     exportSvg: { | ||||||
| @@ -361,6 +360,7 @@ type EventMappings = { | |||||||
|     relationMapResetPanZoom: { ntxId: string | null | undefined }; |     relationMapResetPanZoom: { ntxId: string | null | undefined }; | ||||||
|     relationMapResetZoomIn: { ntxId: string | null | undefined }; |     relationMapResetZoomIn: { ntxId: string | null | undefined }; | ||||||
|     relationMapResetZoomOut: { ntxId: string | null | undefined }; |     relationMapResetZoomOut: { ntxId: string | null | undefined }; | ||||||
|  |     activeNoteChangedEvent: {}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export type EventListener<T extends EventNames> = { | export type EventListener<T extends EventNames> = { | ||||||
|   | |||||||
| @@ -66,12 +66,13 @@ export default class Entrypoints extends Component { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async toggleNoteHoistingCommand({ noteId = appContext.tabManager.getActiveContextNoteId() }) { |     async toggleNoteHoistingCommand({ noteId = appContext.tabManager.getActiveContextNoteId() }) { | ||||||
|         if (!noteId) { |         const activeNoteContext = appContext.tabManager.getActiveContext(); | ||||||
|  |  | ||||||
|  |         if (!activeNoteContext || !noteId) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const noteToHoist = await froca.getNote(noteId); |         const noteToHoist = await froca.getNote(noteId); | ||||||
|         const activeNoteContext = appContext.tabManager.getActiveContext(); |  | ||||||
|  |  | ||||||
|         if (noteToHoist?.noteId === activeNoteContext.hoistedNoteId) { |         if (noteToHoist?.noteId === activeNoteContext.hoistedNoteId) { | ||||||
|             await activeNoteContext.unhoist(); |             await activeNoteContext.unhoist(); | ||||||
| @@ -83,6 +84,11 @@ export default class Entrypoints extends Component { | |||||||
|     async hoistNoteCommand({ noteId }: { noteId: string }) { |     async hoistNoteCommand({ noteId }: { noteId: string }) { | ||||||
|         const noteContext = appContext.tabManager.getActiveContext(); |         const noteContext = appContext.tabManager.getActiveContext(); | ||||||
|  |  | ||||||
|  |         if (!noteContext) { | ||||||
|  |             logError("hoistNoteCommand: noteContext is null"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (noteContext.hoistedNoteId !== noteId) { |         if (noteContext.hoistedNoteId !== noteId) { | ||||||
|             await noteContext.setHoistedNoteId(noteId); |             await noteContext.setHoistedNoteId(noteId); | ||||||
|         } |         } | ||||||
| @@ -174,7 +180,11 @@ export default class Entrypoints extends Component { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async runActiveNoteCommand() { |     async runActiveNoteCommand() { | ||||||
|         const { ntxId, note } = appContext.tabManager.getActiveContext(); |         const noteContext = appContext.tabManager.getActiveContext(); | ||||||
|  |         if (!noteContext) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         const { ntxId, note } = noteContext; | ||||||
|  |  | ||||||
|         // ctrl+enter is also used elsewhere, so make sure we're running only when appropriate |         // ctrl+enter is also used elsewhere, so make sure we're running only when appropriate | ||||||
|         if (!note || note.type !== "code") { |         if (!note || note.type !== "code") { | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ export default class ShortcutComponent extends Component implements EventListene | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     bindNoteShortcutHandler(labelOrRow: AttributeRow) { |     bindNoteShortcutHandler(labelOrRow: AttributeRow) { | ||||||
|         const handler = () => appContext.tabManager.getActiveContext().setNote(labelOrRow.noteId); |         const handler = () => appContext.tabManager.getActiveContext()?.setNote(labelOrRow.noteId); | ||||||
|         const namespace = labelOrRow.attributeId; |         const namespace = labelOrRow.attributeId; | ||||||
|  |  | ||||||
|         if (labelOrRow.isDeleted) { |         if (labelOrRow.isDeleted) { | ||||||
|   | |||||||
| @@ -4,23 +4,40 @@ import server from "../services/server.js"; | |||||||
| import options from "../services/options.js"; | import options from "../services/options.js"; | ||||||
| import froca from "../services/froca.js"; | import froca from "../services/froca.js"; | ||||||
| import treeService from "../services/tree.js"; | import treeService from "../services/tree.js"; | ||||||
| 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"; | import linkService from "../services/link.js"; | ||||||
|  | import type { EventData } from "./app_context.js"; | ||||||
|  | import type FNote from "../entities/fnote.js"; | ||||||
|  | 
 | ||||||
|  | interface TabState { | ||||||
|  |     contexts: NoteContext[]; | ||||||
|  |     position: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface NoteContextState { | ||||||
|  |     ntxId: string; | ||||||
|  |     mainNtxId: string | null; | ||||||
|  |     notePath: string | null; | ||||||
|  |     hoistedNoteId: string; | ||||||
|  |     active: boolean; | ||||||
|  |     viewScope: Record<string, any>; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| export default class TabManager extends Component { | export default class TabManager extends Component { | ||||||
|  |     public children: NoteContext[]; | ||||||
|  |     public mutex: Mutex; | ||||||
|  |     public activeNtxId: string | null; | ||||||
|  |     public recentlyClosedTabs: TabState[]; | ||||||
|  |     public tabsUpdate: SpacedUpdate; | ||||||
|  | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         /** @property {NoteContext[]} */ |  | ||||||
|         this.children = []; |         this.children = []; | ||||||
|         this.mutex = new Mutex(); |         this.mutex = new Mutex(); | ||||||
| 
 |  | ||||||
|         this.activeNtxId = null; |         this.activeNtxId = null; | ||||||
| 
 |  | ||||||
|         // elements are arrays of {contexts, position}, storing note contexts for each tab (one main context + subcontexts [splits]), and the original position of the tab
 |  | ||||||
|         this.recentlyClosedTabs = []; |         this.recentlyClosedTabs = []; | ||||||
| 
 | 
 | ||||||
|         this.tabsUpdate = new SpacedUpdate(async () => { |         this.tabsUpdate = new SpacedUpdate(async () => { | ||||||
| @@ -28,7 +45,9 @@ export default class TabManager extends Component { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const openNoteContexts = this.noteContexts.map((nc) => nc.getPojoState()).filter((t) => !!t); |             const openNoteContexts = this.noteContexts | ||||||
|  |                 .map((nc) => nc.getPojoState()) | ||||||
|  |                 .filter((t) => !!t); | ||||||
| 
 | 
 | ||||||
|             await server.put("options", { |             await server.put("options", { | ||||||
|                 openNoteContexts: JSON.stringify(openNoteContexts) |                 openNoteContexts: JSON.stringify(openNoteContexts) | ||||||
| @@ -38,13 +57,11 @@ export default class TabManager extends Component { | |||||||
|         appContext.addBeforeUnloadListener(this); |         appContext.addBeforeUnloadListener(this); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {NoteContext[]} */ |     get noteContexts(): NoteContext[] { | ||||||
|     get noteContexts() { |  | ||||||
|         return this.children; |         return this.children; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @type {NoteContext[]} */ |     get mainNoteContexts(): NoteContext[] { | ||||||
|     get mainNoteContexts() { |  | ||||||
|         return this.noteContexts.filter((nc) => !nc.mainNtxId); |         return this.noteContexts.filter((nc) => !nc.mainNtxId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -53,11 +70,12 @@ export default class TabManager extends Component { | |||||||
|             const noteContextsToOpen = (appContext.isMainWindow && options.getJson("openNoteContexts")) || []; |             const noteContextsToOpen = (appContext.isMainWindow && options.getJson("openNoteContexts")) || []; | ||||||
| 
 | 
 | ||||||
|             // preload all notes at once
 |             // preload all notes at once
 | ||||||
|             await froca.getNotes([...noteContextsToOpen.flatMap((tab) => [treeService.getNoteIdFromUrl(tab.notePath), tab.hoistedNoteId])], true); |             await froca.getNotes([...noteContextsToOpen.flatMap((tab: NoteContextState) => | ||||||
|  |                 [treeService.getNoteIdFromUrl(tab.notePath), tab.hoistedNoteId])], true); | ||||||
| 
 | 
 | ||||||
|             const filteredNoteContexts = noteContextsToOpen.filter((openTab) => { |             const filteredNoteContexts = noteContextsToOpen.filter((openTab: NoteContextState) => { | ||||||
|                 const noteId = treeService.getNoteIdFromUrl(openTab.notePath); |                 const noteId = treeService.getNoteIdFromUrl(openTab.notePath); | ||||||
|                 if (!(noteId in froca.notes)) { |                 if (noteId && !(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
 | ||||||
|                     return false; |                     return false; | ||||||
|                 } |                 } | ||||||
| @@ -80,9 +98,10 @@ export default class TabManager extends Component { | |||||||
|                     ntxId: parsedFromUrl.ntxId, |                     ntxId: parsedFromUrl.ntxId, | ||||||
|                     active: true, |                     active: true, | ||||||
|                     hoistedNoteId: parsedFromUrl.hoistedNoteId || "root", |                     hoistedNoteId: parsedFromUrl.hoistedNoteId || "root", | ||||||
|                     viewScope: parsedFromUrl.viewScope || {} |                     viewScope: parsedFromUrl.viewScope || {}, | ||||||
|  |                     mainNtxId: null | ||||||
|                 }); |                 }); | ||||||
|             } else if (!filteredNoteContexts.find((tab) => tab.active)) { |             } else if (!filteredNoteContexts.find((tab: NoteContextState) => tab.active)) { | ||||||
|                 filteredNoteContexts[0].active = true; |                 filteredNoteContexts[0].active = true; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @@ -101,21 +120,30 @@ export default class TabManager extends Component { | |||||||
|             // if there's a notePath in the URL, make sure it's open and active
 |             // if there's a 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 (parsedFromUrl.notePath) { |             if (parsedFromUrl.notePath) { | ||||||
|                 await appContext.tabManager.switchToNoteContext(parsedFromUrl.ntxId, parsedFromUrl.notePath, parsedFromUrl.viewScope, parsedFromUrl.hoistedNoteId); |                 await appContext.tabManager.switchToNoteContext( | ||||||
|  |                     parsedFromUrl.ntxId, | ||||||
|  |                     parsedFromUrl.notePath, | ||||||
|  |                     parsedFromUrl.viewScope, | ||||||
|  |                     parsedFromUrl.hoistedNoteId | ||||||
|  |                 ); | ||||||
|             } else if (parsedFromUrl.searchString) { |             } else if (parsedFromUrl.searchString) { | ||||||
|                 await appContext.triggerCommand("searchNotes", { |                 await appContext.triggerCommand("searchNotes", { | ||||||
|                     searchString: parsedFromUrl.searchString |                     searchString: parsedFromUrl.searchString | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|         } catch (e) { |         } catch (e: unknown) { | ||||||
|  |             if (e instanceof Error) { | ||||||
|                 logError(`Loading note contexts '${options.get("openNoteContexts")}' failed: ${e.message} ${e.stack}`); |                 logError(`Loading note contexts '${options.get("openNoteContexts")}' failed: ${e.message} ${e.stack}`); | ||||||
|  |             } else { | ||||||
|  |                 logError(`Loading note contexts '${options.get("openNoteContexts")}' failed: ${String(e)}`); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             // try to recover
 |             // try to recover
 | ||||||
|             await this.openEmptyTab(); |             await this.openEmptyTab(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     noteSwitchedEvent({ noteContext }) { |     noteSwitchedEvent({ noteContext }: EventData<"noteSwitched">) { | ||||||
|         if (noteContext.isActive()) { |         if (noteContext.isActive()) { | ||||||
|             this.setCurrentNavigationStateToHash(); |             this.setCurrentNavigationStateToHash(); | ||||||
|         } |         } | ||||||
| @@ -135,10 +163,10 @@ export default class TabManager extends Component { | |||||||
|         const activeNoteContext = this.getActiveContext(); |         const activeNoteContext = this.getActiveContext(); | ||||||
|         this.updateDocumentTitle(activeNoteContext); |         this.updateDocumentTitle(activeNoteContext); | ||||||
| 
 | 
 | ||||||
|         this.triggerEvent("activeNoteChanged"); // trigger this even in on popstate event
 |         this.triggerEvent("activeNoteChangedEvent", {}); // trigger this even in on popstate event
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     calculateHash() { |     calculateHash(): string { | ||||||
|         const activeNoteContext = this.getActiveContext(); |         const activeNoteContext = this.getActiveContext(); | ||||||
|         if (!activeNoteContext) { |         if (!activeNoteContext) { | ||||||
|             return ""; |             return ""; | ||||||
| @@ -152,21 +180,15 @@ export default class TabManager extends Component { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {NoteContext[]} */ |     getNoteContexts(): NoteContext[] { | ||||||
|     getNoteContexts() { |  | ||||||
|         return this.noteContexts; |         return this.noteContexts; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     getMainNoteContexts(): NoteContext[] { | ||||||
|      * Main context is essentially a tab (children are splits), so this returns tabs. |  | ||||||
|      * @returns {NoteContext[]} |  | ||||||
|      */ |  | ||||||
|     getMainNoteContexts() { |  | ||||||
|         return this.noteContexts.filter((nc) => nc.isMainContext()); |         return this.noteContexts.filter((nc) => nc.isMainContext()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {NoteContext} */ |     getNoteContextById(ntxId: string | null): NoteContext { | ||||||
|     getNoteContextById(ntxId) { |  | ||||||
|         const noteContext = this.noteContexts.find((nc) => nc.ntxId === ntxId); |         const noteContext = this.noteContexts.find((nc) => nc.ntxId === ntxId); | ||||||
| 
 | 
 | ||||||
|         if (!noteContext) { |         if (!noteContext) { | ||||||
| @@ -176,58 +198,47 @@ export default class TabManager extends Component { | |||||||
|         return noteContext; |         return noteContext; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     getActiveContext(): NoteContext | null { | ||||||
|      * Get active context which represents the visible split with focus. Active context can, but doesn't have to be "main". |  | ||||||
|      * |  | ||||||
|      * @returns {NoteContext} |  | ||||||
|      */ |  | ||||||
|     getActiveContext() { |  | ||||||
|         return this.activeNtxId ? this.getNoteContextById(this.activeNtxId) : null; |         return this.activeNtxId ? this.getNoteContextById(this.activeNtxId) : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     getActiveMainContext(): NoteContext | null { | ||||||
|      * Get active main context which corresponds to the active tab. |  | ||||||
|      * |  | ||||||
|      * @returns {NoteContext} |  | ||||||
|      */ |  | ||||||
|     getActiveMainContext() { |  | ||||||
|         return this.activeNtxId ? this.getNoteContextById(this.activeNtxId).getMainContext() : null; |         return this.activeNtxId ? this.getNoteContextById(this.activeNtxId).getMainContext() : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {string|null} */ |     getActiveContextNotePath(): string | null { | ||||||
|     getActiveContextNotePath() { |  | ||||||
|         const activeContext = this.getActiveContext(); |         const activeContext = this.getActiveContext(); | ||||||
|         return activeContext ? activeContext.notePath : null; |         return activeContext?.notePath ?? null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {FNote} */ |     getActiveContextNote(): FNote | null { | ||||||
|     getActiveContextNote() { |  | ||||||
|         const activeContext = this.getActiveContext(); |         const activeContext = this.getActiveContext(); | ||||||
|         return activeContext ? activeContext.note : null; |         return activeContext ? activeContext.note : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {string|null} */ |     getActiveContextNoteId(): string | null { | ||||||
|     getActiveContextNoteId() { |  | ||||||
|         const activeNote = this.getActiveContextNote(); |         const activeNote = this.getActiveContextNote(); | ||||||
| 
 |  | ||||||
|         return activeNote ? activeNote.noteId : null; |         return activeNote ? activeNote.noteId : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {string|null} */ |     getActiveContextNoteType(): string | null { | ||||||
|     getActiveContextNoteType() { |  | ||||||
|         const activeNote = this.getActiveContextNote(); |         const activeNote = this.getActiveContextNote(); | ||||||
| 
 |  | ||||||
|         return activeNote ? activeNote.type : null; |         return activeNote ? activeNote.type : null; | ||||||
|     } |     } | ||||||
|     /** @returns {string|null} */ |  | ||||||
|     getActiveContextNoteMime() { |  | ||||||
|         const activeNote = this.getActiveContextNote(); |  | ||||||
| 
 | 
 | ||||||
|  |     getActiveContextNoteMime(): string | null { | ||||||
|  |         const activeNote = this.getActiveContextNote(); | ||||||
|         return activeNote ? activeNote.mime : null; |         return activeNote ? activeNote.mime : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async switchToNoteContext(ntxId, notePath, viewScope = {}, hoistedNoteId = null) { |     async switchToNoteContext( | ||||||
|         const noteContext = this.noteContexts.find((nc) => nc.ntxId === ntxId) || (await this.openEmptyTab()); |         ntxId: string | null, | ||||||
|  |         notePath: string, | ||||||
|  |         viewScope: Record<string, any> = {}, | ||||||
|  |         hoistedNoteId: string | null = null | ||||||
|  |     ) { | ||||||
|  |         const noteContext = this.noteContexts.find((nc) => nc.ntxId === ntxId) || | ||||||
|  |             await this.openEmptyTab(); | ||||||
| 
 | 
 | ||||||
|         await this.activateNoteContext(noteContext.ntxId); |         await this.activateNoteContext(noteContext.ntxId); | ||||||
| 
 | 
 | ||||||
| @@ -242,20 +253,21 @@ export default class TabManager extends Component { | |||||||
| 
 | 
 | ||||||
|     async openAndActivateEmptyTab() { |     async openAndActivateEmptyTab() { | ||||||
|         const noteContext = await this.openEmptyTab(); |         const noteContext = await this.openEmptyTab(); | ||||||
| 
 |  | ||||||
|         await this.activateNoteContext(noteContext.ntxId); |         await this.activateNoteContext(noteContext.ntxId); | ||||||
| 
 |         noteContext.setEmpty(); | ||||||
|         await noteContext.setEmpty(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async openEmptyTab(ntxId = null, hoistedNoteId = "root", mainNtxId = null) { |     async openEmptyTab( | ||||||
|  |         ntxId: string | null = null, | ||||||
|  |         hoistedNoteId: string = "root", | ||||||
|  |         mainNtxId: string | null = null | ||||||
|  |     ): Promise<NoteContext> { | ||||||
|         const noteContext = new NoteContext(ntxId, hoistedNoteId, mainNtxId); |         const noteContext = new NoteContext(ntxId, hoistedNoteId, mainNtxId); | ||||||
| 
 | 
 | ||||||
|         const existingNoteContext = this.children.find((nc) => nc.ntxId === noteContext.ntxId); |         const existingNoteContext = this.children.find((nc) => nc.ntxId === noteContext.ntxId); | ||||||
| 
 | 
 | ||||||
|         if (existingNoteContext) { |         if (existingNoteContext) { | ||||||
|             await existingNoteContext.setHoistedNoteId(hoistedNoteId); |             await existingNoteContext.setHoistedNoteId(hoistedNoteId); | ||||||
| 
 |  | ||||||
|             return existingNoteContext; |             return existingNoteContext; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -266,29 +278,40 @@ export default class TabManager extends Component { | |||||||
|         return noteContext; |         return noteContext; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async openInNewTab(targetNoteId, hoistedNoteId = null) { |     async openInNewTab(targetNoteId: string, hoistedNoteId: string | null = null) { | ||||||
|         const noteContext = await this.openEmptyTab(null, hoistedNoteId || this.getActiveContext().hoistedNoteId); |         const noteContext = await this.openEmptyTab( | ||||||
|  |             null, | ||||||
|  |             hoistedNoteId || this.getActiveContext()?.hoistedNoteId | ||||||
|  |         ); | ||||||
| 
 | 
 | ||||||
|         await noteContext.setNote(targetNoteId); |         await noteContext.setNote(targetNoteId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async openInSameTab(targetNoteId, hoistedNoteId = null) { |     async openInSameTab(targetNoteId: string, hoistedNoteId: string | null = null) { | ||||||
|         const activeContext = this.getActiveContext(); |         const activeContext = this.getActiveContext(); | ||||||
|  |         if (!activeContext) return; | ||||||
|  | 
 | ||||||
|         await activeContext.setHoistedNoteId(hoistedNoteId || activeContext.hoistedNoteId); |         await activeContext.setHoistedNoteId(hoistedNoteId || activeContext.hoistedNoteId); | ||||||
|         await activeContext.setNote(targetNoteId); |         await activeContext.setNote(targetNoteId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     async openTabWithNoteWithHoisting( | ||||||
|      * If the requested notePath is within current note hoisting scope then keep the note hoisting also for the new tab. |         notePath: string, | ||||||
|      */ |         opts: { | ||||||
|     async openTabWithNoteWithHoisting(notePath, opts = {}) { |             activate?: boolean | null; | ||||||
|  |             ntxId?: string | null; | ||||||
|  |             mainNtxId?: string | null; | ||||||
|  |             hoistedNoteId?: string | null; | ||||||
|  |             viewScope?: Record<string, any> | null; | ||||||
|  |         } = {} | ||||||
|  |     ): Promise<NoteContext> { | ||||||
|         const noteContext = this.getActiveContext(); |         const noteContext = this.getActiveContext(); | ||||||
|         let hoistedNoteId = "root"; |         let hoistedNoteId = "root"; | ||||||
| 
 | 
 | ||||||
|         if (noteContext) { |         if (noteContext) { | ||||||
|             const resolvedNotePath = await treeService.resolveNotePath(notePath, noteContext.hoistedNoteId); |             const resolvedNotePath = await treeService.resolveNotePath(notePath, noteContext.hoistedNoteId); | ||||||
| 
 | 
 | ||||||
|             if (resolvedNotePath.includes(noteContext.hoistedNoteId) || resolvedNotePath.includes("_hidden")) { |             if (resolvedNotePath?.includes(noteContext.hoistedNoteId) || resolvedNotePath?.includes("_hidden")) { | ||||||
|                 hoistedNoteId = noteContext.hoistedNoteId; |                 hoistedNoteId = noteContext.hoistedNoteId; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -298,7 +321,16 @@ export default class TabManager extends Component { | |||||||
|         return this.openContextWithNote(notePath, opts); |         return this.openContextWithNote(notePath, opts); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async openContextWithNote(notePath, opts = {}) { |     async openContextWithNote( | ||||||
|  |         notePath: string | null, | ||||||
|  |         opts: { | ||||||
|  |             activate?: boolean | null; | ||||||
|  |             ntxId?: string | null; | ||||||
|  |             mainNtxId?: string | null; | ||||||
|  |             hoistedNoteId?: string | null; | ||||||
|  |             viewScope?: Record<string, any> | null; | ||||||
|  |         } = {} | ||||||
|  |     ): Promise<NoteContext> { | ||||||
|         const activate = !!opts.activate; |         const activate = !!opts.activate; | ||||||
|         const ntxId = opts.ntxId || null; |         const ntxId = opts.ntxId || null; | ||||||
|         const mainNtxId = opts.mainNtxId || null; |         const mainNtxId = opts.mainNtxId || null; | ||||||
| @@ -315,10 +347,10 @@ export default class TabManager extends Component { | |||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (activate) { |         if (activate && noteContext.notePath) { | ||||||
|             this.activateNoteContext(noteContext.ntxId, false); |             this.activateNoteContext(noteContext.ntxId, false); | ||||||
| 
 | 
 | ||||||
|             await this.triggerEvent("noteSwitchedAndActivated", { |             await this.triggerEvent("noteSwitchedAndActivatedEvent", { | ||||||
|                 noteContext, |                 noteContext, | ||||||
|                 notePath: noteContext.notePath // resolved note path
 |                 notePath: noteContext.notePath // resolved note path
 | ||||||
|             }); |             }); | ||||||
| @@ -327,21 +359,24 @@ export default class TabManager extends Component { | |||||||
|         return noteContext; |         return noteContext; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async activateOrOpenNote(noteId) { |     async activateOrOpenNote(noteId: string) { | ||||||
|         for (const noteContext of this.getNoteContexts()) { |         for (const noteContext of this.getNoteContexts()) { | ||||||
|             if (noteContext.note && noteContext.note.noteId === noteId) { |             if (noteContext.note && noteContext.note.noteId === noteId) { | ||||||
|                 this.activateNoteContext(noteContext.ntxId); |                 this.activateNoteContext(noteContext.ntxId); | ||||||
| 
 |  | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // if no tab with this note has been found we'll create new tab
 |         // if no tab with this note has been found we'll create new tab
 | ||||||
| 
 |  | ||||||
|         await this.openContextWithNote(noteId, { activate: true }); |         await this.openContextWithNote(noteId, { activate: true }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async activateNoteContext(ntxId, triggerEvent = true) { |     async activateNoteContext(ntxId: string | null, triggerEvent: boolean = true) { | ||||||
|  |         if (!ntxId) { | ||||||
|  |             logError("activateNoteContext: ntxId is null"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if (ntxId === this.activeNtxId) { |         if (ntxId === this.activeNtxId) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -359,11 +394,7 @@ export default class TabManager extends Component { | |||||||
|         this.setCurrentNavigationStateToHash(); |         this.setCurrentNavigationStateToHash(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     async removeNoteContext(ntxId: string | null) { | ||||||
|      * @param ntxId |  | ||||||
|      * @returns {Promise<boolean>} true if note context has been removed, false otherwise |  | ||||||
|      */ |  | ||||||
|     async removeNoteContext(ntxId) { |  | ||||||
|         // removing note context is an async process which can take some time, if users presses CTRL-W quickly, two
 |         // removing note context is an async process which can take some time, if users presses CTRL-W quickly, two
 | ||||||
|         // close events could interleave which would then lead to attempting to activate already removed context.
 |         // close events could interleave which would then lead to attempting to activate already removed context.
 | ||||||
|         return await this.mutex.runExclusively(async () => { |         return await this.mutex.runExclusively(async () => { | ||||||
| @@ -373,7 +404,7 @@ export default class TabManager extends Component { | |||||||
|                 noteContextToRemove = this.getNoteContextById(ntxId); |                 noteContextToRemove = this.getNoteContextById(ntxId); | ||||||
|             } catch { |             } catch { | ||||||
|                 // note context not found
 |                 // note context not found
 | ||||||
|                 return false; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (noteContextToRemove.isMainContext()) { |             if (noteContextToRemove.isMainContext()) { | ||||||
| @@ -383,7 +414,7 @@ export default class TabManager extends Component { | |||||||
|                     if (noteContextToRemove.isEmpty()) { |                     if (noteContextToRemove.isEmpty()) { | ||||||
|                         // this is already the empty note context, no point in closing it and replacing with another
 |                         // this is already the empty note context, no point in closing it and replacing with another
 | ||||||
|                         // empty tab
 |                         // empty tab
 | ||||||
|                         return false; |                         return; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     await this.openEmptyTab(); |                     await this.openEmptyTab(); | ||||||
| @@ -399,7 +430,7 @@ export default class TabManager extends Component { | |||||||
|             const noteContextsToRemove = noteContextToRemove.getSubContexts(); |             const noteContextsToRemove = noteContextToRemove.getSubContexts(); | ||||||
|             const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId); |             const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId); | ||||||
| 
 | 
 | ||||||
|             await this.triggerEvent("beforeNoteContextRemove", { ntxIds: ntxIdsToRemove }); |             await this.triggerEvent("beforeNoteContextRemove", { ntxIds: ntxIdsToRemove.filter((id) => id !== null) }); | ||||||
| 
 | 
 | ||||||
|             if (!noteContextToRemove.isMainContext()) { |             if (!noteContextToRemove.isMainContext()) { | ||||||
|                 const siblings = noteContextToRemove.getMainContext().getSubContexts(); |                 const siblings = noteContextToRemove.getMainContext().getSubContexts(); | ||||||
| @@ -421,12 +452,10 @@ export default class TabManager extends Component { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             this.removeNoteContexts(noteContextsToRemove); |             this.removeNoteContexts(noteContextsToRemove); | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     removeNoteContexts(noteContextsToRemove) { |     removeNoteContexts(noteContextsToRemove: NoteContext[]) { | ||||||
|         const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId); |         const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId); | ||||||
| 
 | 
 | ||||||
|         const position = this.noteContexts.findIndex((nc) => ntxIdsToRemove.includes(nc.ntxId)); |         const position = this.noteContexts.findIndex((nc) => ntxIdsToRemove.includes(nc.ntxId)); | ||||||
| @@ -435,12 +464,12 @@ export default class TabManager extends Component { | |||||||
| 
 | 
 | ||||||
|         this.addToRecentlyClosedTabs(noteContextsToRemove, position); |         this.addToRecentlyClosedTabs(noteContextsToRemove, position); | ||||||
| 
 | 
 | ||||||
|         this.triggerEvent("noteContextRemoved", { ntxIds: ntxIdsToRemove }); |         this.triggerEvent("noteContextRemoved", { ntxIds: ntxIdsToRemove.filter((id) => id !== null) }); | ||||||
| 
 | 
 | ||||||
|         this.tabsUpdate.scheduleUpdate(); |         this.tabsUpdate.scheduleUpdate(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     addToRecentlyClosedTabs(noteContexts, position) { |     addToRecentlyClosedTabs(noteContexts: NoteContext[], position: number) { | ||||||
|         if (noteContexts.length === 1 && noteContexts[0].isEmpty()) { |         if (noteContexts.length === 1 && noteContexts[0].isEmpty()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -448,26 +477,42 @@ export default class TabManager extends Component { | |||||||
|         this.recentlyClosedTabs.push({ contexts: noteContexts, position: position }); |         this.recentlyClosedTabs.push({ contexts: noteContexts, position: position }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     tabReorderEvent({ ntxIdsInOrder }) { |     tabReorderEvent({ ntxIdsInOrder }: { ntxIdsInOrder: string[] }) { | ||||||
|         const order = {}; |         const order: Record<string, number> = {}; | ||||||
| 
 | 
 | ||||||
|         let i = 0; |         let i = 0; | ||||||
| 
 | 
 | ||||||
|         for (const ntxId of ntxIdsInOrder) { |         for (const ntxId of ntxIdsInOrder) { | ||||||
|             for (const noteContext of this.getNoteContextById(ntxId).getSubContexts()) { |             for (const noteContext of this.getNoteContextById(ntxId).getSubContexts()) { | ||||||
|  |                 if (noteContext.ntxId) { | ||||||
|                     order[noteContext.ntxId] = i++; |                     order[noteContext.ntxId] = i++; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         this.children.sort((a, b) => (order[a.ntxId] < order[b.ntxId] ? -1 : 1)); |         this.children.sort((a, b) => { | ||||||
|  |             if (!a.ntxId || !b.ntxId) return 0; | ||||||
|  |             return (order[a.ntxId] ?? 0) < (order[b.ntxId] ?? 0) ? -1 : 1; | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         this.tabsUpdate.scheduleUpdate(); |         this.tabsUpdate.scheduleUpdate(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     noteContextReorderEvent({ ntxIdsInOrder, oldMainNtxId, newMainNtxId }) { |     noteContextReorderEvent({ | ||||||
|  |         ntxIdsInOrder, | ||||||
|  |         oldMainNtxId, | ||||||
|  |         newMainNtxId | ||||||
|  |     }: { | ||||||
|  |         ntxIdsInOrder: string[]; | ||||||
|  |         oldMainNtxId?: string; | ||||||
|  |         newMainNtxId?: string; | ||||||
|  |     }) { | ||||||
|         const order = Object.fromEntries(ntxIdsInOrder.map((v, i) => [v, i])); |         const order = Object.fromEntries(ntxIdsInOrder.map((v, i) => [v, i])); | ||||||
| 
 | 
 | ||||||
|         this.children.sort((a, b) => (order[a.ntxId] < order[b.ntxId] ? -1 : 1)); |         this.children.sort((a, b) => { | ||||||
|  |             if (!a.ntxId || !b.ntxId) return 0; | ||||||
|  |             return (order[a.ntxId] ?? 0) < (order[b.ntxId] ?? 0) ? -1 : 1; | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         if (oldMainNtxId && newMainNtxId) { |         if (oldMainNtxId && newMainNtxId) { | ||||||
|             this.children.forEach((c) => { |             this.children.forEach((c) => { | ||||||
| @@ -485,7 +530,8 @@ export default class TabManager extends Component { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async activateNextTabCommand() { |     async activateNextTabCommand() { | ||||||
|         const activeMainNtxId = this.getActiveMainContext().ntxId; |         const activeMainNtxId = this.getActiveMainContext()?.ntxId; | ||||||
|  |         if (!activeMainNtxId) return; | ||||||
| 
 | 
 | ||||||
|         const oldIdx = this.mainNoteContexts.findIndex((nc) => nc.ntxId === activeMainNtxId); |         const oldIdx = this.mainNoteContexts.findIndex((nc) => nc.ntxId === activeMainNtxId); | ||||||
|         const newActiveNtxId = this.mainNoteContexts[oldIdx === this.mainNoteContexts.length - 1 ? 0 : oldIdx + 1].ntxId; |         const newActiveNtxId = this.mainNoteContexts[oldIdx === this.mainNoteContexts.length - 1 ? 0 : oldIdx + 1].ntxId; | ||||||
| @@ -494,7 +540,8 @@ export default class TabManager extends Component { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async activatePreviousTabCommand() { |     async activatePreviousTabCommand() { | ||||||
|         const activeMainNtxId = this.getActiveMainContext().ntxId; |         const activeMainNtxId = this.getActiveMainContext()?.ntxId; | ||||||
|  |         if (!activeMainNtxId) return; | ||||||
| 
 | 
 | ||||||
|         const oldIdx = this.mainNoteContexts.findIndex((nc) => nc.ntxId === activeMainNtxId); |         const oldIdx = this.mainNoteContexts.findIndex((nc) => nc.ntxId === activeMainNtxId); | ||||||
|         const newActiveNtxId = this.mainNoteContexts[oldIdx === 0 ? this.mainNoteContexts.length - 1 : oldIdx - 1].ntxId; |         const newActiveNtxId = this.mainNoteContexts[oldIdx === 0 ? this.mainNoteContexts.length - 1 : oldIdx - 1].ntxId; | ||||||
| @@ -503,12 +550,13 @@ export default class TabManager extends Component { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async closeActiveTabCommand() { |     async closeActiveTabCommand() { | ||||||
|  |         if (this.activeNtxId) { | ||||||
|             await this.removeNoteContext(this.activeNtxId); |             await this.removeNoteContext(this.activeNtxId); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     beforeUnloadEvent() { |     beforeUnloadEvent(): boolean { | ||||||
|         this.tabsUpdate.updateNowIfNecessary(); |         this.tabsUpdate.updateNowIfNecessary(); | ||||||
| 
 |  | ||||||
|         return true; // don't block closing the tab, this metadata is not that important
 |         return true; // don't block closing the tab, this metadata is not that important
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -518,35 +566,39 @@ export default class TabManager extends Component { | |||||||
| 
 | 
 | ||||||
|     async closeAllTabsCommand() { |     async closeAllTabsCommand() { | ||||||
|         for (const ntxIdToRemove of this.mainNoteContexts.map((nc) => nc.ntxId)) { |         for (const ntxIdToRemove of this.mainNoteContexts.map((nc) => nc.ntxId)) { | ||||||
|  |             if (ntxIdToRemove) { | ||||||
|                 await this.removeNoteContext(ntxIdToRemove); |                 await this.removeNoteContext(ntxIdToRemove); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     async closeOtherTabsCommand({ ntxId }) { |     async closeOtherTabsCommand({ ntxId }: { ntxId: string }) { | ||||||
|         for (const ntxIdToRemove of this.mainNoteContexts.map((nc) => nc.ntxId)) { |         for (const ntxIdToRemove of this.mainNoteContexts.map((nc) => nc.ntxId)) { | ||||||
|             if (ntxIdToRemove !== ntxId) { |             if (ntxIdToRemove && ntxIdToRemove !== ntxId) { | ||||||
|                 await this.removeNoteContext(ntxIdToRemove); |                 await this.removeNoteContext(ntxIdToRemove); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async closeRightTabsCommand({ ntxId }) { |     async closeRightTabsCommand({ ntxId }: { ntxId: string }) { | ||||||
|         const ntxIds = this.mainNoteContexts.map((nc) => nc.ntxId); |         const ntxIds = this.mainNoteContexts.map((nc) => nc.ntxId); | ||||||
|         const index = ntxIds.indexOf(ntxId); |         const index = ntxIds.indexOf(ntxId); | ||||||
| 
 | 
 | ||||||
|         if (index !== -1) { |         if (index !== -1) { | ||||||
|             const idsToRemove = ntxIds.slice(index + 1); |             const idsToRemove = ntxIds.slice(index + 1); | ||||||
|             for (const ntxIdToRemove of idsToRemove) { |             for (const ntxIdToRemove of idsToRemove) { | ||||||
|  |                 if (ntxIdToRemove) { | ||||||
|                     await this.removeNoteContext(ntxIdToRemove); |                     await this.removeNoteContext(ntxIdToRemove); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     async closeTabCommand({ ntxId }) { |     async closeTabCommand({ ntxId }: { ntxId: string }) { | ||||||
|         await this.removeNoteContext(ntxId); |         await this.removeNoteContext(ntxId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async moveTabToNewWindowCommand({ ntxId }) { |     async moveTabToNewWindowCommand({ ntxId }: { ntxId: string }) { | ||||||
|         const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId); |         const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId); | ||||||
| 
 | 
 | ||||||
|         const removed = await this.removeNoteContext(ntxId); |         const removed = await this.removeNoteContext(ntxId); | ||||||
| @@ -556,17 +608,16 @@ export default class TabManager extends Component { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async copyTabToNewWindowCommand({ ntxId }) { |     async copyTabToNewWindowCommand({ ntxId }: { ntxId: string }) { | ||||||
|         const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId); |         const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId); | ||||||
|         this.triggerCommand("openInWindow", { notePath, hoistedNoteId }); |         this.triggerCommand("openInWindow", { notePath, hoistedNoteId }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async reopenLastTabCommand() { |     async reopenLastTabCommand() { | ||||||
|         let closeLastEmptyTab = null; |         const closeLastEmptyTab: NoteContext | undefined = await this.mutex.runExclusively(async () => { | ||||||
| 
 |             let closeLastEmptyTab | ||||||
|         await this.mutex.runExclusively(async () => { |  | ||||||
|             if (this.recentlyClosedTabs.length === 0) { |             if (this.recentlyClosedTabs.length === 0) { | ||||||
|                 return; |                 return closeLastEmptyTab; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (this.noteContexts.length === 1 && this.noteContexts[0].isEmpty()) { |             if (this.noteContexts.length === 1 && this.noteContexts[0].isEmpty()) { | ||||||
| @@ -575,6 +626,8 @@ export default class TabManager extends Component { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const lastClosedTab = this.recentlyClosedTabs.pop(); |             const lastClosedTab = this.recentlyClosedTabs.pop(); | ||||||
|  |             if (!lastClosedTab) return closeLastEmptyTab; | ||||||
|  | 
 | ||||||
|             const noteContexts = lastClosedTab.contexts; |             const noteContexts = lastClosedTab.contexts; | ||||||
| 
 | 
 | ||||||
|             for (const noteContext of noteContexts) { |             for (const noteContext of noteContexts) { | ||||||
| @@ -589,25 +642,26 @@ export default class TabManager extends Component { | |||||||
|                 ...this.noteContexts.slice(-noteContexts.length), |                 ...this.noteContexts.slice(-noteContexts.length), | ||||||
|                 ...this.noteContexts.slice(lastClosedTab.position, -noteContexts.length) |                 ...this.noteContexts.slice(lastClosedTab.position, -noteContexts.length) | ||||||
|             ]; |             ]; | ||||||
|             await this.noteContextReorderEvent({ ntxIdsInOrder: ntxsInOrder.map((nc) => nc.ntxId) }); |             this.noteContextReorderEvent({ ntxIdsInOrder: ntxsInOrder.map((nc) => nc.ntxId).filter((id) => id !== null) }); | ||||||
| 
 | 
 | ||||||
|             let mainNtx = noteContexts.find((nc) => nc.isMainContext()); |             let mainNtx = noteContexts.find((nc) => nc.isMainContext()); | ||||||
|             if (mainNtx) { |             if (mainNtx) { | ||||||
|                 // reopened a tab, need to reorder new tab widget in tab row
 |                 // reopened a tab, need to reorder new tab widget in tab row
 | ||||||
|                 await this.triggerEvent("contextsReopened", { |                 await this.triggerEvent("contextsReopenedEvent", { | ||||||
|                     mainNtxId: mainNtx.ntxId, |                     mainNtxId: mainNtx.ntxId, | ||||||
|                     tabPosition: ntxsInOrder.filter((nc) => nc.isMainContext()).findIndex((nc) => nc.ntxId === mainNtx.ntxId) |                     tabPosition: ntxsInOrder.filter((nc) => nc.isMainContext()).findIndex((nc) => nc.ntxId === mainNtx.ntxId) | ||||||
|                 }); |                 }); | ||||||
|             } else { |             } else { | ||||||
|                 // reopened a single split, need to reorder the pane widget in split note container
 |                 // reopened a single split, need to reorder the pane widget in split note container
 | ||||||
|                 await this.triggerEvent("contextsReopened", { |                 await this.triggerEvent("contextsReopenedEvent", { | ||||||
|                     ntxId: ntxsInOrder[lastClosedTab.position].ntxId, |                     mainNtxId: ntxsInOrder[lastClosedTab.position].ntxId, | ||||||
|                     // this is safe since lastClosedTab.position can never be 0 in this case
 |                     // this is safe since lastClosedTab.position can never be 0 in this case
 | ||||||
|                     afterNtxId: ntxsInOrder[lastClosedTab.position - 1].ntxId |                     tabPosition: lastClosedTab.position - 1 | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const noteContextToActivate = noteContexts.length === 1 ? noteContexts[0] : noteContexts.find((nc) => nc.isMainContext()); |             const noteContextToActivate = noteContexts.length === 1 ? noteContexts[0] : noteContexts.find((nc) => nc.isMainContext()); | ||||||
|  |             if (!noteContextToActivate) return closeLastEmptyTab; | ||||||
| 
 | 
 | ||||||
|             await this.activateNoteContext(noteContextToActivate.ntxId); |             await this.activateNoteContext(noteContextToActivate.ntxId); | ||||||
| 
 | 
 | ||||||
| @@ -615,6 +669,7 @@ export default class TabManager extends Component { | |||||||
|                 noteContext: noteContextToActivate, |                 noteContext: noteContextToActivate, | ||||||
|                 notePath: noteContextToActivate.notePath |                 notePath: noteContextToActivate.notePath | ||||||
|             }); |             }); | ||||||
|  |             return closeLastEmptyTab; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         if (closeLastEmptyTab) { |         if (closeLastEmptyTab) { | ||||||
| @@ -626,7 +681,9 @@ export default class TabManager extends Component { | |||||||
|         this.tabsUpdate.scheduleUpdate(); |         this.tabsUpdate.scheduleUpdate(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async updateDocumentTitle(activeNoteContext) { |     async updateDocumentTitle(activeNoteContext: NoteContext | null) { | ||||||
|  |         if (!activeNoteContext) return; | ||||||
|  | 
 | ||||||
|         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
 | ||||||
|             await activeNoteContext.getNavigationTitle(), |             await activeNoteContext.getNavigationTitle(), | ||||||
| @@ -636,7 +693,7 @@ export default class TabManager extends Component { | |||||||
|         document.title = titleFragments.join(" - "); |         document.title = titleFragments.join(" - "); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async entitiesReloadedEvent({ loadResults }) { |     async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||||
|         const activeContext = this.getActiveContext(); |         const activeContext = this.getActiveContext(); | ||||||
| 
 | 
 | ||||||
|         if (activeContext && loadResults.isNoteReloaded(activeContext.noteId)) { |         if (activeContext && loadResults.isNoteReloaded(activeContext.noteId)) { | ||||||
| @@ -646,7 +703,6 @@ export default class TabManager extends Component { | |||||||
| 
 | 
 | ||||||
|     async frocaReloadedEvent() { |     async frocaReloadedEvent() { | ||||||
|         const activeContext = this.getActiveContext(); |         const activeContext = this.getActiveContext(); | ||||||
| 
 |  | ||||||
|         if (activeContext) { |         if (activeContext) { | ||||||
|             await this.updateDocumentTitle(activeContext); |             await this.updateDocumentTitle(activeContext); | ||||||
|         } |         } | ||||||
| @@ -22,13 +22,19 @@ function getItems(): MenuItem<CommandNames>[] { | |||||||
|  |  | ||||||
| function handleLinkContextMenuItem(command: string | undefined, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) { | function handleLinkContextMenuItem(command: string | undefined, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) { | ||||||
|     if (!hoistedNoteId) { |     if (!hoistedNoteId) { | ||||||
|         hoistedNoteId = appContext.tabManager.getActiveContext().hoistedNoteId; |         hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId ?? null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (command === "openNoteInNewTab") { |     if (command === "openNoteInNewTab") { | ||||||
|         appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope }); |         appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope }); | ||||||
|     } else if (command === "openNoteInNewSplit") { |     } else if (command === "openNoteInNewSplit") { | ||||||
|         const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); |         const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts(); | ||||||
|  |  | ||||||
|  |         if (!subContexts) { | ||||||
|  |             logError("subContexts is null"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const { ntxId } = subContexts[subContexts.length - 1]; |         const { ntxId } = subContexts[subContexts.length - 1]; | ||||||
|  |  | ||||||
|         appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope }); |         appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope }); | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree | |||||||
|         const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null; |         const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null; | ||||||
|         const branch = froca.getBranch(this.node.data.branchId); |         const branch = froca.getBranch(this.node.data.branchId); | ||||||
|         const isNotRoot = note?.noteId !== "root"; |         const isNotRoot = note?.noteId !== "root"; | ||||||
|         const isHoisted = note?.noteId === appContext.tabManager.getActiveContext().hoistedNoteId; |         const isHoisted = note?.noteId === appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|         const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null; |         const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null; | ||||||
|  |  | ||||||
|         // some actions don't support multi-note, so they are disabled when notes are selected, |         // some actions don't support multi-note, so they are disabled when notes are selected, | ||||||
| @@ -226,8 +226,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree | |||||||
|                 templateNoteId: templateNoteId |                 templateNoteId: templateNoteId | ||||||
|             }); |             }); | ||||||
|         } else if (command === "openNoteInSplit") { |         } else if (command === "openNoteInSplit") { | ||||||
|             const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); |             const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts(); | ||||||
|             const { ntxId } = subContexts[subContexts.length - 1]; |             const { ntxId } = subContexts?.[subContexts.length - 1] ?? {}; | ||||||
|  |  | ||||||
|             this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath }); |             this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath }); | ||||||
|         } else if (command === "convertNoteToAttachment") { |         } else if (command === "convertNoteToAttachment") { | ||||||
|   | |||||||
| @@ -152,10 +152,10 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f | |||||||
| async function activateParentNotePath() { | async function activateParentNotePath() { | ||||||
|     // this is not perfect, maybe we should find the next/previous sibling, but that's more complex |     // this is not perfect, maybe we should find the next/previous sibling, but that's more complex | ||||||
|     const activeContext = appContext.tabManager.getActiveContext(); |     const activeContext = appContext.tabManager.getActiveContext(); | ||||||
|     const parentNotePathArr = activeContext.notePathArray.slice(0, -1); |     const parentNotePathArr = activeContext?.notePathArray.slice(0, -1); | ||||||
|  |  | ||||||
|     if (parentNotePathArr.length > 0) { |     if (parentNotePathArr && parentNotePathArr.length > 0) { | ||||||
|         activeContext.setNote(parentNotePathArr.join("/")); |         activeContext?.setNote(parentNotePathArr.join("/")); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -457,13 +457,13 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig | |||||||
|     this.BasicWidget = BasicWidget; |     this.BasicWidget = BasicWidget; | ||||||
|  |  | ||||||
|     this.activateNote = async (notePath) => { |     this.activateNote = async (notePath) => { | ||||||
|         await appContext.tabManager.getActiveContext().setNote(notePath); |         await appContext.tabManager.getActiveContext()?.setNote(notePath); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.activateNewNote = async (notePath) => { |     this.activateNewNote = async (notePath) => { | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|         await appContext.tabManager.getActiveContext().setNote(notePath); |         await appContext.tabManager.getActiveContext()?.setNote(notePath); | ||||||
|         await appContext.triggerEvent("focusAndSelectTitle", {}); |         await appContext.triggerEvent("focusAndSelectTitle", {}); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -480,8 +480,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig | |||||||
|     this.openSplitWithNote = async (notePath, activate) => { |     this.openSplitWithNote = async (notePath, activate) => { | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|         const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); |         const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts(); | ||||||
|         const { ntxId } = subContexts[subContexts.length - 1]; |         const { ntxId } = subContexts?.[subContexts.length - 1] ?? {}; | ||||||
|  |  | ||||||
|         await appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath }); |         await appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath }); | ||||||
|  |  | ||||||
| @@ -591,15 +591,48 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig | |||||||
|  |  | ||||||
|     this.addTextToActiveContextEditor = (text) => appContext.triggerCommand("addTextToActiveEditor", { text }); |     this.addTextToActiveContextEditor = (text) => appContext.triggerCommand("addTextToActiveEditor", { text }); | ||||||
|  |  | ||||||
|     this.getActiveContextNote = () => appContext.tabManager.getActiveContextNote(); |     this.getActiveContextNote = (): FNote => { | ||||||
|     this.getActiveContext = () => appContext.tabManager.getActiveContext(); |         const note = appContext.tabManager.getActiveContextNote(); | ||||||
|     this.getActiveMainContext = () => appContext.tabManager.getActiveMainContext(); |         if (!note) { | ||||||
|  |             throw new Error("No active context note found"); | ||||||
|  |         } | ||||||
|  |         return note; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.getActiveContext = (): NoteContext => { | ||||||
|  |         const context = appContext.tabManager.getActiveContext(); | ||||||
|  |         if (!context) { | ||||||
|  |             throw new Error("No active context found"); | ||||||
|  |         } | ||||||
|  |         return context; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.getActiveMainContext = (): NoteContext => { | ||||||
|  |         const context = appContext.tabManager.getActiveMainContext(); | ||||||
|  |         if (!context) { | ||||||
|  |             throw new Error("No active main context found"); | ||||||
|  |         } | ||||||
|  |         return context; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     this.getNoteContexts = () => appContext.tabManager.getNoteContexts(); |     this.getNoteContexts = () => appContext.tabManager.getNoteContexts(); | ||||||
|     this.getMainNoteContexts = () => appContext.tabManager.getMainNoteContexts(); |     this.getMainNoteContexts = () => appContext.tabManager.getMainNoteContexts(); | ||||||
|  |  | ||||||
|     this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContext()?.getTextEditor(); |     this.getActiveContextTextEditor = () => { | ||||||
|     this.getActiveContextCodeEditor = () => appContext.tabManager.getActiveContext()?.getCodeEditor(); |         const context = appContext.tabManager.getActiveContext(); | ||||||
|  |         if (!context) { | ||||||
|  |             throw new Error("No active context found"); | ||||||
|  |         } | ||||||
|  |         return context.getTextEditor(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.getActiveContextCodeEditor = () => { | ||||||
|  |         const context = appContext.tabManager.getActiveContext(); | ||||||
|  |         if (!context) { | ||||||
|  |             throw new Error("No active context found"); | ||||||
|  |         } | ||||||
|  |         return context.getCodeEditor(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     this.getActiveNoteDetailWidget = () => new Promise((resolve) => appContext.triggerCommand("executeInActiveNoteDetailWidget", { callback: resolve })); |     this.getActiveNoteDetailWidget = () => new Promise((resolve) => appContext.triggerCommand("executeInActiveNoteDetailWidget", { callback: resolve })); | ||||||
|     this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath(); |     this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath(); | ||||||
|   | |||||||
| @@ -80,7 +80,7 @@ ws.subscribeToMessages(async (message) => { | |||||||
|         toastService.showPersistent(toast); |         toastService.showPersistent(toast); | ||||||
|  |  | ||||||
|         if (message.result.importedNoteId) { |         if (message.result.importedNoteId) { | ||||||
|             await appContext.tabManager.getActiveContext().setNote(message.result.importedNoteId); |             await appContext.tabManager.getActiveContext()?.setNote(message.result.importedNoteId); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
| @@ -102,7 +102,7 @@ ws.subscribeToMessages(async (message) => { | |||||||
|         toastService.showPersistent(toast); |         toastService.showPersistent(toast); | ||||||
|  |  | ||||||
|         if (message.result.parentNoteId) { |         if (message.result.parentNoteId) { | ||||||
|             await appContext.tabManager.getActiveContext().setNote(message.result.importedNoteId, { |             await appContext.tabManager.getActiveContext()?.setNote(message.result.importedNoteId, { | ||||||
|                 viewScope: { |                 viewScope: { | ||||||
|                     viewMode: "attachments" |                     viewMode: "attachments" | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -288,11 +288,15 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | |||||||
|  |  | ||||||
|             const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext(); |             const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext(); | ||||||
|  |  | ||||||
|  |             if (noteContext) { | ||||||
|                 noteContext.setNote(notePath, { viewScope }).then(() => { |                 noteContext.setNote(notePath, { viewScope }).then(() => { | ||||||
|                     if (noteContext !== appContext.tabManager.getActiveContext()) { |                     if (noteContext !== appContext.tabManager.getActiveContext()) { | ||||||
|                         appContext.tabManager.activateNoteContext(noteContext.ntxId); |                         appContext.tabManager.activateNoteContext(noteContext.ntxId); | ||||||
|                     } |                     } | ||||||
|                 }); |                 }); | ||||||
|  |             } else { | ||||||
|  |                 appContext.tabManager.openContextWithNote(notePath, { viewScope, activate: true }); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } else if (hrefLink) { |     } else if (hrefLink) { | ||||||
|         const withinEditLink = $link?.hasClass("ck-link-actions__preview"); |         const withinEditLink = $link?.hasClass("ck-link-actions__preview"); | ||||||
|   | |||||||
| @@ -86,8 +86,8 @@ async function createNote(parentNotePath: string | undefined, options: CreateNot | |||||||
|  |  | ||||||
|     await ws.waitForMaxKnownEntityChangeId(); |     await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|     if (options.activate) { |  | ||||||
|     const activeNoteContext = appContext.tabManager.getActiveContext(); |     const activeNoteContext = appContext.tabManager.getActiveContext(); | ||||||
|  |     if (activeNoteContext && options.activate) { | ||||||
|         await activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`); |         await activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`); | ||||||
|  |  | ||||||
|         if (options.focus === "title") { |         if (options.focus === "title") { | ||||||
| @@ -152,8 +152,7 @@ async function duplicateSubtree(noteId: string, parentNotePath: string) { | |||||||
|  |  | ||||||
|     await ws.waitForMaxKnownEntityChangeId(); |     await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|     const activeNoteContext = appContext.tabManager.getActiveContext(); |     appContext.tabManager.getActiveContext()?.setNote(`${parentNotePath}/${note.noteId}`); | ||||||
|     activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`); |  | ||||||
|  |  | ||||||
|     const origNote = await froca.getNote(noteId); |     const origNote = await froca.getNote(noteId); | ||||||
|     toastService.showMessage(t("note_create.duplicated", { title: origNote?.title })); |     toastService.showMessage(t("note_create.duplicated", { title: origNote?.title })); | ||||||
|   | |||||||
| @@ -138,7 +138,7 @@ function getParentProtectedStatus(node: Fancytree.FancytreeNode) { | |||||||
|     return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected; |     return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected; | ||||||
| } | } | ||||||
|  |  | ||||||
| function getNoteIdFromUrl(urlOrNotePath: string | undefined) { | function getNoteIdFromUrl(urlOrNotePath: string | null | undefined) { | ||||||
|     if (!urlOrNotePath) { |     if (!urlOrNotePath) { | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ export default class Mutex { | |||||||
|         return newPromise; |         return newPromise; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async runExclusively(cb: () => Promise<void>) { |     async runExclusively(cb: () => Promise<any>) { | ||||||
|         const unlock = await this.lock(); |         const unlock = await this.lock(); | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|   | |||||||
| @@ -171,7 +171,7 @@ export default class AttachmentActionsWidget extends BasicWidget { | |||||||
|         const { note: newNote } = await server.post<ReturnType<typeof attachmentsApiRoute.convertAttachmentToNote>>(`attachments/${this.attachmentId}/convert-to-note`); |         const { note: newNote } = await server.post<ReturnType<typeof attachmentsApiRoute.convertAttachmentToNote>>(`attachments/${this.attachmentId}/convert-to-note`); | ||||||
|         toastService.showMessage(t("attachments_actions.convert_success", { title: this.attachment.title })); |         toastService.showMessage(t("attachments_actions.convert_success", { title: this.attachment.title })); | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|         await appContext.tabManager.getActiveContext().setNote(newNote.noteId); |         await appContext.tabManager.getActiveContext()?.setNote(newNote.noteId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async renameAttachmentCommand() { |     async renameAttachmentCommand() { | ||||||
|   | |||||||
| @@ -149,7 +149,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget { | |||||||
|                 const note = await dateNoteService.getDayNote(date); |                 const note = await dateNoteService.getDayNote(date); | ||||||
|  |  | ||||||
|                 if (note) { |                 if (note) { | ||||||
|                     appContext.tabManager.getActiveContext().setNote(note.noteId); |                     appContext.tabManager.getActiveContext()?.setNote(note.noteId); | ||||||
|                     this.dropdown?.hide(); |                     this.dropdown?.hide(); | ||||||
|                 } else { |                 } else { | ||||||
|                     toastService.showError(t("calendar.cannot_find_day_note")); |                     toastService.showError(t("calendar.cannot_find_day_note")); | ||||||
| @@ -189,10 +189,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget { | |||||||
|  |  | ||||||
|     async dropdownShown() { |     async dropdownShown() { | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.CALENDAR_WIDGET); |         await libraryLoader.requireLibrary(libraryLoader.CALENDAR_WIDGET); | ||||||
|  |         this.init(appContext.tabManager.getActiveContextNote()?.getOwnedLabelValue("dateNote") ?? null); | ||||||
|         const activeNote = appContext.tabManager.getActiveContextNote(); |  | ||||||
|  |  | ||||||
|         this.init(activeNote?.getOwnedLabelValue("dateNote")); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     init(activeDate: string | null) { |     init(activeDate: string | null) { | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ export default class NoteLauncher extends AbstractLauncher { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     getHoistedNoteId() { |     getHoistedNoteId() { | ||||||
|         return this.launcherNote.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext().hoistedNoteId; |         return this.launcherNote.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getTitle() { |     getTitle() { | ||||||
|   | |||||||
| @@ -10,6 +10,6 @@ export default class TodayLauncher extends NoteLauncher { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     getHoistedNoteId() { |     getHoistedNoteId() { | ||||||
|         return appContext.tabManager.getActiveContext().hoistedNoteId; |         return appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -228,7 +228,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title })); |         toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title })); | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|         await appContext.tabManager.getActiveContext().setNote(newAttachment.ownerId, { |         await appContext.tabManager.getActiveContext()?.setNote(newAttachment.ownerId, { | ||||||
|             viewScope: { |             viewScope: { | ||||||
|                 viewMode: "attachments", |                 viewMode: "attachments", | ||||||
|                 attachmentId: newAttachment.attachmentId |                 attachmentId: newAttachment.attachmentId | ||||||
|   | |||||||
| @@ -24,8 +24,7 @@ export default class LeftPaneContainer extends FlexContainer<Component> { | |||||||
|             if (visible) { |             if (visible) { | ||||||
|                 this.triggerEvent("focusTree", {}); |                 this.triggerEvent("focusTree", {}); | ||||||
|             } else { |             } else { | ||||||
|                 const activeNoteContext = appContext.tabManager.getActiveContext(); |                 this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId }); | ||||||
|                 this.triggerEvent("focusOnDetail", { ntxId: activeNoteContext.ntxId }); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,8 +1,29 @@ | |||||||
| import FlexContainer from "./flex_container.js"; | import FlexContainer from "./flex_container.js"; | ||||||
| import appContext from "../../components/app_context.js"; | import appContext from "../../components/app_context.js"; | ||||||
|  | import NoteContext from "../../components/note_context.js"; | ||||||
|  | import type { CommandMappings, EventNames, EventData } from "../../components/app_context.js"; | ||||||
|  | import type BasicWidget from "../basic_widget.js"; | ||||||
| 
 | 
 | ||||||
| export default class SplitNoteContainer extends FlexContainer { | interface NoteContextEvent { | ||||||
|     constructor(widgetFactory) { |     noteContext: NoteContext; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface SplitNoteWidget extends BasicWidget { | ||||||
|  |     hasBeenAlreadyShown?: boolean; | ||||||
|  |     ntxId?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type WidgetFactory = () => SplitNoteWidget; | ||||||
|  | 
 | ||||||
|  | interface Widgets { | ||||||
|  |     [key: string]: SplitNoteWidget; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> { | ||||||
|  |     private widgetFactory: WidgetFactory; | ||||||
|  |     private widgets: Widgets; | ||||||
|  | 
 | ||||||
|  |     constructor(widgetFactory: WidgetFactory) { | ||||||
|         super("row"); |         super("row"); | ||||||
| 
 | 
 | ||||||
|         this.widgetFactory = widgetFactory; |         this.widgetFactory = widgetFactory; | ||||||
| @@ -13,7 +34,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         this.collapsible(); |         this.collapsible(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async newNoteContextCreatedEvent({ noteContext }) { |     async newNoteContextCreatedEvent({ noteContext }: NoteContextEvent) { | ||||||
|         const widget = this.widgetFactory(); |         const widget = this.widgetFactory(); | ||||||
| 
 | 
 | ||||||
|         const $renderedWidget = widget.render(); |         const $renderedWidget = widget.render(); | ||||||
| @@ -23,19 +44,31 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
| 
 | 
 | ||||||
|         this.$widget.append($renderedWidget); |         this.$widget.append($renderedWidget); | ||||||
| 
 | 
 | ||||||
|         widget.handleEvent("initialRenderComplete"); |         widget.handleEvent("initialRenderComplete", {}); | ||||||
| 
 | 
 | ||||||
|         widget.toggleExt(false); |         widget.toggleExt(false); | ||||||
| 
 | 
 | ||||||
|  |         if (noteContext.ntxId) { | ||||||
|             this.widgets[noteContext.ntxId] = widget; |             this.widgets[noteContext.ntxId] = widget; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         await widget.handleEvent("setNoteContext", { noteContext }); |         await widget.handleEvent("setNoteContext", { noteContext }); | ||||||
| 
 | 
 | ||||||
|         this.child(widget); |         this.child(widget); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }) { |     async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }: { | ||||||
|         const mainNtxId = appContext.tabManager.getActiveMainContext().ntxId; |         ntxId: string; | ||||||
|  |         notePath?: string; | ||||||
|  |         hoistedNoteId?: string; | ||||||
|  |         viewScope?: any; | ||||||
|  |     }) { | ||||||
|  |         const mainNtxId = appContext.tabManager.getActiveMainContext()?.ntxId; | ||||||
|  | 
 | ||||||
|  |         if (!mainNtxId) { | ||||||
|  |             logError("empty mainNtxId!"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         if (!ntxId) { |         if (!ntxId) { | ||||||
|             logError("empty ntxId!"); |             logError("empty ntxId!"); | ||||||
| @@ -43,7 +76,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|             ntxId = mainNtxId; |             ntxId = mainNtxId; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         hoistedNoteId = hoistedNoteId || appContext.tabManager.getActiveContext().hoistedNoteId; |         hoistedNoteId = hoistedNoteId || appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
| 
 | 
 | ||||||
|         const noteContext = await appContext.tabManager.openEmptyTab(null, hoistedNoteId, mainNtxId); |         const noteContext = await appContext.tabManager.openEmptyTab(null, hoistedNoteId, mainNtxId); | ||||||
| 
 | 
 | ||||||
| @@ -53,7 +86,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         // insert the note context after the originating note context
 |         // insert the note context after the originating note context
 | ||||||
|         ntxIds.splice(ntxIds.indexOf(ntxId) + 1, 0, noteContext.ntxId); |         ntxIds.splice(ntxIds.indexOf(ntxId) + 1, 0, noteContext.ntxId); | ||||||
| 
 | 
 | ||||||
|         this.triggerCommand("noteContextReorder", { ntxIdsInOrder: ntxIds }); |         this.triggerCommand("noteContextReorder" as keyof CommandMappings, { ntxIdsInOrder: ntxIds }); | ||||||
| 
 | 
 | ||||||
|         // move the note context rendered widget after the originating widget
 |         // move the note context rendered widget after the originating widget
 | ||||||
|         this.$widget.find(`[data-ntx-id="${noteContext.ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${ntxId}"]`)); |         this.$widget.find(`[data-ntx-id="${noteContext.ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${ntxId}"]`)); | ||||||
| @@ -67,11 +100,11 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     closeThisNoteSplitCommand({ ntxId }) { |     closeThisNoteSplitCommand({ ntxId }: { ntxId: string }): void { | ||||||
|         appContext.tabManager.removeNoteContext(ntxId); |         appContext.tabManager.removeNoteContext(ntxId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async moveThisNoteSplitCommand({ ntxId, isMovingLeft }) { |     async moveThisNoteSplitCommand({ ntxId, isMovingLeft }: { ntxId: string; isMovingLeft: boolean }): Promise<void> { | ||||||
|         if (!ntxId) { |         if (!ntxId) { | ||||||
|             logError("empty ntxId!"); |             logError("empty ntxId!"); | ||||||
|             return; |             return; | ||||||
| @@ -96,7 +129,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         const newNtxIds = [...ntxIds.slice(0, leftIndex), ntxIds[leftIndex + 1], ntxIds[leftIndex], ...ntxIds.slice(leftIndex + 2)]; |         const newNtxIds = [...ntxIds.slice(0, leftIndex), ntxIds[leftIndex + 1], ntxIds[leftIndex], ...ntxIds.slice(leftIndex + 2)]; | ||||||
|         const isChangingMainContext = !contexts[leftIndex].mainNtxId; |         const isChangingMainContext = !contexts[leftIndex].mainNtxId; | ||||||
| 
 | 
 | ||||||
|         this.triggerCommand("noteContextReorder", { |         this.triggerCommand("noteContextReorder" as keyof CommandMappings, { | ||||||
|             ntxIdsInOrder: newNtxIds, |             ntxIdsInOrder: newNtxIds, | ||||||
|             oldMainNtxId: isChangingMainContext ? ntxIds[leftIndex] : null, |             oldMainNtxId: isChangingMainContext ? ntxIds[leftIndex] : null, | ||||||
|             newMainNtxId: isChangingMainContext ? ntxIds[leftIndex + 1] : null |             newMainNtxId: isChangingMainContext ? ntxIds[leftIndex + 1] : null | ||||||
| @@ -109,16 +142,16 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]); |         await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     activeContextChangedEvent() { |     activeContextChangedEvent(): void { | ||||||
|         this.refresh(); |         this.refresh(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     noteSwitchedAndActivatedEvent() { |     noteSwitchedAndActivatedEvent(): void { | ||||||
|         this.refresh(); |         this.refresh(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     noteContextRemovedEvent({ ntxIds }) { |     noteContextRemovedEvent({ ntxIds }: { ntxIds: string[] }): void { | ||||||
|         this.children = this.children.filter((c) => !ntxIds.includes(c.ntxId)); |         this.children = this.children.filter((c) => c.ntxId && !ntxIds.includes(c.ntxId)); | ||||||
| 
 | 
 | ||||||
|         for (const ntxId of ntxIds) { |         for (const ntxId of ntxIds) { | ||||||
|             this.$widget.find(`[data-ntx-id="${ntxId}"]`).remove(); |             this.$widget.find(`[data-ntx-id="${ntxId}"]`).remove(); | ||||||
| @@ -127,7 +160,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     contextsReopenedEvent({ ntxId, afterNtxId }) { |     contextsReopenedEvent({ ntxId, afterNtxId }: { ntxId?: string; afterNtxId?: string }): void { | ||||||
|         if (ntxId === undefined || afterNtxId === undefined) { |         if (ntxId === undefined || afterNtxId === undefined) { | ||||||
|             // no single split reopened
 |             // no single split reopened
 | ||||||
|             return; |             return; | ||||||
| @@ -135,13 +168,11 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         this.$widget.find(`[data-ntx-id="${ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${afterNtxId}"]`)); |         this.$widget.find(`[data-ntx-id="${ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${afterNtxId}"]`)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async refresh() { |     async refresh(): Promise<void> { | ||||||
|         this.toggleExt(true); |         this.toggleExt(true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     toggleInt(show) {} // not needed
 |     toggleExt(show: boolean): void { | ||||||
| 
 |  | ||||||
|     toggleExt(show) { |  | ||||||
|         const activeMainContext = appContext.tabManager.getActiveMainContext(); |         const activeMainContext = appContext.tabManager.getActiveMainContext(); | ||||||
|         const activeNtxId = activeMainContext ? activeMainContext.ntxId : null; |         const activeNtxId = activeMainContext ? activeMainContext.ntxId : null; | ||||||
| 
 | 
 | ||||||
| @@ -149,7 +180,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|             const noteContext = appContext.tabManager.getNoteContextById(ntxId); |             const noteContext = appContext.tabManager.getNoteContextById(ntxId); | ||||||
| 
 | 
 | ||||||
|             const widget = this.widgets[ntxId]; |             const widget = this.widgets[ntxId]; | ||||||
|             widget.toggleExt(show && activeNtxId && [noteContext.ntxId, noteContext.mainNtxId].includes(activeNtxId)); |             widget.toggleExt(show && activeNtxId !== null && [noteContext.ntxId, noteContext.mainNtxId].includes(activeNtxId)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -158,41 +189,50 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|      * are not executed, we're waiting for the first tab activation, and then we update the tab. After this initial |      * are not executed, we're waiting for the first tab activation, and then we update the tab. After this initial | ||||||
|      * activation, further note switches are always propagated to the tabs. |      * activation, further note switches are always propagated to the tabs. | ||||||
|      */ |      */ | ||||||
|     handleEventInChildren(name, data) { |     handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<any> | null { | ||||||
|         if (["noteSwitched", "noteSwitchedAndActivated"].includes(name)) { |         if (["noteSwitched", "noteSwitchedAndActivated"].includes(name)) { | ||||||
|             // this event is propagated only to the widgets of a particular tab
 |             // this event is propagated only to the widgets of a particular tab
 | ||||||
|             const widget = this.widgets[data.noteContext.ntxId]; |             const noteContext = (data as NoteContextEvent).noteContext; | ||||||
|  |             const widget = noteContext.ntxId ? this.widgets[noteContext.ntxId] : undefined; | ||||||
| 
 | 
 | ||||||
|             if (!widget) { |             if (!widget) { | ||||||
|                 return Promise.resolve(); |                 return Promise.resolve(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (widget.hasBeenAlreadyShown || name === "noteSwitchedAndActivated" || appContext.tabManager.getActiveMainContext() === data.noteContext.getMainContext()) { |             if (widget.hasBeenAlreadyShown || name === "noteSwitchedAndActivatedEvent" || appContext.tabManager.getActiveMainContext() === noteContext.getMainContext()) { | ||||||
|                 widget.hasBeenAlreadyShown = true; |                 widget.hasBeenAlreadyShown = true; | ||||||
| 
 | 
 | ||||||
|                 return [widget.handleEvent("noteSwitched", data), this.refreshNotShown(data)]; |                 return Promise.all([ | ||||||
|  |                     widget.handleEvent("noteSwitched", { noteContext, notePath: noteContext.notePath }), | ||||||
|  |                     this.refreshNotShown({ noteContext }) | ||||||
|  |                 ]); | ||||||
|             } else { |             } else { | ||||||
|                 return Promise.resolve(); |                 return Promise.resolve(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (name === "activeContextChanged") { |         if (name === "activeContextChanged") { | ||||||
|             return this.refreshNotShown(data); |             return this.refreshNotShown(data as NoteContextEvent); | ||||||
|         } else { |         } else { | ||||||
|             return super.handleEventInChildren(name, data); |             return super.handleEventInChildren(name, data); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     refreshNotShown(data) { |     private refreshNotShown(data: NoteContextEvent): Promise<any> { | ||||||
|         const promises = []; |         const promises: Promise<any>[] = []; | ||||||
| 
 | 
 | ||||||
|         for (const subContext of data.noteContext.getMainContext().getSubContexts()) { |         for (const subContext of data.noteContext.getMainContext().getSubContexts()) { | ||||||
|  |             if (!subContext.ntxId) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             const widget = this.widgets[subContext.ntxId]; |             const widget = this.widgets[subContext.ntxId]; | ||||||
| 
 | 
 | ||||||
|             if (!widget.hasBeenAlreadyShown) { |             if (!widget.hasBeenAlreadyShown) { | ||||||
|                 widget.hasBeenAlreadyShown = true; |                 widget.hasBeenAlreadyShown = true; | ||||||
| 
 | 
 | ||||||
|                 promises.push(widget.handleEvent("activeContextChanged", { noteContext: subContext })); |                 const eventPromise = widget.handleEvent("activeContextChanged", { noteContext: subContext }); | ||||||
|  |                 promises.push(eventPromise || Promise.resolve()); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -65,7 +65,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|             await ws.waitForMaxKnownEntityChangeId(); |             await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|             await appContext.tabManager.getActiveContext().setNote(notePath); |             await appContext.tabManager.getActiveContext()?.setNote(notePath); | ||||||
|  |  | ||||||
|             toastService.showMessage(t("code_buttons.sql_console_saved_message", { notePath: await treeService.getNotePathTitle(notePath) })); |             toastService.showMessage(t("code_buttons.sql_console_saved_message", { notePath: await treeService.getNotePathTitle(notePath) })); | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -60,15 +60,15 @@ export default class ContextualHelpButton extends NoteContextAwareWidget { | |||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$widget.on("click", () => { |         this.$widget.on("click", () => { | ||||||
|             const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); |             const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts(); | ||||||
|             const targetNote = `_help_${this.helpNoteIdToOpen}`; |             const targetNote = `_help_${this.helpNoteIdToOpen}`; | ||||||
|             const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help"); |             const helpSubcontext = subContexts?.find((s) => s.viewScope?.viewMode === "contextual-help"); | ||||||
|             const viewScope: ViewScope = { |             const viewScope: ViewScope = { | ||||||
|                 viewMode: "contextual-help" |                 viewMode: "contextual-help" | ||||||
|             }; |             }; | ||||||
|             if (!helpSubcontext) { |             if (!helpSubcontext) { | ||||||
|                 // The help is not already open, open a new split with it. |                 // The help is not already open, open a new split with it. | ||||||
|                 const { ntxId } = subContexts[subContexts.length - 1]; |                 const { ntxId } = subContexts?.[subContexts.length - 1] ?? {}; | ||||||
|                 this.triggerCommand("openNewNoteSplit", { |                 this.triggerCommand("openNewNoteSplit", { | ||||||
|                     ntxId, |                     ntxId, | ||||||
|                     notePath: targetNote, |                     notePath: targetNote, | ||||||
|   | |||||||
| @@ -28,8 +28,8 @@ class MobileDetailMenuWidget extends BasicWidget { | |||||||
|                 x: e.pageX, |                 x: e.pageX, | ||||||
|                 y: e.pageY, |                 y: e.pageY, | ||||||
|                 items: [ |                 items: [ | ||||||
|                     { title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note.type !== "search" }, |                     { title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" }, | ||||||
|                     { title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note.noteId !== "root" } |                     { title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note?.noteId !== "root" } | ||||||
|                 ], |                 ], | ||||||
|                 selectMenuItemHandler: async ({ command }) => { |                 selectMenuItemHandler: async ({ command }) => { | ||||||
|                     if (command === "insertChildNote") { |                     if (command === "insertChildNote") { | ||||||
|   | |||||||
| @@ -322,7 +322,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget { | |||||||
|             .warmupTicks(30) |             .warmupTicks(30) | ||||||
|             .onNodeClick((node) => { |             .onNodeClick((node) => { | ||||||
|                 if (node.id) { |                 if (node.id) { | ||||||
|                     appContext.tabManager.getActiveContext().setNote((node as Node).id); |                     appContext.tabManager.getActiveContext()?.setNote((node as Node).id); | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|             .onNodeRightClick((node, e) => { |             .onNodeRightClick((node, e) => { | ||||||
| @@ -371,7 +371,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget { | |||||||
|         if (mapRootNoteId === "hoisted") { |         if (mapRootNoteId === "hoisted") { | ||||||
|             mapRootNoteId = hoistedNoteService.getHoistedNoteId(); |             mapRootNoteId = hoistedNoteService.getHoistedNoteId(); | ||||||
|         } else if (!mapRootNoteId) { |         } else if (!mapRootNoteId) { | ||||||
|             mapRootNoteId = appContext.tabManager.getActiveContext().parentNoteId; |             mapRootNoteId = appContext.tabManager.getActiveContext()?.parentNoteId; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return mapRootNoteId ?? ""; |         return mapRootNoteId ?? ""; | ||||||
|   | |||||||
| @@ -424,10 +424,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|                 const activeNoteContext = appContext.tabManager.getActiveContext(); |                 const activeNoteContext = appContext.tabManager.getActiveContext(); | ||||||
|                 const opts: SetNoteOpts = {}; |                 const opts: SetNoteOpts = {}; | ||||||
|                 if (activeNoteContext.viewScope?.viewMode === "contextual-help") { |                 if (activeNoteContext?.viewScope?.viewMode === "contextual-help") { | ||||||
|                     opts.viewScope = activeNoteContext.viewScope; |                     opts.viewScope = activeNoteContext.viewScope; | ||||||
|                 } |                 } | ||||||
|                 await activeNoteContext.setNote(notePath, opts); |                 await activeNoteContext?.setNote(notePath, opts); | ||||||
|             }, |             }, | ||||||
|             expand: (event, data) => this.setExpanded(data.node.data.branchId, true), |             expand: (event, data) => this.setExpanded(data.node.data.branchId, true), | ||||||
|             collapse: (event, data) => this.setExpanded(data.node.data.branchId, false), |             collapse: (event, data) => this.setExpanded(data.node.data.branchId, false), | ||||||
| @@ -1758,6 +1758,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|         appContext.tabManager.getActiveContext().setNote(resp.note.noteId); |         appContext.tabManager.getActiveContext()?.setNote(resp.note.noteId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -246,7 +246,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|             await ws.waitForMaxKnownEntityChangeId(); |             await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|             await appContext.tabManager.getActiveContext().setNote(notePath); |             await appContext.tabManager.getActiveContext()?.setNote(notePath); | ||||||
|             // Note the {{- notePathTitle}} in json file is not typo, it's unescaping |             // Note the {{- notePathTitle}} in json file is not typo, it's unescaping | ||||||
|             // See https://www.i18next.com/translation-function/interpolation#unescape |             // See https://www.i18next.com/translation-function/interpolation#unescape | ||||||
|             toastService.showMessage(t("search_definition.search_note_saved", { notePathTitle: await treeService.getNotePathTitle(notePath) })); |             toastService.showMessage(t("search_definition.search_note_saved", { notePathTitle: await treeService.getNotePathTitle(notePath) })); | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import Draggabilly, { type DraggabillyCallback, type MoveVector } from "draggabilly"; | import Draggabilly, { type MoveVector } from "draggabilly"; | ||||||
| import { t } from "../services/i18n.js"; | import { t } from "../services/i18n.js"; | ||||||
| import BasicWidget from "./basic_widget.js"; | import BasicWidget from "./basic_widget.js"; | ||||||
| import contextMenu from "../menus/context_menu.js"; | import contextMenu from "../menus/context_menu.js"; | ||||||
| import utils from "../services/utils.js"; | import utils from "../services/utils.js"; | ||||||
| import keyboardActionService from "../services/keyboard_actions.js"; | import keyboardActionService from "../services/keyboard_actions.js"; | ||||||
| import appContext, { type CommandData, type CommandListenerData, type EventData } from "../components/app_context.js"; | import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js"; | ||||||
| import froca from "../services/froca.js"; | import froca from "../services/froca.js"; | ||||||
| import attributeService from "../services/attributes.js"; | import attributeService from "../services/attributes.js"; | ||||||
| import type NoteContext from "../components/note_context.js"; | import type NoteContext from "../components/note_context.js"; | ||||||
| @@ -419,13 +419,13 @@ export default class TabRowWidget extends BasicWidget { | |||||||
|     closeActiveTabCommand({ $el }: CommandListenerData<"closeActiveTab">) { |     closeActiveTabCommand({ $el }: CommandListenerData<"closeActiveTab">) { | ||||||
|         const ntxId = $el.closest(".note-tab").attr("data-ntx-id"); |         const ntxId = $el.closest(".note-tab").attr("data-ntx-id"); | ||||||
|  |  | ||||||
|         appContext.tabManager.removeNoteContext(ntxId); |         appContext.tabManager.removeNoteContext(ntxId ?? null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     setTabCloseEvent($tab: JQuery<HTMLElement>) { |     setTabCloseEvent($tab: JQuery<HTMLElement>) { | ||||||
|         $tab.on("mousedown", (e) => { |         $tab.on("mousedown", (e) => { | ||||||
|             if (e.which === 2) { |             if (e.which === 2) { | ||||||
|                 appContext.tabManager.removeNoteContext($tab.attr("data-ntx-id")); |                 appContext.tabManager.removeNoteContext($tab.attr("data-ntx-id") ?? null); | ||||||
|  |  | ||||||
|                 return true; // event has been handled |                 return true; // event has been handled | ||||||
|             } |             } | ||||||
| @@ -494,7 +494,7 @@ export default class TabRowWidget extends BasicWidget { | |||||||
|         return $tab.attr("data-ntx-id"); |         return $tab.attr("data-ntx-id"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     noteContextRemovedEvent({ ntxIds }: EventData<"noteContextRemovedEvent">) { |     noteContextRemovedEvent({ ntxIds }: EventData<"noteContextRemoved">) { | ||||||
|         for (const ntxId of ntxIds) { |         for (const ntxId of ntxIds) { | ||||||
|             this.removeTab(ntxId); |             this.removeTab(ntxId); | ||||||
|         } |         } | ||||||
| @@ -650,7 +650,7 @@ export default class TabRowWidget extends BasicWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     contextsReopenedEvent({ mainNtxId, tabPosition }: EventData<"contextsReopenedEvent">) { |     contextsReopenedEvent({ mainNtxId, tabPosition }: EventData<"contextsReopenedEvent">) { | ||||||
|         if (mainNtxId === undefined || tabPosition === undefined) { |         if (!mainNtxId || !tabPosition) { | ||||||
|             // no tab reopened |             // no tab reopened | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -748,7 +748,7 @@ export default class TabRowWidget extends BasicWidget { | |||||||
|     hoistedNoteChangedEvent({ ntxId }: EventData<"hoistedNoteChanged">) { |     hoistedNoteChangedEvent({ ntxId }: EventData<"hoistedNoteChanged">) { | ||||||
|         const $tab = this.getTabById(ntxId); |         const $tab = this.getTabById(ntxId); | ||||||
|  |  | ||||||
|         if ($tab) { |         if ($tab && ntxId) { | ||||||
|             const noteContext = appContext.tabManager.getNoteContextById(ntxId); |             const noteContext = appContext.tabManager.getNoteContextById(ntxId); | ||||||
|  |  | ||||||
|             this.updateTab($tab, noteContext); |             this.updateTab($tab, noteContext); | ||||||
|   | |||||||
| @@ -155,7 +155,7 @@ export default class CalendarView extends ViewMode { | |||||||
|  |  | ||||||
|                 const note = await date_notes.getDayNote(e.dateStr); |                 const note = await date_notes.getDayNote(e.dateStr); | ||||||
|                 if (note) { |                 if (note) { | ||||||
|                     appContext.tabManager.getActiveContext().setNote(note.noteId); |                     appContext.tabManager.getActiveContext()?.setNote(note.noteId); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user