From 47fd2affa4e3e4684efc65f871f7047d19cd9266 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 6 Jan 2026 00:59:32 +0200 Subject: [PATCH 1/6] feat(tree): use direct DOM manipulation instead of jQuery --- apps/client/src/widgets/note_tree.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index 9a2c61d22..742d6490a 100644 --- a/apps/client/src/widgets/note_tree.ts +++ b/apps/client/src/widgets/note_tree.ts @@ -153,7 +153,7 @@ const TPL = /*html*/` const MAX_SEARCH_RESULTS_IN_TREE = 100; // this has to be hanged on the actual elements to effectively intercept and stop click event -const cancelClickPropagation: (e: JQuery.ClickEvent) => void = (e) => e.stopPropagation(); +const cancelClickPropagation: (e: JQuery.ClickEvent | MouseEvent) => void = (e) => e.stopPropagation(); // TODO: Fix once we remove Node.js API from public type Timeout = NodeJS.Timeout | string | number | undefined; @@ -652,12 +652,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { && !note.isLaunchBarConfig() && !note.noteId.startsWith("_help") ) { - const $createChildNoteButton = $(``).on( - "click", - cancelClickPropagation - ); - - $span.append($createChildNoteButton); + const createChildEl = document.createElement("span"); + createChildEl.className = "tree-item-button tn-icon add-note-button bx bx-plus"; + createChildEl.title = t("note_tree.create-child-note"); + createChildEl.addEventListener("click", cancelClickPropagation); + node.span.append(createChildEl); } if (isHoistedNote) { From bde6068f2d4da3b63c1a40ee7582da1a83e77436 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 6 Jan 2026 01:07:37 +0200 Subject: [PATCH 2/6] refactor(tree): extract enchance title into separate method --- apps/client/src/widgets/note_tree.ts | 192 ++++++++++++++------------- 1 file changed, 97 insertions(+), 95 deletions(-) diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index 742d6490a..3e9481458 100644 --- a/apps/client/src/widgets/note_tree.ts +++ b/apps/client/src/widgets/note_tree.ts @@ -598,101 +598,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { clones: { highlightActiveClones: true }, - async enhanceTitle ( - event: Event, - data: { - node: Fancytree.FancytreeNode; - noteId: string; - } - ) { - const node = data.node; - - if (!node.data.noteId) { - // if there's "non-note" node, then don't enhance - // this can happen for e.g. "Load error!" node - return; - } - - const note = await froca.getNote(node.data.noteId, true); - - if (!note) { - return; - } - - const activeNoteContext = appContext.tabManager.getActiveContext(); - - const $span = $(node.span); - - $span.find(".tree-item-button").remove(); - $span.find(".note-indicator-icon").remove(); - - const isHoistedNote = activeNoteContext && activeNoteContext.hoistedNoteId === note.noteId && note.noteId !== "root"; - - if (note.hasLabel("workspace") && !isHoistedNote) { - const $enterWorkspaceButton = $(``).on( - "click", - cancelClickPropagation - ); - - $span.append($enterWorkspaceButton); - } - - if (note.type === "search") { - const $refreshSearchButton = $(``).on( - "click", - cancelClickPropagation - ); - - $span.append($refreshSearchButton); - } - - // TODO: Deduplicate with server's notes.ts#getAndValidateParent - if (!["search", "launcher"].includes(note.type) - && !note.isOptions() - && !note.isLaunchBarConfig() - && !note.noteId.startsWith("_help") - ) { - const createChildEl = document.createElement("span"); - createChildEl.className = "tree-item-button tn-icon add-note-button bx bx-plus"; - createChildEl.title = t("note_tree.create-child-note"); - createChildEl.addEventListener("click", cancelClickPropagation); - node.span.append(createChildEl); - } - - if (isHoistedNote) { - const $unhoistButton = $(``).on("click", cancelClickPropagation); - - $span.append($unhoistButton); - } - - // Add clone indicator with tooltip if note has multiple parents - const parentNotes = note.getParentNotes(); - const realParents = parentNotes.filter( - (parent) => !["_share", "_lbBookmarks"].includes(parent.noteId) && parent.type !== "search" - ); - - if (realParents.length > 1) { - const parentTitles = realParents.map((p) => p.title).join(", "); - const tooltipText = realParents.length === 2 - ? t("note_tree.clone-indicator-tooltip-single", { parent: realParents[1].title }) - : t("note_tree.clone-indicator-tooltip", { count: realParents.length, parents: parentTitles }); - - const $cloneIndicator = $(``); - $cloneIndicator.attr("title", tooltipText); - $span.find(".fancytree-title").append($cloneIndicator); - } - - // Add shared indicator with tooltip if note is shared - if (note.isShared()) { - const shareId = note.getOwnedLabelValue("shareAlias") || note.noteId; - const shareUrl = `${location.origin}${location.pathname}share/${shareId}`; - const tooltipText = t("note_tree.shared-indicator-tooltip-with-url", { url: shareUrl }); - - const $sharedIndicator = $(``); - $sharedIndicator.attr("title", tooltipText); - $span.find(".fancytree-title").append($sharedIndicator); - } - }, + enhanceTitle: buildEnhanceTitle(), // this is done to automatically lazy load all expanded notes after tree load loadChildren: (event, data) => { data.node.visit((subNode) => { @@ -1881,3 +1787,99 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { return items; } } + +function buildEnhanceTitle() { + return async (event: Event, + data: { + node: Fancytree.FancytreeNode; + noteId: string; + }) => { + const node = data.node; + + if (!node.data.noteId) { + // if there's "non-note" node, then don't enhance + // this can happen for e.g. "Load error!" node + return; + } + + const note = await froca.getNote(node.data.noteId, true); + + if (!note) { + return; + } + + const activeNoteContext = appContext.tabManager.getActiveContext(); + + const $span = $(node.span); + + $span.find(".tree-item-button").remove(); + $span.find(".note-indicator-icon").remove(); + + const isHoistedNote = activeNoteContext && activeNoteContext.hoistedNoteId === note.noteId && note.noteId !== "root"; + + if (note.hasLabel("workspace") && !isHoistedNote) { + const $enterWorkspaceButton = $(``).on( + "click", + cancelClickPropagation + ); + + $span.append($enterWorkspaceButton); + } + + if (note.type === "search") { + const $refreshSearchButton = $(``).on( + "click", + cancelClickPropagation + ); + + $span.append($refreshSearchButton); + } + + // TODO: Deduplicate with server's notes.ts#getAndValidateParent + if (!["search", "launcher"].includes(note.type) + && !note.isOptions() + && !note.isLaunchBarConfig() + && !note.noteId.startsWith("_help") + ) { + const createChildEl = document.createElement("span"); + createChildEl.className = "tree-item-button tn-icon add-note-button bx bx-plus"; + createChildEl.title = t("note_tree.create-child-note"); + createChildEl.addEventListener("click", cancelClickPropagation); + node.span.append(createChildEl); + } + + if (isHoistedNote) { + const $unhoistButton = $(``).on("click", cancelClickPropagation); + + $span.append($unhoistButton); + } + + // Add clone indicator with tooltip if note has multiple parents + const parentNotes = note.getParentNotes(); + const realParents = parentNotes.filter( + (parent) => !["_share", "_lbBookmarks"].includes(parent.noteId) && parent.type !== "search" + ); + + if (realParents.length > 1) { + const parentTitles = realParents.map((p) => p.title).join(", "); + const tooltipText = realParents.length === 2 + ? t("note_tree.clone-indicator-tooltip-single", { parent: realParents[1].title }) + : t("note_tree.clone-indicator-tooltip", { count: realParents.length, parents: parentTitles }); + + const $cloneIndicator = $(``); + $cloneIndicator.attr("title", tooltipText); + $span.find(".fancytree-title").append($cloneIndicator); + } + + // Add shared indicator with tooltip if note is shared + if (note.isShared()) { + const shareId = note.getOwnedLabelValue("shareAlias") || note.noteId; + const shareUrl = `${location.origin}${location.pathname}share/${shareId}`; + const tooltipText = t("note_tree.shared-indicator-tooltip-with-url", { url: shareUrl }); + + const $sharedIndicator = $(``); + $sharedIndicator.attr("title", tooltipText); + $span.find(".fancytree-title").append($sharedIndicator); + } + }; +} From 0867b81c7ad492f6fe475a349f1853a52436e9aa Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 6 Jan 2026 01:13:31 +0200 Subject: [PATCH 3/6] feat(tree): use template for create child to improve performance --- apps/client/src/widgets/note_tree.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index 3e9481458..b0446fae9 100644 --- a/apps/client/src/widgets/note_tree.ts +++ b/apps/client/src/widgets/note_tree.ts @@ -1789,11 +1789,16 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { } function buildEnhanceTitle() { - return async (event: Event, + const createChildTemplate = document.createElement("span"); + createChildTemplate.className = "tree-item-button tn-icon add-note-button bx bx-plus"; + createChildTemplate.title = t("note_tree.create-child-note"); + createChildTemplate.addEventListener("click", cancelClickPropagation); + + return async function enhanceTitle(event: Event, data: { node: Fancytree.FancytreeNode; noteId: string; - }) => { + }) { const node = data.node; if (!node.data.noteId) { @@ -1841,11 +1846,7 @@ function buildEnhanceTitle() { && !note.isLaunchBarConfig() && !note.noteId.startsWith("_help") ) { - const createChildEl = document.createElement("span"); - createChildEl.className = "tree-item-button tn-icon add-note-button bx bx-plus"; - createChildEl.title = t("note_tree.create-child-note"); - createChildEl.addEventListener("click", cancelClickPropagation); - node.span.append(createChildEl); + node.span.append(createChildTemplate.cloneNode()); } if (isHoistedNote) { From d0cdcfc32c9cf03fec0e55f44dbfa73d97d3cf5e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 6 Jan 2026 01:21:43 +0200 Subject: [PATCH 4/6] refactor(tree): use loop for mini optimisation --- apps/client/src/widgets/note_tree.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index b0446fae9..c4d4ce0c2 100644 --- a/apps/client/src/widgets/note_tree.ts +++ b/apps/client/src/widgets/note_tree.ts @@ -1857,9 +1857,12 @@ function buildEnhanceTitle() { // Add clone indicator with tooltip if note has multiple parents const parentNotes = note.getParentNotes(); - const realParents = parentNotes.filter( - (parent) => !["_share", "_lbBookmarks"].includes(parent.noteId) && parent.type !== "search" - ); + const realParents: FNote[] = []; + for (const parent of parentNotes) { + if (parent.noteId !== "_share" && parent.noteId !== "_lbBookmarks" && parent.type !== "search") { + realParents.push(parent); + } + } if (realParents.length > 1) { const parentTitles = realParents.map((p) => p.title).join(", "); From dec4dafba61c084adbfcba012b2c38fede122049 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 6 Jan 2026 01:26:56 +0200 Subject: [PATCH 5/6] feat(tree): avoid async --- apps/client/src/widgets/note_tree.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index c4d4ce0c2..f815edfc9 100644 --- a/apps/client/src/widgets/note_tree.ts +++ b/apps/client/src/widgets/note_tree.ts @@ -1807,11 +1807,8 @@ function buildEnhanceTitle() { return; } - const note = await froca.getNote(node.data.noteId, true); - - if (!note) { - return; - } + const note = froca.getNoteFromCache(node.data.noteId); + if (!note) return; const activeNoteContext = appContext.tabManager.getActiveContext(); From aff4f7e01091281895ab7bdaa2f60ec8f907b5b0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 6 Jan 2026 01:34:02 +0200 Subject: [PATCH 6/6] feat(tree): disable animation for performance --- apps/client/src/widgets/note_tree.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index f815edfc9..8f4ead199 100644 --- a/apps/client/src/widgets/note_tree.ts +++ b/apps/client/src/widgets/note_tree.ts @@ -353,6 +353,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { this.$tree.fancytree({ titlesTabbable: true, keyboard: true, + toggleEffect: false, extensions: ["dnd5", "clones", "filter"], source: treeData, scrollOfs: {