From 2693b18ee61d409629e01592338cf6b6e4f5b147 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 16 Dec 2025 13:31:17 +0200 Subject: [PATCH] refactor(breadcrumb): use new component for rendering note links --- apps/client/src/services/link.ts | 37 +++++++++-------- apps/client/src/widgets/layout/Breadcrumb.tsx | 6 +-- apps/client/src/widgets/react/NoteLink.tsx | 41 ++++++++++++++++++- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/apps/client/src/services/link.ts b/apps/client/src/services/link.ts index 4ff3a39cf..03595920b 100644 --- a/apps/client/src/services/link.ts +++ b/apps/client/src/services/link.ts @@ -1,10 +1,11 @@ -import treeService from "./tree.js"; -import linkContextMenuService from "../menus/link_context_menu.js"; -import appContext, { type NoteCommandData } from "../components/app_context.js"; -import froca from "./froca.js"; -import utils from "./utils.js"; import { ALLOWED_PROTOCOLS } from "@triliumnext/commons"; + +import appContext, { type NoteCommandData } from "../components/app_context.js"; import { openInCurrentNoteContext } from "../components/note_context.js"; +import linkContextMenuService from "../menus/link_context_menu.js"; +import froca from "./froca.js"; +import treeService from "./tree.js"; +import utils from "./utils.js"; function getNotePathFromUrl(url: string) { const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url); @@ -122,7 +123,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio const $container = $(""); if (showNoteIcon) { - let icon = await getLinkIcon(noteId, viewMode); + const icon = await getLinkIcon(noteId, viewMode); if (icon) { $container.append($("").addClass(`bx ${icon}`)).append(" "); @@ -131,7 +132,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio const hash = calculateHash({ notePath, - viewScope: viewScope + viewScope }); const $noteLink = $("", { @@ -171,11 +172,11 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio return $container; } -function calculateHash({ notePath, ntxId, hoistedNoteId, viewScope = {} }: NoteCommandData) { +export function calculateHash({ notePath, ntxId, hoistedNoteId, viewScope = {} }: NoteCommandData) { notePath = notePath || ""; const params = [ - ntxId ? { ntxId: ntxId } : null, - hoistedNoteId && hoistedNoteId !== "root" ? { hoistedNoteId: hoistedNoteId } : null, + ntxId ? { ntxId } : null, + hoistedNoteId && hoistedNoteId !== "root" ? { hoistedNoteId } : null, viewScope.viewMode && viewScope.viewMode !== "default" ? { viewMode: viewScope.viewMode } : null, viewScope.attachmentId ? { attachmentId: viewScope.attachmentId } : null ].filter((p) => !!p); @@ -219,7 +220,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) { } const hash = url.substr(hashIdx + 1); // strip also the initial '#' - let [notePath, paramString] = hash.split("?"); + const [notePath, paramString] = hash.split("?"); const viewScope: ViewScope = { viewMode: "default" @@ -252,7 +253,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) { } if (searchString) { - return { searchString } + return { searchString }; } if (!notePath.match(/^[_a-z0-9]{4,}(\/[_a-z0-9]{4,})*$/i)) { @@ -334,7 +335,7 @@ export function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDo window.open(hrefLink, "_blank"); } else { // Enable protocols supported by CKEditor 5 to be clickable. - if (ALLOWED_PROTOCOLS.some((protocol) => hrefLink.toLowerCase().startsWith(protocol + ":"))) { + if (ALLOWED_PROTOCOLS.some((protocol) => hrefLink.toLowerCase().startsWith(`${protocol }:`))) { if ( utils.isElectron()) { const electron = utils.dynamicRequire("electron"); electron.shell.openExternal(hrefLink); @@ -395,7 +396,7 @@ async function loadReferenceLinkTitle($el: JQuery, href: string | n href = href || $link.attr("href"); if (!href) { - console.warn("Empty URL for parsing: " + $el[0].outerHTML); + console.warn(`Empty URL for parsing: ${$el[0].outerHTML}`); return; } @@ -438,9 +439,9 @@ async function getReferenceLinkTitle(href: string) { const attachment = await note.getAttachmentById(viewScope.attachmentId); return attachment ? attachment.title : "[missing attachment]"; - } else { - return note.title; } + return note.title; + } function getReferenceLinkTitleSync(href: string) { @@ -462,9 +463,9 @@ function getReferenceLinkTitleSync(href: string) { const attachment = note.attachments.find((att) => att.attachmentId === viewScope.attachmentId); return attachment ? attachment.title : "[missing attachment]"; - } else { - return note.title; } + return note.title; + } if (glob.device !== "print") { diff --git a/apps/client/src/widgets/layout/Breadcrumb.tsx b/apps/client/src/widgets/layout/Breadcrumb.tsx index bc445068a..780ae0d4b 100644 --- a/apps/client/src/widgets/layout/Breadcrumb.tsx +++ b/apps/client/src/widgets/layout/Breadcrumb.tsx @@ -25,7 +25,7 @@ import Dropdown from "../react/Dropdown"; import { FormListItem } from "../react/FormList"; import { useActiveNoteContext, useChildNotes, useNote, useNoteIcon, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useStaticTooltip } from "../react/hooks"; import Icon from "../react/Icon"; -import NoteLink from "../react/NoteLink"; +import { NewNoteLink } from "../react/NoteLink"; import { ParentComponent } from "../react/react_utils"; const COLLAPSE_THRESHOLD = 5; @@ -114,7 +114,7 @@ function BreadcrumbHoistedNoteRoot({ noteId }: { noteId: string }) { "color": getReadableTextColor(workspaceColor) } : undefined} /> - ; } - return ; } + +interface NewNoteLinkProps extends Pick, "onContextMenu"> { + notePath: string; + viewScope?: ViewScope; + noContextMenu?: boolean; + showNoteIcon?: boolean; + noPreview?: boolean; +} + +export function NewNoteLink({ notePath, viewScope, noContextMenu, showNoteIcon, noPreview, ...linkProps }: NewNoteLinkProps) { + const noteId = notePath.split("/").at(-1); + const note = useNote(noteId); + const title = useNoteProperty(note, "title"); + const icon = useNoteIcon(showNoteIcon ? note : null); + + return ( + + + {icon && } + + + {title} + + + + ); +}