docs: remove deprecated integration checklists and documentation files
This commit is contained in:
@@ -4,17 +4,23 @@ import {
|
||||
Output,
|
||||
EventEmitter,
|
||||
signal,
|
||||
computed,
|
||||
OnInit,
|
||||
inject,
|
||||
ChangeDetectionStrategy
|
||||
ChangeDetectionStrategy,
|
||||
effect,
|
||||
untracked
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SearchBarComponent } from '../search-bar/search-bar.component';
|
||||
import { SearchResultsComponent } from '../search-results/search-results.component';
|
||||
import { SearchEvaluatorService, SearchResult } from '../../core/search/search-evaluator.service';
|
||||
import { SearchOrchestratorService, SearchResult } from '../../core/search/search-orchestrator.service';
|
||||
import { SearchIndexService } from '../../core/search/search-index.service';
|
||||
import { SearchPreferencesService } from '../../core/search/search-preferences.service';
|
||||
import { SearchOptions } from '../../core/search/search-parser.types';
|
||||
import { VaultService } from '../../services/vault.service';
|
||||
import { ClientLoggingService } from '../../services/client-logging.service';
|
||||
|
||||
/**
|
||||
* Complete search panel with bar and results
|
||||
@@ -23,7 +29,7 @@ import { VaultService } from '../../services/vault.service';
|
||||
@Component({
|
||||
selector: 'app-search-panel',
|
||||
standalone: true,
|
||||
imports: [CommonModule, SearchBarComponent, SearchResultsComponent],
|
||||
imports: [CommonModule, FormsModule, SearchBarComponent, SearchResultsComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="flex flex-col h-full bg-bg-primary dark:bg-gray-900">
|
||||
@@ -39,6 +45,56 @@ import { VaultService } from '../../services/vault.service';
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search options toggles -->
|
||||
@if (hasSearched() && results().length > 0) {
|
||||
<div class="flex flex-col gap-3 p-4 border-b border-border dark:border-gray-700 bg-bg-muted dark:bg-gray-800">
|
||||
<!-- Collapse results toggle -->
|
||||
<label class="flex items-center justify-between cursor-pointer group">
|
||||
<span class="text-sm text-text-main dark:text-gray-200">Collapse results</span>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="collapseResults"
|
||||
(change)="onToggleCollapse()"
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div class="w-11 h-6 bg-gray-300 dark:bg-gray-600 rounded-full peer peer-checked:bg-blue-500 dark:peer-checked:bg-blue-600 transition-colors"></div>
|
||||
<div class="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-transform peer-checked:translate-x-5"></div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Show more context toggle -->
|
||||
<label class="flex items-center justify-between cursor-pointer group">
|
||||
<span class="text-sm text-text-main dark:text-gray-200">Show more context</span>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="showMoreContext"
|
||||
(change)="onToggleContext()"
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div class="w-11 h-6 bg-gray-300 dark:bg-gray-600 rounded-full peer peer-checked:bg-blue-500 dark:peer-checked:bg-blue-600 transition-colors"></div>
|
||||
<div class="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-transform peer-checked:translate-x-5"></div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Explain search terms toggle -->
|
||||
<label class="flex items-center justify-between cursor-pointer group">
|
||||
<span class="text-sm text-text-main dark:text-gray-200">Explain search terms</span>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="explainSearchTerms"
|
||||
(change)="onToggleExplain()"
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div class="w-11 h-6 bg-gray-300 dark:bg-gray-600 rounded-full peer peer-checked:bg-blue-500 dark:peer-checked:bg-blue-600 transition-colors"></div>
|
||||
<div class="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-transform peer-checked:translate-x-5"></div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Search results -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
@if (isSearching()) {
|
||||
@@ -59,6 +115,9 @@ import { VaultService } from '../../services/vault.service';
|
||||
} @else if (results().length > 0) {
|
||||
<app-search-results
|
||||
[results]="results()"
|
||||
[collapseAll]="collapseResults"
|
||||
[showMoreContext]="showMoreContext"
|
||||
[contextLines]="contextLines()"
|
||||
(noteOpen)="onNoteOpen($event)"
|
||||
/>
|
||||
} @else {
|
||||
@@ -85,21 +144,51 @@ export class SearchPanelComponent implements OnInit {
|
||||
|
||||
@Output() noteOpen = new EventEmitter<{ noteId: string; line?: number }>();
|
||||
|
||||
private searchEvaluator = inject(SearchEvaluatorService);
|
||||
private orchestrator = inject(SearchOrchestratorService);
|
||||
private searchIndex = inject(SearchIndexService);
|
||||
private vaultService = inject(VaultService);
|
||||
private preferences = inject(SearchPreferencesService);
|
||||
private logger = inject(ClientLoggingService);
|
||||
|
||||
results = signal<SearchResult[]>([]);
|
||||
isSearching = signal(false);
|
||||
hasSearched = signal(false);
|
||||
currentQuery = signal('');
|
||||
|
||||
ngOnInit(): void {
|
||||
// Build search index from vault notes
|
||||
this.rebuildIndex();
|
||||
private lastOptions: SearchOptions = {};
|
||||
|
||||
// Rebuild index when vault changes (could be optimized with incremental updates)
|
||||
// For now, we'll rebuild on init
|
||||
// UI toggles
|
||||
collapseResults = false;
|
||||
showMoreContext = false;
|
||||
explainSearchTerms = false;
|
||||
|
||||
// Computed context lines based on showMoreContext
|
||||
contextLines = computed(() => this.showMoreContext ? 5 : 2);
|
||||
|
||||
private syncIndexEffect = effect(() => {
|
||||
const notes = this.vaultService.allNotes();
|
||||
this.logger.info('SearchPanel', 'Detected notes change, rebuilding index', {
|
||||
context: this.context,
|
||||
noteCount: notes.length
|
||||
});
|
||||
this.searchIndex.rebuildIndex(notes);
|
||||
|
||||
const query = untracked(() => this.currentQuery());
|
||||
if (query && query.trim()) {
|
||||
this.logger.debug('SearchPanel', 'Re-running search after index rebuild', {
|
||||
query,
|
||||
context: this.context
|
||||
});
|
||||
this.executeSearch(query);
|
||||
}
|
||||
}, { allowSignalWrites: true });
|
||||
|
||||
ngOnInit(): void {
|
||||
// Load preferences for this context
|
||||
const prefs = this.preferences.getPreferences(this.context);
|
||||
this.collapseResults = prefs.collapseResults;
|
||||
this.showMoreContext = prefs.showMoreContext;
|
||||
this.explainSearchTerms = prefs.explainSearchTerms;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,29 +199,46 @@ export class SearchPanelComponent implements OnInit {
|
||||
this.searchIndex.rebuildIndex(notes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle search execution
|
||||
*/
|
||||
onSearch(event: { query: string; options: SearchOptions }): void {
|
||||
const { query, options } = event;
|
||||
private executeSearch(query: string, options?: SearchOptions): void {
|
||||
const trimmed = query?.trim() ?? '';
|
||||
|
||||
if (!query || !query.trim()) {
|
||||
if (!trimmed) {
|
||||
this.logger.debug('SearchPanel', 'Clearing search (empty query)');
|
||||
this.isSearching.set(false);
|
||||
this.results.set([]);
|
||||
this.hasSearched.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const baseOptions = options
|
||||
? { ...this.lastOptions, ...options }
|
||||
: { ...this.lastOptions };
|
||||
|
||||
this.logger.info('SearchPanel', 'Executing search', {
|
||||
query: trimmed,
|
||||
options: baseOptions,
|
||||
contextLines: this.contextLines(),
|
||||
context: this.context
|
||||
});
|
||||
|
||||
this.isSearching.set(true);
|
||||
this.currentQuery.set(query);
|
||||
|
||||
// Execute search asynchronously to avoid blocking UI
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const searchResults = this.searchEvaluator.search(query, options);
|
||||
const searchResults = this.orchestrator.execute(trimmed, {
|
||||
...baseOptions,
|
||||
contextLines: this.contextLines()
|
||||
});
|
||||
this.logger.info('SearchPanel', 'Search completed', {
|
||||
query: trimmed,
|
||||
resultCount: searchResults.length
|
||||
});
|
||||
this.results.set(searchResults);
|
||||
this.hasSearched.set(true);
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
this.logger.error('SearchPanel', 'Search execution error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
this.results.set([]);
|
||||
this.hasSearched.set(true);
|
||||
} finally {
|
||||
@@ -141,6 +247,17 @@ export class SearchPanelComponent implements OnInit {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle search execution
|
||||
*/
|
||||
onSearch(event: { query: string; options: SearchOptions }): void {
|
||||
const { query, options } = event;
|
||||
|
||||
this.currentQuery.set(query);
|
||||
this.lastOptions = { ...options };
|
||||
this.executeSearch(query, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle query change (for live search if needed)
|
||||
*/
|
||||
@@ -166,10 +283,39 @@ export class SearchPanelComponent implements OnInit {
|
||||
|
||||
// Re-run current search if there is one
|
||||
if (this.currentQuery()) {
|
||||
this.onSearch({
|
||||
query: this.currentQuery(),
|
||||
options: {}
|
||||
});
|
||||
this.executeSearch(this.currentQuery());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle collapse results
|
||||
*/
|
||||
onToggleCollapse(): void {
|
||||
this.preferences.updatePreferences(this.context, {
|
||||
collapseResults: this.collapseResults
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle show more context
|
||||
*/
|
||||
onToggleContext(): void {
|
||||
this.preferences.updatePreferences(this.context, {
|
||||
showMoreContext: this.showMoreContext
|
||||
});
|
||||
|
||||
// Re-run search with new context lines
|
||||
if (this.currentQuery()) {
|
||||
this.executeSearch(this.currentQuery());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle explain search terms
|
||||
*/
|
||||
onToggleExplain(): void {
|
||||
this.preferences.updatePreferences(this.context, {
|
||||
explainSearchTerms: this.explainSearchTerms
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import {
|
||||
EventEmitter,
|
||||
signal,
|
||||
computed,
|
||||
effect,
|
||||
ChangeDetectionStrategy,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SearchResult, SearchMatch } from '../../core/search/search-evaluator.service';
|
||||
import { SearchResult, SearchMatch } from '../../core/search/search-orchestrator.service';
|
||||
import { SearchHighlighterService } from '../../core/search/search-highlighter.service';
|
||||
import { VaultService } from '../../services/vault.service';
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,7 @@ interface ResultGroup {
|
||||
matchCount: number;
|
||||
isExpanded: boolean;
|
||||
score: number;
|
||||
allRanges: any[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,15 +70,6 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
<option value="name">Name</option>
|
||||
<option value="modified">Modified</option>
|
||||
</select>
|
||||
|
||||
<!-- Expand/Collapse all -->
|
||||
<button
|
||||
(click)="toggleAllGroups()"
|
||||
class="btn-standard-xs"
|
||||
title="Expand/Collapse all"
|
||||
>
|
||||
{{ allExpanded() ? 'Collapse all' : 'Expand all' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -164,8 +158,8 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
</div>
|
||||
|
||||
<!-- Match context with highlighting -->
|
||||
<div class="text-sm text-text-main dark:text-gray-200 font-mono leading-relaxed">
|
||||
<span [innerHTML]="highlightMatch(match.context, match.text)"></span>
|
||||
<div class="text-sm text-text-main dark:text-gray-200 font-mono leading-relaxed whitespace-pre-wrap">
|
||||
<span [innerHTML]="highlightMatch(match)"></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -205,16 +199,32 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
})
|
||||
export class SearchResultsComponent {
|
||||
@Input() set results(value: SearchResult[]) {
|
||||
this._results.set(value);
|
||||
this.buildGroups(value);
|
||||
}
|
||||
|
||||
@Input() collapseAll: boolean = false;
|
||||
@Input() showMoreContext: boolean = false;
|
||||
@Input() contextLines: number = 2;
|
||||
|
||||
@Output() noteOpen = new EventEmitter<{ noteId: string; line?: number }>();
|
||||
|
||||
private vaultService = inject(VaultService);
|
||||
private highlighter = inject(SearchHighlighterService);
|
||||
|
||||
private _results = signal<SearchResult[]>([]);
|
||||
private groups = signal<ResultGroup[]>([]);
|
||||
sortBy: SortOption = 'relevance';
|
||||
|
||||
constructor() {
|
||||
// Watch for collapseAll changes
|
||||
effect(() => {
|
||||
if (this.collapseAll !== undefined) {
|
||||
this.applyCollapseAll(this.collapseAll);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Total number of result files
|
||||
*/
|
||||
@@ -273,14 +283,24 @@ export class SearchResultsComponent {
|
||||
filePath,
|
||||
matches: result.matches,
|
||||
matchCount: result.matches.length,
|
||||
isExpanded: true, // Expand by default
|
||||
score: result.score
|
||||
isExpanded: !this.collapseAll, // Respect collapseAll setting
|
||||
score: result.score,
|
||||
allRanges: result.allRanges || []
|
||||
};
|
||||
});
|
||||
|
||||
this.groups.set(groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply collapse all setting to all groups
|
||||
*/
|
||||
private applyCollapseAll(collapse: boolean): void {
|
||||
this.groups.update(groups => {
|
||||
return groups.map(g => ({ ...g, isExpanded: !collapse }));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle a result group
|
||||
*/
|
||||
@@ -295,7 +315,7 @@ export class SearchResultsComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle all groups
|
||||
* Toggle all groups (not used when controlled by parent)
|
||||
*/
|
||||
toggleAllGroups(): void {
|
||||
const shouldExpand = !this.allExpanded();
|
||||
@@ -320,30 +340,24 @@ export class SearchResultsComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight matched text in context
|
||||
* Highlight matched text in context using ranges
|
||||
*/
|
||||
highlightMatch(context: string, matchText: string): string {
|
||||
if (!matchText || !context) {
|
||||
return this.escapeHtml(context);
|
||||
highlightMatch(match: SearchMatch): string {
|
||||
if (!match || !match.context) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const escapedContext = this.escapeHtml(context);
|
||||
const escapedMatch = this.escapeHtml(matchText);
|
||||
|
||||
// Case-insensitive replacement with highlighting
|
||||
const regex = new RegExp(`(${escapedMatch})`, 'gi');
|
||||
return escapedContext.replace(
|
||||
regex,
|
||||
'<mark class="bg-yellow-200 dark:bg-yellow-600 text-text-main dark:text-gray-900 px-0.5 rounded">$1</mark>'
|
||||
);
|
||||
}
|
||||
// If we have ranges, use them for precise highlighting
|
||||
if (match.ranges && match.ranges.length > 0) {
|
||||
return this.highlighter.highlightWithRanges(match.context, match.ranges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML to prevent XSS
|
||||
*/
|
||||
private escapeHtml(text: string): string {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
// Fallback to simple text-based highlighting
|
||||
if (match.text) {
|
||||
return this.highlighter.highlightMatches(match.context, [match.text], false);
|
||||
}
|
||||
|
||||
// No highlighting needed
|
||||
return this.highlighter.highlightMatches(match.context, [], false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user