feat(jump_to): don't show content context in results, go faster 🚄

This commit is contained in:
perf3ct
2025-08-20 19:46:49 +00:00
parent 843be0da22
commit e8f08bcdfe
5 changed files with 13 additions and 58 deletions

View File

@@ -30,8 +30,6 @@ export interface Suggestion {
notePathTitle?: string; notePathTitle?: string;
notePath?: string; notePath?: string;
highlightedNotePathTitle?: string; highlightedNotePathTitle?: string;
contentSnippet?: string;
highlightedContentSnippet?: string;
attributeSnippet?: string; attributeSnippet?: string;
highlightedAttributeSnippet?: string; highlightedAttributeSnippet?: string;
action?: string | "create-note" | "search-notes" | "external-link" | "command"; action?: string | "create-note" | "search-notes" | "external-link" | "command";
@@ -329,21 +327,16 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
return html; return html;
} }
// For note suggestions, match Quick Search structure exactly // For note suggestions, match Quick Search structure
// Title row with icon // Title row with icon
let html = `<div style="display: flex; align-items: center; gap: 6px;">`; let html = `<div style="display: flex; align-items: center; gap: 6px;">`;
html += `<span class="${suggestion.icon ?? "bx bx-note"}" style="flex-shrink: 0;"></span>`; html += `<span class="${suggestion.icon ?? "bx bx-note"}" style="flex-shrink: 0;"></span>`;
html += `<span class="search-result-title" style="flex: 1;">${suggestion.highlightedNotePathTitle || ''}</span>`; html += `<span class="search-result-title" style="flex: 1;">${suggestion.highlightedNotePathTitle || ''}</span>`;
html += `</div>`; html += `</div>`;
// Add attribute snippet if available // Add attribute snippet if available (inline display)
if (suggestion.highlightedAttributeSnippet && suggestion.highlightedAttributeSnippet.trim()) { if (suggestion.highlightedAttributeSnippet && suggestion.highlightedAttributeSnippet.trim()) {
html += `<div class="search-result-attributes" style="margin-left: 20px; margin-top: 2px;">${suggestion.highlightedAttributeSnippet}</div>`; html += `<div class="search-result-attributes" style="margin-left: 20px; margin-top: 2px; color: var(--muted-text-color); font-size: 0.9em;">${suggestion.highlightedAttributeSnippet}</div>`;
}
// Add content snippet if available
if (suggestion.highlightedContentSnippet && suggestion.highlightedContentSnippet.trim()) {
html += `<div class="search-result-content" style="margin-left: 20px; margin-top: 2px;">${suggestion.highlightedContentSnippet}</div>`;
} }
return html; return html;

View File

