diff --git a/apps/client/src/services/note_autocomplete.ts b/apps/client/src/services/note_autocomplete.ts index 1138b191e7..917e1ce893 100644 --- a/apps/client/src/services/note_autocomplete.ts +++ b/apps/client/src/services/note_autocomplete.ts @@ -30,6 +30,8 @@ export interface Suggestion { notePathTitle?: string; notePath?: string; highlightedNotePathTitle?: string; + attributeSnippet?: string; + highlightedAttributeSnippet?: string; action?: string | "create-note" | "search-notes" | "external-link" | "command"; parentNoteId?: string; icon?: string; @@ -308,11 +310,12 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { displayKey: "notePathTitle", templates: { suggestion: (suggestion) => { + // Handle different suggestion types if (suggestion.action === "command") { let html = `
`; html += ``; html += `
`; - html += `
${suggestion.highlightedNotePathTitle}
`; + html += `
${suggestion.highlightedNotePathTitle || suggestion.noteTitle || ''}
`; if (suggestion.commandDescription) { html += `
${suggestion.commandDescription}
`; } @@ -323,7 +326,20 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { html += '
'; return html; } - return ` ${suggestion.highlightedNotePathTitle}`; + + // For note suggestions, match Quick Search structure + // Title row with icon + let html = `
`; + html += ``; + html += `${suggestion.highlightedNotePathTitle || ''}`; + html += `
`; + + // Add attribute snippet if available (inline display) + if (suggestion.highlightedAttributeSnippet && suggestion.highlightedAttributeSnippet.trim()) { + html += `
${suggestion.highlightedAttributeSnippet}
`; + } + + return html; } }, // we can't cache identical searches because notes can be created / renamed, new recent notes can be added diff --git a/apps/client/src/services/quick_search_renderer.ts b/apps/client/src/services/quick_search_renderer.ts new file mode 100644 index 0000000000..5fd061b2fb --- /dev/null +++ b/apps/client/src/services/quick_search_renderer.ts @@ -0,0 +1,62 @@ +/** + * Quick Search specific result renderer + * + * This module provides HTML rendering functionality specifically for the Quick Search widget. + * The Jump To dialog (note_autocomplete) intentionally has its own inline rendering logic + * with different styling and layout requirements. + * + * SECURITY NOTE: HTML Snippet Handling + * The highlighted snippet fields (highlightedAttributeSnippet) contain + * pre-sanitized HTML from the server. The server-side processing: + * 1. Escapes all HTML using the escape-html library + * 2. Adds safe HTML tags for display: for search term highlighting + * 3. See apps/server/src/services/search/services/search.ts for implementation + * + * This means the HTML snippets can be safely inserted without additional escaping on the client side. + */ + +import type { Suggestion } from "./note_autocomplete.js"; + +/** + * Creates HTML for a Quick Search result item + * + * @param result - The search result item to render + * @returns HTML string formatted for Quick Search widget display + */ +export function createSearchResultHtml(result: Suggestion): string { + // Handle command action + if (result.action === "command") { + let html = `
`; + html += ``; + html += `
`; + html += `
${result.highlightedNotePathTitle || ''}
`; + if (result.commandDescription) { + html += `
${result.commandDescription}
`; + } + html += `
`; + if (result.commandShortcut) { + html += `${result.commandShortcut}`; + } + html += '
'; + return html; + } + + // Default: render as note result + // Wrap everything in a flex column container + let itemHtml = `
`; + + // Title row with icon + itemHtml += `
`; + itemHtml += ``; + itemHtml += `${result.highlightedNotePathTitle || result.notePathTitle || ''}`; + itemHtml += `
`; + + // Add attribute snippet if available (inline display) + if (result.highlightedAttributeSnippet && result.highlightedAttributeSnippet.trim()) { + itemHtml += `
${result.highlightedAttributeSnippet}
`; + } + + itemHtml += `
`; + + return itemHtml; +} \ No newline at end of file diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index c5b24f88e8..ca0b657b04 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -840,8 +840,25 @@ table.promoted-attributes-in-tooltip th { .aa-dropdown-menu .aa-suggestion { cursor: pointer; - padding: 5px; + padding: 12px 16px; margin: 0; + line-height: 1.4; + position: relative; + white-space: normal; +} + +/* Add separator between Jump To suggestions like Quick Search */ +.jump-to-note-results .aa-suggestion:not(:last-child)::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 80%; + height: 2px; + background: var(--main-border-color); + border-radius: 1px; + opacity: 0.4; } .aa-dropdown-menu .aa-suggestion p { @@ -1786,7 +1803,7 @@ textarea { } .jump-to-note-results .aa-suggestions { - padding: 1rem; + padding: 0; } /* Command palette styling */ @@ -2260,13 +2277,43 @@ footer.webview-footer button { padding: 1px 10px 1px 10px; } -/* Search result highlighting */ +/* Search result highlighting - applies to both Quick Search and Jump To */ .search-result-title b, -.search-result-content b { +.search-result-content b, +.search-result-attributes b, +.quick-search .search-result-title b, +.quick-search .search-result-content b, +.quick-search .search-result-attributes b { font-weight: 900; color: var(--admonition-warning-accent-color); } +/* Quick Search specific snippet styling */ +.quick-search .search-result-content { + font-size: 0.85em; + color: var(--main-text-color); + opacity: 0.7; +} + +.quick-search .search-result-attributes { + font-size: 0.75em; + color: var(--muted-text-color); + opacity: 0.5; +} + +/* Jump To (autocomplete) specific snippet styling */ +.aa-dropdown-menu .search-result-content { + font-size: 0.82em; + color: var(--main-text-color); + opacity: 0.6; +} + +.aa-dropdown-menu .search-result-attributes { + font-size: 0.75em; + color: var(--muted-text-color); + opacity: 0.5; +} + /* Customized icons */ .bx-tn-toc::before { diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index c992f7ecbd..02551ce662 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -530,17 +530,16 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after { } /* List item */ -.jump-to-note-dialog .aa-suggestions div, -.note-detail-empty .aa-suggestions div { +.jump-to-note-dialog .aa-suggestions .aa-suggestion, +.note-detail-empty .aa-suggestions .aa-suggestion { border-radius: 6px; - padding: 6px 12px; color: var(--menu-text-color); cursor: default; } /* Selected list item */ -.jump-to-note-dialog .aa-suggestions div.aa-cursor, -.note-detail-empty .aa-suggestions div.aa-cursor { +.jump-to-note-dialog .aa-suggestions .aa-suggestion.aa-cursor, +.note-detail-empty .aa-suggestions .aa-suggestion.aa-cursor { background: var(--hover-item-background-color); color: var(--hover-item-text-color); } \ No newline at end of file diff --git a/apps/client/src/widgets/quick_search.ts b/apps/client/src/widgets/quick_search.ts index 2449d4bc5f..fdc8314027 100644 --- a/apps/client/src/widgets/quick_search.ts +++ b/apps/client/src/widgets/quick_search.ts @@ -7,6 +7,7 @@ import appContext from "../components/app_context.js"; import shortcutService from "../services/shortcuts.js"; import { t } from "../services/i18n.js"; import { Dropdown, Tooltip } from "bootstrap"; +import { createSearchResultHtml } from "../services/quick_search_renderer.js"; const TPL = /*html*/`