diff --git a/src/ui.ts b/src/ui.ts index f1822e0..436626d 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -110,6 +110,51 @@ export function createSearchModal(): void { document.addEventListener("keydown", handleKeydown); const resultsContainer = document.getElementById("ss-results")!; + + resultsContainer.addEventListener("click", (e) => { + const pageLink = (e.target as HTMLElement).closest(".ss-breadcrumb-page-link") as HTMLAnchorElement | null; + if (pageLink) { + e.preventDefault(); + e.stopPropagation(); + const pageName = pageLink.getAttribute("data-page"); + if (pageName) { + if (e.shiftKey) { + logseq.Editor.getPage(pageName).then((page) => { + if (page?.id) logseq.Editor.openInRightSidebar(page.id); + }); + } else { + logseq.App.pushState("page", { name: pageName }); + hideModal(); + } + } + } + const blockRefLink = (e.target as HTMLElement).closest(".ss-breadcrumb-block-ref") as HTMLAnchorElement | null; + if (blockRefLink) { + e.preventDefault(); + e.stopPropagation(); + const blockId = blockRefLink.getAttribute("data-block-ref"); + if (blockId) { + if (e.shiftKey) { + logseq.Editor.openInRightSidebar(blockId); + } else { + logseq.Editor.getBlock(blockId).then(async (block) => { + if (block?.page?.id) { + const page = await logseq.Editor.getPage(block.page.id); + if (page?.name) { + logseq.Editor.scrollToBlockInPage(page.name, blockId); + } + } + }); + hideModal(); + } + } + } + const urlLink = (e.target as HTMLElement).closest(".ss-breadcrumb-url") as HTMLAnchorElement | null; + if (urlLink) { + e.stopPropagation(); + } + }); + document.addEventListener("keydown", (e) => { if (e.key === "Shift") resultsContainer.classList.add("shift-held"); }); @@ -336,8 +381,7 @@ async function fetchBreadcrumbs(blockId: string, pageName: string): Promise 50; - ancestors.unshift(truncated ? firstLine.slice(0, 50) + "..." : firstLine); + ancestors.unshift(firstLine); childUuid = parentUuid; } catch { break; @@ -466,7 +510,7 @@ function renderBlockElement(block: DisplayBlock, extraClass = ""): HTMLDivElemen : block.content; const breadcrumbHtml = block.breadcrumbs - .map((b) => `${escapeHtml(b)}`) + .map((b) => `${formatBreadcrumbHtml(b)}`) .join(''); item.innerHTML = ` @@ -485,6 +529,10 @@ function renderBlockElement(block: DisplayBlock, extraClass = ""): HTMLDivElemen }); item.addEventListener("click", async (e) => { + const target = e.target as HTMLElement; + if (target.closest(".ss-breadcrumb-page-link") || target.closest(".ss-breadcrumb-block-ref") || target.closest(".ss-breadcrumb-url")) { + return; + } if (e.shiftKey) { try { logseq.Editor.openInRightSidebar(block.blockId); @@ -676,6 +724,78 @@ function escapeHtml(text: string): string { return div.innerHTML; } +function escapeAttr(text: string): string { + return text.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); +} + +function formatBreadcrumbHtml(text: string): string { + const patterns: { regex: RegExp; type: string }[] = [ + { regex: /`([^`]+)`/, type: "code" }, + { regex: /\*\*([^*]+)\*\*/, type: "bold" }, + { regex: /\*([^*]+)\*/, type: "italic" }, + { regex: /~~([^~]+)~~/, type: "strikethrough" }, + { regex: /\^\^([^^]+)\^\^/, type: "highlight" }, + { regex: /#\[\[([^\]]+)\]\]/, type: "tag-bracket" }, + { regex: /#([a-zA-Z0-9_-][a-zA-Z0-9_/-]*)/, type: "tag" }, + { regex: /\[\[([^\]]+)\]\]/, type: "page-link" }, + { regex: /\[([^\]]+)\]\(\(\(([0-9a-f-]+)\)\)\)/, type: "block-ref-link" }, + { regex: /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/, type: "md-link" }, + { regex: /https?:\/\/[^\s<>)\]]+/, type: "bare-url" }, + ]; + + const parts: string[] = []; + let remaining = text; + + while (remaining.length > 0) { + let earliest: { index: number; length: number; html: string } | null = null; + + for (const p of patterns) { + const match = remaining.match(p.regex); + if (match && match.index !== undefined) { + let html = ""; + if (p.type === "code") { + html = `${escapeHtml(match[1])}`; + } else if (p.type === "bold") { + html = `${formatBreadcrumbHtml(match[1])}`; + } else if (p.type === "italic") { + html = `${formatBreadcrumbHtml(match[1])}`; + } else if (p.type === "strikethrough") { + html = `${formatBreadcrumbHtml(match[1])}`; + } else if (p.type === "highlight") { + html = `${formatBreadcrumbHtml(match[1])}`; + } else if (p.type === "tag-bracket" || p.type === "tag") { + html = `` + + `#${escapeHtml(match[1])}`; + } else if (p.type === "page-link") { + html = `` + + `[[${escapeHtml(match[1])}]]`; + } else if (p.type === "block-ref-link") { + html = `${escapeHtml(match[1])}`; + } else if (p.type === "md-link") { + html = `${escapeHtml(match[1])}`; + } else if (p.type === "bare-url") { + html = `${escapeHtml(match[0])}`; + } + const candidate = { index: match.index, length: match[0].length, html }; + if (!earliest || candidate.index < earliest.index) { + earliest = candidate; + } + } + } + + if (earliest) { + parts.push(escapeHtml(remaining.slice(0, earliest.index))); + parts.push(earliest.html); + remaining = remaining.slice(earliest.index + earliest.length); + } else { + parts.push(escapeHtml(remaining)); + break; + } + } + + return parts.join(""); +} + export function showModal(): void { if (evictTimer) { clearTimeout(evictTimer); diff --git a/styles/search-modal.css b/styles/search-modal.css index 8b4c1e4..d16a5cb 100644 --- a/styles/search-modal.css +++ b/styles/search-modal.css @@ -116,7 +116,7 @@ .ss-result-item:hover, .ss-result-item.active { - background: var(--ls-secondary-background-color, #f0f0f0); + background: var(--ls-tertiary-background-color, rgba(0, 0, 0, 0.05)); border-color: var(--ls-border-color, #e0e0e0); } @@ -151,6 +151,56 @@ opacity: 0.6; } +.ss-breadcrumb-code { + font-family: monospace; + font-size: 0.9em; +} + +.ss-breadcrumb-page-link { + color: inherit; + text-decoration: none; + cursor: alias; + border-radius: 3px; + padding: 0 2px; +} + +.ss-breadcrumb-page-link:hover { + background: var(--ls-tertiary-background-color, rgba(0, 0, 0, 0.05)); +} + +.ss-breadcrumb-page-link .ss-bracket { + opacity: 0.3; +} + +.ss-breadcrumb-highlight { + background: var(--ls-page-mark-bg-color, rgba(255, 255, 0, 0.3)); + color: inherit; +} + +.ss-breadcrumb-block-ref { + color: inherit; + text-decoration: underline dashed; + cursor: alias; + border-radius: 3px; + padding: 0 2px; +} + +.ss-breadcrumb-block-ref:hover { + background: var(--ls-tertiary-background-color, rgba(0, 0, 0, 0.05)); +} + +.ss-breadcrumb-url { + color: inherit; + text-decoration: underline; + cursor: alias; + border-radius: 3px; + padding: 0 2px; +} + +.ss-breadcrumb-url:hover { + background: var(--ls-tertiary-background-color, rgba(0, 0, 0, 0.05)); +} + .ss-result-content { font-size: 13px; line-height: 1.4;