@@ -6,10 +6,10 @@
* with different styling and layout requirements. * with different styling and layout requirements.
* *
* SECURITY NOTE: HTML Snippet Handling * SECURITY NOTE: HTML Snippet Handling
* The highlighted snippet fields (highlightedContentSnippet, highlightedAttributeSnippet) contain * The highlighted snippet fields (highlightedAttributeSnippet) contain
* pre-sanitized HTML from the server. The server-side processing: * pre-sanitized HTML from the server. The server-side processing:
* 1. Escapes all HTML using the escape-html library * 1. Escapes all HTML using the escape-html library
* 2. Adds safe HTML tags for display: <b> for search term highlighting, <br> for line breaks * 2. Adds safe HTML tags for display: <b> for search term highlighting
* 3. See apps/server/src/services/search/services/search.ts for implementation * 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. * This means the HTML snippets can be safely inserted without additional escaping on the client side.
@@ -41,7 +41,7 @@ export function createSearchResultHtml(result: Suggestion): string {
return html; return html;
} }
// Default: render as note result with snippets // Default: render as note result
// Wrap everything in a flex column container // Wrap everything in a flex column container
let itemHtml = `<div style="display: flex; flex-direction: column; gap: 2px;">`; let itemHtml = `<div style="display: flex; flex-direction: column; gap: 2px;">`;
@@ -51,14 +51,9 @@ export function createSearchResultHtml(result: Suggestion): string {
itemHtml += `<span class="search-result-title" style="flex: 1;">${result.highlightedNotePathTitle || result.notePathTitle || ''}</span>`; itemHtml += `<span class="search-result-title" style="flex: 1;">${result.highlightedNotePathTitle || result.notePathTitle || ''}</span>`;
itemHtml += `</div>`; itemHtml += `</div>`;
// Add attribute snippet if available // Add attribute snippet if available (inline display)
if (result.highlightedAttributeSnippet && result.highlightedAttributeSnippet.trim()) { if (result.highlightedAttributeSnippet && result.highlightedAttributeSnippet.trim()) {
itemHtml += `<div class="search-result-attributes" style="margin-left: 20px;">${result.highlightedAttributeSnippet}</div>`; itemHtml += `<div class="search-result-attributes" style="margin-left: 20px; color: var(--muted-text-color); font-size: 0.9em;">${result.highlightedAttributeSnippet}</div>`;
}
// Add content snippet if available
if (result.highlightedContentSnippet && result.highlightedContentSnippet.trim()) {
itemHtml += `<div class="search-result-content" style="margin-left: 20px;">${result.highlightedContentSnippet}</div>`;
} }
itemHtml += `</div>`; itemHtml += `</div>`;

View File

@@ -92,8 +92,6 @@ interface QuickSearchResponse {
noteTitle: string; noteTitle: string;
notePathTitle: string; notePathTitle: string;
highlightedNotePathTitle: string; highlightedNotePathTitle: string;
contentSnippet?: string;
highlightedContentSnippet?: string;
attributeSnippet?: string; attributeSnippet?: string;
highlightedAttributeSnippet?: string; highlightedAttributeSnippet?: string;
icon: string; icon: string;

View File

@@ -33,8 +33,6 @@ class SearchResult {
score: number; score: number;
notePathTitle: string; notePathTitle: string;
highlightedNotePathTitle?: string; highlightedNotePathTitle?: string;
contentSnippet?: string;
highlightedContentSnippet?: string;
attributeSnippet?: string; attributeSnippet?: string;
highlightedAttributeSnippet?: string; highlightedAttributeSnippet?: string;
private fuzzyScore: number; // Track fuzzy score separately private fuzzyScore: number; // Track fuzzy score separately

View File

@@ -607,7 +607,7 @@ function extractAttributeSnippet(noteId: string, searchTokens: string[], maxLeng
return ""; return "";
} }
// Limit to 4 lines maximum, similar to content snippet logic // Display attributes inline, separated by spaces
const lines: string[] = []; const lines: string[] = [];
for (const attr of matchingAttributes.slice(0, 4)) { for (const attr of matchingAttributes.slice(0, 4)) {
let line = ""; let line = "";
@@ -625,7 +625,7 @@ function extractAttributeSnippet(noteId: string, searchTokens: string[], maxLeng
} }
} }
let snippet = lines.join('\n'); let snippet = lines.join(' '); // Join with spaces instead of newlines
// Apply length limit while preserving line structure // Apply length limit while preserving line structure
if (snippet.length > maxLength) { if (snippet.length > maxLength) {
@@ -665,9 +665,8 @@ function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) {
const trimmed = allSearchResults.slice(0, 200); const trimmed = allSearchResults.slice(0, 200);
// Extract content and attribute snippets // Extract attribute snippets only (content snippets removed for performance)
for (const result of trimmed) { for (const result of trimmed) {
result.contentSnippet = extractContentSnippet(result.noteId, searchContext.highlightedTokens);
result.attributeSnippet = extractAttributeSnippet(result.noteId, searchContext.highlightedTokens); result.attributeSnippet = extractAttributeSnippet(result.noteId, searchContext.highlightedTokens);
} }
@@ -680,8 +679,6 @@ function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) {
noteTitle: title, noteTitle: title,
notePathTitle: result.notePathTitle, notePathTitle: result.notePathTitle,
highlightedNotePathTitle: result.highlightedNotePathTitle, highlightedNotePathTitle: result.highlightedNotePathTitle,
contentSnippet: result.contentSnippet,
highlightedContentSnippet: result.highlightedContentSnippet,
attributeSnippet: result.attributeSnippet, attributeSnippet: result.attributeSnippet,
highlightedAttributeSnippet: result.highlightedAttributeSnippet, highlightedAttributeSnippet: result.highlightedAttributeSnippet,
icon: icon ?? "bx bx-note" icon: icon ?? "bx bx-note"
@@ -707,17 +704,9 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens
for (const result of searchResults) { for (const result of searchResults) {
result.highlightedNotePathTitle = result.notePathTitle.replace(/[<{}]/g, ""); result.highlightedNotePathTitle = result.notePathTitle.replace(/[<{}]/g, "");
// Initialize highlighted content snippet
if (result.contentSnippet) {
// Escape HTML but preserve newlines for later conversion to <br>
result.highlightedContentSnippet = escapeHtml(result.contentSnippet);
// Remove any stray < { } that might interfere with our highlighting markers
result.highlightedContentSnippet = result.highlightedContentSnippet.replace(/[<{}]/g, "");
}
// Initialize highlighted attribute snippet // Initialize highlighted attribute snippet
if (result.attributeSnippet) { if (result.attributeSnippet) {
// Escape HTML but preserve newlines for later conversion to <br> // Escape HTML
result.highlightedAttributeSnippet = escapeHtml(result.attributeSnippet); result.highlightedAttributeSnippet = escapeHtml(result.attributeSnippet);
// Remove any stray < { } that might interfere with our highlighting markers // Remove any stray < { } that might interfere with our highlighting markers
result.highlightedAttributeSnippet = result.highlightedAttributeSnippet.replace(/[<{}]/g, ""); result.highlightedAttributeSnippet = result.highlightedAttributeSnippet.replace(/[<{}]/g, "");
@@ -749,16 +738,6 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens
} }
} }
// Highlight in content snippet
if (result.highlightedContentSnippet) {
const contentRegex = new RegExp(escapeRegExp(token), "gi");
while ((match = contentRegex.exec(normalizeString(result.highlightedContentSnippet))) !== null) {
result.highlightedContentSnippet = wrapText(result.highlightedContentSnippet, match.index, token.length, "{", "}");
// 2 characters are added, so we need to adjust the index
contentRegex.lastIndex += 2;
}
}
// Highlight in attribute snippet // Highlight in attribute snippet
if (result.highlightedAttributeSnippet) { if (result.highlightedAttributeSnippet) {
const attributeRegex = new RegExp(escapeRegExp(token), "gi"); const attributeRegex = new RegExp(escapeRegExp(token), "gi");
@@ -776,18 +755,10 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens
result.highlightedNotePathTitle = result.highlightedNotePathTitle.replace(/{/g, "<b>").replace(/}/g, "</b>"); result.highlightedNotePathTitle = result.highlightedNotePathTitle.replace(/{/g, "<b>").replace(/}/g, "</b>");
} }
if (result.highlightedContentSnippet) {
// Replace highlighting markers with HTML tags
result.highlightedContentSnippet = result.highlightedContentSnippet.replace(/{/g, "<b>").replace(/}/g, "</b>");
// Convert newlines to <br> tags for HTML display
result.highlightedContentSnippet = result.highlightedContentSnippet.replace(/\n/g, "<br>");
}
if (result.highlightedAttributeSnippet) { if (result.highlightedAttributeSnippet) {
// Replace highlighting markers with HTML tags // Replace highlighting markers with HTML tags
result.highlightedAttributeSnippet = result.highlightedAttributeSnippet.replace(/{/g, "<b>").replace(/}/g, "</b>"); result.highlightedAttributeSnippet = result.highlightedAttributeSnippet.replace(/{/g, "<b>").replace(/}/g, "</b>");
// Convert newlines to <br> tags for HTML display // Keep inline display - no conversion of newlines needed
result.highlightedAttributeSnippet = result.highlightedAttributeSnippet.replace(/\n/g, "<br>");
} }
} }
} }