mirror of
https://github.com/zadam/trilium.git
synced 2025-11-03 20:06:08 +01:00
feat(jump_to): don't show content context in results, go faster 🚄
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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>`;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user