Merge branch 'features/enhancements' into features/graph
This commit is contained in:
commit
70a293e3fd
|
|
@ -273,9 +273,10 @@ def search_notes(notes_dir: str, query: str) -> List[Dict]:
|
|||
"""
|
||||
Full-text search through note contents only.
|
||||
Does NOT search in file names, folder names, or paths - only note content.
|
||||
Uses character-based context extraction with highlighted matches.
|
||||
"""
|
||||
from html import escape
|
||||
results = []
|
||||
query_lower = query.lower()
|
||||
notes_path = Path(notes_dir)
|
||||
|
||||
for md_file in notes_path.rglob("*.md"):
|
||||
|
|
@ -283,29 +284,51 @@ def search_notes(notes_dir: str, query: str) -> List[Dict]:
|
|||
with open(md_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Only search in note content (not file name, folder name, or path)
|
||||
if query_lower in content.lower():
|
||||
# Find context around match
|
||||
lines = content.split('\n')
|
||||
# Find all matches using regex (case-insensitive)
|
||||
matches = list(re.finditer(re.escape(query), content, re.IGNORECASE))
|
||||
|
||||
if matches:
|
||||
matched_lines = []
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if query_lower in line.lower():
|
||||
# Get surrounding context
|
||||
start = max(0, i - 1)
|
||||
end = min(len(lines), i + 2)
|
||||
context = '\n'.join(lines[start:end])
|
||||
matched_lines.append({
|
||||
"line_number": i + 1,
|
||||
"context": context[:200] # Limit context length
|
||||
})
|
||||
for match in matches[:3]: # Limit to 3 matches per file
|
||||
start_index = match.start()
|
||||
end_index = match.end()
|
||||
matched_text = match.group() # Preserve original case
|
||||
|
||||
# Create slice window: ±15 characters around match
|
||||
context_start = max(0, start_index - 15)
|
||||
context_end = min(len(content), end_index + 15)
|
||||
|
||||
# Extract and clean parts (newlines → spaces)
|
||||
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', ' '))
|
||||
|
||||
# Build snippet with <mark> highlight (styled via CSS)
|
||||
snippet = f'{before}<mark class="search-highlight">{matched_clean}</mark>{after}'
|
||||
|
||||
# Add ellipsis if truncated at start
|
||||
if context_start > 0:
|
||||
snippet = '...' + snippet
|
||||
|
||||
# Add ellipsis if truncated at end
|
||||
if context_end < len(content):
|
||||
snippet = snippet + '...'
|
||||
|
||||
# Calculate line number by counting newlines up to match start
|
||||
line_number = content.count('\n', 0, start_index) + 1
|
||||
|
||||
matched_lines.append({
|
||||
"line_number": line_number,
|
||||
"context": snippet
|
||||
})
|
||||
|
||||
relative_path = md_file.relative_to(notes_path)
|
||||
results.append({
|
||||
"name": md_file.stem,
|
||||
"path": str(relative_path.as_posix()),
|
||||
"folder": str(relative_path.parent.as_posix()) if str(relative_path.parent) != "." else "",
|
||||
"matches": matched_lines[:3] # Limit to 3 matches per file
|
||||
"matches": matched_lines
|
||||
})
|
||||
except Exception:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -1915,18 +1915,10 @@ function noteApp() {
|
|||
mark.className = 'search-highlight';
|
||||
mark.setAttribute('data-match-index', matchIndex);
|
||||
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) {
|
||||
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)';
|
||||
}
|
||||
|
||||
fragment.appendChild(mark);
|
||||
|
|
@ -1981,17 +1973,9 @@ function noteApp() {
|
|||
const allMatches = preview.querySelectorAll('mark.search-highlight');
|
||||
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) => {
|
||||
if (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');
|
||||
}
|
||||
mark.classList.toggle('active-match', i === index);
|
||||
});
|
||||
|
||||
// Scroll to the match
|
||||
|
|
|
|||
|
|
@ -293,6 +293,19 @@
|
|||
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 {
|
||||
font-size: 0.8125rem;
|
||||
|
|
@ -992,9 +1005,10 @@
|
|||
:style="currentNote === note.path ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'"
|
||||
onmouseover="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='transparent'"
|
||||
:title="note.path"
|
||||
>
|
||||
<div class="font-medium truncate" x-text="note.name"></div>
|
||||
<div class="text-xs truncate" style="color: var(--text-tertiary);" x-text="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>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue