mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 21:19:01 +01:00 
			
		
		
		
	Merge branch 'develop' of https://github.com/TriliumNext/Notes into develop
This commit is contained in:
		
						commit
						3f641c98fb
					
				@ -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;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -172,9 +171,9 @@ export type CommandMappings = {
 | 
				
			|||||||
        callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
 | 
					        callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    executeWithTextEditor: CommandData &
 | 
					    executeWithTextEditor: CommandData &
 | 
				
			||||||
        ExecuteCommandData<TextEditor> & {
 | 
					    ExecuteCommandData<TextEditor> & {
 | 
				
			||||||
            callback?: GetTextEditorCallback;
 | 
					        callback?: GetTextEditorCallback;
 | 
				
			||||||
        };
 | 
					    };
 | 
				
			||||||
    executeWithCodeEditor: CommandData & ExecuteCommandData<null>;
 | 
					    executeWithCodeEditor: CommandData & ExecuteCommandData<null>;
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Called upon when attempting to retrieve the content element of a {@link NoteContext}.
 | 
					     * Called upon when attempting to retrieve the content element of a {@link NoteContext}.
 | 
				
			||||||
@ -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) {
 | 
				
			||||||
            logError(`Loading note contexts '${options.get("openNoteContexts")}' failed: ${e.message} ${e.stack}`);
 | 
					            if (e instanceof Error) {
 | 
				
			||||||
 | 
					                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()) {
 | 
				
			||||||
                order[noteContext.ntxId] = i++;
 | 
					                if (noteContext.ntxId) {
 | 
				
			||||||
 | 
					                    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() {
 | 
				
			||||||
        await this.removeNoteContext(this.activeNtxId);
 | 
					        if (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)) {
 | 
				
			||||||
            await this.removeNoteContext(ntxIdToRemove);
 | 
					            if (ntxIdToRemove) {
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async closeOtherTabsCommand({ ntxId }) {
 | 
					 | 
				
			||||||
        for (const ntxIdToRemove of this.mainNoteContexts.map((nc) => nc.ntxId)) {
 | 
					 | 
				
			||||||
            if (ntxIdToRemove !== ntxId) {
 | 
					 | 
				
			||||||
                await this.removeNoteContext(ntxIdToRemove);
 | 
					                await this.removeNoteContext(ntxIdToRemove);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async closeRightTabsCommand({ ntxId }) {
 | 
					    async closeOtherTabsCommand({ ntxId }: { ntxId: string }) {
 | 
				
			||||||
 | 
					        for (const ntxIdToRemove of this.mainNoteContexts.map((nc) => nc.ntxId)) {
 | 
				
			||||||
 | 
					            if (ntxIdToRemove && ntxIdToRemove !== ntxId) {
 | 
				
			||||||
 | 
					                await this.removeNoteContext(ntxIdToRemove);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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) {
 | 
				
			||||||
                await this.removeNoteContext(ntxIdToRemove);
 | 
					                if (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();
 | 
				
			||||||
@ -665,5 +698,5 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default FrontendScriptApi as any as {
 | 
					export default FrontendScriptApi as any as {
 | 
				
			||||||
    new (startNote: FNote, currentNote: FNote, originEntity: Entity | null, $container: JQuery<HTMLElement> | null): Api;
 | 
					    new(startNote: FNote, currentNote: FNote, originEntity: Entity | null, $container: JQuery<HTMLElement> | null): Api;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            noteContext.setNote(notePath, { viewScope }).then(() => {
 | 
					            if (noteContext) {
 | 
				
			||||||
                if (noteContext !== appContext.tabManager.getActiveContext()) {
 | 
					                noteContext.setNote(notePath, { viewScope }).then(() => {
 | 
				
			||||||
                    appContext.tabManager.activateNoteContext(noteContext.ntxId);
 | 
					                    if (noteContext !== appContext.tabManager.getActiveContext()) {
 | 
				
			||||||
                }
 | 
					                        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() {
 | 
				
			||||||
 | 
				
			|||||||
@ -43,8 +43,8 @@ const DROPDOWN_TPL = `
 | 
				
			|||||||
                data-calendar-input="month"></button>
 | 
					                data-calendar-input="month"></button>
 | 
				
			||||||
            <ul class="dropdown-menu" data-calendar-input="month-list">
 | 
					            <ul class="dropdown-menu" data-calendar-input="month-list">
 | 
				
			||||||
                ${Object.entries(MONTHS)
 | 
					                ${Object.entries(MONTHS)
 | 
				
			||||||
                    .map(([i, month]) => `<li><button class="dropdown-item" data-value=${i}>${month}</button></li>`)
 | 
					        .map(([i, month]) => `<li><button class="dropdown-item" data-value=${i}>${month}</button></li>`)
 | 
				
			||||||
                    .join("")}
 | 
					        .join("")}
 | 
				
			||||||
            </ul>
 | 
					            </ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <button class="calendar-btn tn-tool-button bx bx-chevron-right" data-calendar-toggle="next"></button>
 | 
					            <button class="calendar-btn tn-tool-button bx bx-chevron-right" data-calendar-toggle="next"></button>
 | 
				
			||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.widgets[noteContext.ntxId] = widget;
 | 
					        if (noteContext.ntxId) {
 | 
				
			||||||
 | 
					            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),
 | 
				
			||||||
@ -619,10 +619,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                // TODO: Deduplicate with server's notes.ts#getAndValidateParent
 | 
					                // TODO: Deduplicate with server's notes.ts#getAndValidateParent
 | 
				
			||||||
                if (!["search", "launcher"].includes(note.type)
 | 
					                if (!["search", "launcher"].includes(note.type)
 | 
				
			||||||
                        && !note.isOptions()
 | 
					                    && !note.isOptions()
 | 
				
			||||||
                        && !note.isLaunchBarConfig()
 | 
					                    && !note.isLaunchBarConfig()
 | 
				
			||||||
                        && !note.noteId.startsWith("_help")
 | 
					                    && !note.noteId.startsWith("_help")
 | 
				
			||||||
                    ) {
 | 
					                ) {
 | 
				
			||||||
                    const $createChildNoteButton = $(`<span class="tree-item-button add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on(
 | 
					                    const $createChildNoteButton = $(`<span class="tree-item-button add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on(
 | 
				
			||||||
                        "click",
 | 
					                        "click",
 | 
				
			||||||
                        cancelClickPropagation
 | 
					                        cancelClickPropagation
 | 
				
			||||||
@ -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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -516,7 +516,7 @@ export default class TabRowWidget extends BasicWidget {
 | 
				
			|||||||
            this.draggabillyDragging.element.style.transform = "";
 | 
					            this.draggabillyDragging.element.style.transform = "";
 | 
				
			||||||
            this.draggabillyDragging.dragEnd();
 | 
					            this.draggabillyDragging.dragEnd();
 | 
				
			||||||
            this.draggabillyDragging.isDragging = false;
 | 
					            this.draggabillyDragging.isDragging = false;
 | 
				
			||||||
            this.draggabillyDragging.positionDrag = () => {}; // Prevent Draggabilly from updating tabEl.style.transform in later frames
 | 
					            this.draggabillyDragging.positionDrag = () => { }; // Prevent Draggabilly from updating tabEl.style.transform in later frames
 | 
				
			||||||
            this.draggabillyDragging.destroy();
 | 
					            this.draggabillyDragging.destroy();
 | 
				
			||||||
            this.draggabillyDragging = null;
 | 
					            this.draggabillyDragging = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -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);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user