reduce context search results; highlight matched text in context
This commit is contained in:
parent
1abd8e9d18
commit
2a69426273
|
|
@ -273,8 +273,9 @@ def search_notes(notes_dir: str, query: str) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Full-text search through note contents only.
|
Full-text search through note contents only.
|
||||||
Does NOT search in file names, folder names, or paths - only note content.
|
Does NOT search in file names, folder names, or paths - only note content.
|
||||||
Uses character-based context extraction for better snippet quality.
|
Uses character-based context extraction with highlighted matches.
|
||||||
"""
|
"""
|
||||||
|
from html import escape
|
||||||
results = []
|
results = []
|
||||||
notes_path = Path(notes_dir)
|
notes_path = Path(notes_dir)
|
||||||
|
|
||||||
|
|
@ -292,16 +293,19 @@ def search_notes(notes_dir: str, query: str) -> List[Dict]:
|
||||||
for match in matches[:3]: # Limit to 3 matches per file
|
for match in matches[:3]: # Limit to 3 matches per file
|
||||||
start_index = match.start()
|
start_index = match.start()
|
||||||
end_index = match.end()
|
end_index = match.end()
|
||||||
|
matched_text = match.group() # Preserve original case
|
||||||
|
|
||||||
# Create slice window: ±30 characters around match
|
# Create slice window: ±15 characters around match
|
||||||
context_start = max(0, start_index - 30)
|
context_start = max(0, start_index - 15)
|
||||||
context_end = min(len(content), end_index + 30)
|
context_end = min(len(content), end_index + 15)
|
||||||
|
|
||||||
# Extract snippet
|
# Extract and clean parts (newlines → spaces)
|
||||||
snippet = content[context_start:context_end]
|
before = escape(content[context_start:start_index].replace('\n', ' '))
|
||||||
|
after = escape(content[end_index:context_end].replace('\n', ' '))
|
||||||
|
matched_clean = escape(matched_text.replace('\n', ' '))
|
||||||
|
|
||||||
# Replace all newlines with spaces to ensure UI doesn't break
|
# Build snippet with <mark> highlight (styled via CSS)
|
||||||
snippet = snippet.replace('\n', ' ')
|
snippet = f'{before}<mark class="search-highlight">{matched_clean}</mark>{after}'
|
||||||
|
|
||||||
# Add ellipsis if truncated at start
|
# Add ellipsis if truncated at start
|
||||||
if context_start > 0:
|
if context_start > 0:
|
||||||
|
|
|
||||||
|
|
@ -1895,18 +1895,10 @@ function noteApp() {
|
||||||
mark.className = 'search-highlight';
|
mark.className = 'search-highlight';
|
||||||
mark.setAttribute('data-match-index', matchIndex);
|
mark.setAttribute('data-match-index', matchIndex);
|
||||||
mark.textContent = text.substring(index, index + searchTerm.length);
|
mark.textContent = text.substring(index, index + searchTerm.length);
|
||||||
mark.style.padding = '2px 4px';
|
|
||||||
mark.style.borderRadius = '3px';
|
|
||||||
mark.style.transition = 'all 0.2s';
|
|
||||||
|
|
||||||
// Style first match as active, others as inactive
|
// First match is active (styled via CSS)
|
||||||
if (matchIndex === 0) {
|
if (matchIndex === 0) {
|
||||||
mark.style.backgroundColor = 'var(--accent-primary)';
|
|
||||||
mark.style.color = 'white';
|
|
||||||
mark.classList.add('active-match');
|
mark.classList.add('active-match');
|
||||||
} else {
|
|
||||||
mark.style.backgroundColor = 'rgba(255, 193, 7, 0.4)';
|
|
||||||
mark.style.color = 'var(--text-primary)';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment.appendChild(mark);
|
fragment.appendChild(mark);
|
||||||
|
|
@ -1961,17 +1953,9 @@ function noteApp() {
|
||||||
const allMatches = preview.querySelectorAll('mark.search-highlight');
|
const allMatches = preview.querySelectorAll('mark.search-highlight');
|
||||||
if (index < 0 || index >= allMatches.length) return;
|
if (index < 0 || index >= allMatches.length) return;
|
||||||
|
|
||||||
// Update styling - make current match prominent
|
// Update styling - make current match prominent (via CSS class)
|
||||||
allMatches.forEach((mark, i) => {
|
allMatches.forEach((mark, i) => {
|
||||||
if (i === index) {
|
mark.classList.toggle('active-match', i === index);
|
||||||
mark.style.backgroundColor = 'var(--accent-primary)';
|
|
||||||
mark.style.color = 'white';
|
|
||||||
mark.classList.add('active-match');
|
|
||||||
} else {
|
|
||||||
mark.style.backgroundColor = 'rgba(255, 193, 7, 0.4)';
|
|
||||||
mark.style.color = 'var(--text-primary)';
|
|
||||||
mark.classList.remove('active-match');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Scroll to the match
|
// Scroll to the match
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,19 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search highlight base style (used in note preview and search results) */
|
||||||
|
.search-highlight {
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: rgba(255, 193, 7, 0.4);
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.search-highlight.active-match {
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
/* Note Properties Panel */
|
/* Note Properties Panel */
|
||||||
.note-properties {
|
.note-properties {
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
|
|
@ -956,7 +969,7 @@
|
||||||
:title="note.path"
|
:title="note.path"
|
||||||
>
|
>
|
||||||
<div class="font-medium truncate" x-text="note.name"></div>
|
<div class="font-medium truncate" x-text="note.name"></div>
|
||||||
<div class="text-xs truncate" style="color: var(--text-tertiary);" x-text="(note.matches && note.matches.length > 0) ? note.matches[0].context : (note.folder || 'Root')"></div>
|
<div class="text-xs truncate" style="color: var(--text-tertiary);" x-html="(note.matches && note.matches.length > 0) ? note.matches[0].context : (note.folder || 'Root')"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue