diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index a1f477993..24e02286c 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; @@ -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: { @@ -598,102 +599,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 $createChildNoteButton = $(``).on( - "click", - cancelClickPropagation - ); - - $span.append($createChildNoteButton); - } - - 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) => { @@ -1886,3 +1792,100 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { return items; } } + +function buildEnhanceTitle() { + 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) { + // if there's "non-note" node, then don't enhance + // this can happen for e.g. "Load error!" node + return; + } + + const note = froca.getNoteFromCache(node.data.noteId); + 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") + ) { + node.span.append(createChildTemplate.cloneNode()); + } + + 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: 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(", "); + 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); + } + }; +}