feat: add syntax highlighting and code block styling with theme support
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" (click)="onBackdropClick($event)">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-lg shadow-xl max-w-2xl w-full p-6" (click)="$event.stopPropagation()">
|
||||
<div class="bg-card dark:bg-main rounded-lg shadow-xl max-w-2xl w-full p-6" (click)="$event.stopPropagation()">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
<h2 class="text-xl font-semibold text-main dark:text-gray-100">
|
||||
{{ isEditMode() ? 'Edit bookmark' : 'Add bookmark' }}
|
||||
</h2>
|
||||
<button
|
||||
(click)="onCancel()"
|
||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
||||
class="text-muted hover:text-muted dark:hover:text-gray-300 transition-colors"
|
||||
aria-label="Close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
@@ -19,41 +19,41 @@
|
||||
<div class="space-y-4">
|
||||
<!-- Path -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<label class="block text-sm font-medium text-main dark:text-main mb-2">
|
||||
Path
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
[value]="path()"
|
||||
(input)="onPathChange($any($event.target).value)"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="w-full px-3 py-2 border border-border dark:border-border rounded-md bg-card dark:bg-card text-main dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="e.g. Tests/Allo note.md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<label class="block text-sm font-medium text-main dark:text-main mb-2">
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
[value]="title()"
|
||||
(input)="onTitleChange($any($event.target).value)"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="w-full px-3 py-2 border border-border dark:border-border rounded-md bg-card dark:bg-card text-main dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Display name (optional)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Bookmark group -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<label class="block text-sm font-medium text-main dark:text-main mb-2">
|
||||
Bookmark group
|
||||
</label>
|
||||
<select
|
||||
[value]="selectedGroupCtime() || ''"
|
||||
(change)="onGroupChange($any($event.target).value)"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
class="w-full px-3 py-2 border border-border dark:border-border rounded-md bg-card dark:bg-card text-main dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">Root (no group)</option>
|
||||
@for (group of groups(); track group.ctime) {
|
||||
<option [value]="group.ctime">{{ group.title }}</option>
|
||||
@@ -79,7 +79,7 @@
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
(click)="onCancel()"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors">
|
||||
class="px-4 py-2 text-sm font-medium text-main dark:text-main bg-surface1 dark:bg-surface2 hover:bg-surface2 dark:hover:bg-gray-600 rounded-md transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="bookmark-node">
|
||||
<div
|
||||
class="relative flex items-center gap-2 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md cursor-pointer transition-colors"
|
||||
class="relative flex items-center gap-2 px-3 py-2 hover:bg-surface1 dark:hover:bg-card rounded-md cursor-pointer transition-colors"
|
||||
[style.padding-left]="indentStyle"
|
||||
(click)="onClick($event)"
|
||||
(contextmenu)="onContextMenu($event)"
|
||||
[class.bg-gray-50]="isGroup"
|
||||
[class.dark:bg-gray-800/50]="isGroup">
|
||||
[class.bg-surface1]="isGroup"
|
||||
[class.dark:bg-card/50]="isGroup">
|
||||
|
||||
<!-- Icon -->
|
||||
<span class="text-base select-none" [class.cursor-pointer]="isGroup">
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<!-- Text -->
|
||||
<span
|
||||
class="flex-1 text-sm text-gray-900 dark:text-gray-100 truncate"
|
||||
class="flex-1 text-sm text-main dark:text-gray-100 truncate"
|
||||
[title]="displayText">
|
||||
{{ displayText }}
|
||||
</span>
|
||||
@@ -22,13 +22,13 @@
|
||||
@if (isGroup) {
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
class="p-1 text-gray-500 hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400 transition-colors"
|
||||
class="p-1 text-muted hover:text-blue-600 dark:text-muted dark:hover:text-blue-400 transition-colors"
|
||||
title="Ajouter un favori dans ce groupe"
|
||||
(click)="onAddBookmark($event)">
|
||||
+
|
||||
</button>
|
||||
<button
|
||||
class="p-1 text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400 transition-colors"
|
||||
class="p-1 text-muted hover:text-red-600 dark:text-muted dark:hover:text-red-400 transition-colors"
|
||||
title="Supprimer ce groupe"
|
||||
(click)="onDelete($event)">
|
||||
🗑
|
||||
@@ -38,14 +38,14 @@
|
||||
|
||||
<!-- Badge for group count -->
|
||||
@if (isGroup && hasChildren) {
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 bg-gray-200 dark:bg-gray-700 px-2 py-0.5 rounded-full">
|
||||
<span class="text-xs text-muted dark:text-muted bg-surface2 dark:bg-surface2 px-2 py-0.5 rounded-full">
|
||||
{{ children.length }}
|
||||
</span>
|
||||
}
|
||||
|
||||
<!-- Context Menu Button -->
|
||||
<button
|
||||
class="opacity-0 hover:opacity-100 focus:opacity-100 p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 rounded transition-opacity"
|
||||
class="opacity-0 hover:opacity-100 focus:opacity-100 p-1 text-muted hover:text-main dark:text-muted dark:hover:text-main rounded transition-opacity"
|
||||
(click)="onContextMenu($event); $event.stopPropagation()"
|
||||
title="More options">
|
||||
⋮
|
||||
@@ -54,20 +54,20 @@
|
||||
<!-- Context Menu -->
|
||||
@if (showMenu()) {
|
||||
<div
|
||||
class="absolute right-0 top-full mt-1 z-10 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md shadow-lg"
|
||||
class="absolute right-0 top-full mt-1 z-10 w-48 bg-card dark:bg-card border border-border dark:border-border rounded-md shadow-lg"
|
||||
(click)="$event.stopPropagation()">
|
||||
@if (isGroup) {
|
||||
<button
|
||||
class="w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
class="w-full px-4 py-2 text-left text-sm text-main dark:text-main hover:bg-surface1 dark:hover:bg-surface2 transition-colors"
|
||||
(click)="onAddBookmark()">
|
||||
Add Bookmark Here
|
||||
</button>
|
||||
<button
|
||||
class="w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
class="w-full px-4 py-2 text-left text-sm text-main dark:text-main hover:bg-surface1 dark:hover:bg-surface2 transition-colors"
|
||||
(click)="onAddGroup()">
|
||||
Add Subgroup
|
||||
</button>
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
<hr class="border-border dark:border-border" />
|
||||
<button
|
||||
class="w-full px-4 py-2 text-left text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
||||
(click)="onDelete()">
|
||||
@@ -75,11 +75,11 @@
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
class="w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
class="w-full px-4 py-2 text-left text-sm text-main dark:text-main hover:bg-surface1 dark:hover:bg-surface2 transition-colors"
|
||||
(click)="onEdit()">
|
||||
Edit
|
||||
</button>
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
<hr class="border-border dark:border-border" />
|
||||
<button
|
||||
class="w-full px-4 py-2 text-left text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
||||
(click)="onDelete()">
|
||||
@@ -93,7 +93,7 @@
|
||||
<!-- Drop list for this group (always present for drag & drop to work) -->
|
||||
@if (isGroup) {
|
||||
<div
|
||||
class="ml-6 border-l border-gray-200 dark:border-gray-700 pl-2 min-h-[40px] transition-colors"
|
||||
class="ml-6 border-l border-border dark:border-border pl-2 min-h-[40px] transition-colors"
|
||||
[class.border-blue-500]="isDraggingOver()"
|
||||
[class.dark:border-blue-400]="isDraggingOver()"
|
||||
[class.bg-blue-500/5]="isDraggingOver()"
|
||||
@@ -128,12 +128,12 @@
|
||||
<!-- Drop zone (always visible for groups) -->
|
||||
<div class="min-h-[20px] flex items-center justify-center">
|
||||
@if (isExpanded() && children.length === 0) {
|
||||
<div class="py-2 px-3 text-xs text-gray-400 dark:text-gray-500 italic">
|
||||
<div class="py-2 px-3 text-xs text-muted dark:text-muted italic">
|
||||
Drop items here
|
||||
</div>
|
||||
}
|
||||
@if (!isExpanded()) {
|
||||
<div class="py-1 px-2 text-xs text-gray-400 dark:text-gray-500 italic opacity-50">
|
||||
<div class="py-1 px-2 text-xs text-muted dark:text-muted italic opacity-50">
|
||||
Drop here ({{ children.length }} items)
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="bookmarks-panel flex flex-col h-full bg-white dark:bg-gray-900">
|
||||
<div class="bookmarks-panel flex flex-col h-full bg-card dark:bg-main">
|
||||
<!-- Header -->
|
||||
<div class="bookmarks-header flex-shrink-0 p-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="bookmarks-header flex-shrink-0 p-4 border-b border-border dark:border-border">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Bookmarks</h2>
|
||||
<h2 class="text-lg font-semibold text-main dark:text-gray-100">Bookmarks</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-standard-sm"
|
||||
class="btn btn-outline btn-sm"
|
||||
(click)="createGroup()"
|
||||
title="Créer un groupe">
|
||||
+ Group
|
||||
@@ -24,13 +24,14 @@
|
||||
[value]="searchTerm()"
|
||||
(input)="onSearchChange($any($event.target).value)"
|
||||
placeholder="Search bookmarks..."
|
||||
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="w-full px-3 py-2 text-sm border border-border dark:border-border rounded-md bg-card dark:bg-card text-main dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
@if (searchTerm()) {
|
||||
<button
|
||||
(click)="onSearchChange('')"
|
||||
class="btn-standard-icon absolute right-2 top-1/2 -translate-y-1/2"
|
||||
title="Clear search">
|
||||
class="btn btn-ghost btn-sm absolute right-2 top-1/2 -translate-y-1/2"
|
||||
title="Clear search"
|
||||
aria-label="Clear search">
|
||||
✕
|
||||
</button>
|
||||
}
|
||||
@@ -41,7 +42,7 @@
|
||||
<!-- Body -->
|
||||
<div class="bookmarks-body flex-1 overflow-y-auto p-4">
|
||||
@if (loading()) {
|
||||
<div class="flex items-center justify-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center justify-center py-8 text-muted dark:text-muted">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
} @else if (error()) {
|
||||
@@ -51,14 +52,14 @@
|
||||
<span class="text-sm text-red-700 dark:text-red-300 flex-1">{{ error() }}</span>
|
||||
<button
|
||||
(click)="clearError()"
|
||||
class="btn-standard-icon !text-red-600 dark:!text-red-400 hover:!text-red-800 dark:hover:!text-red-200"
|
||||
title="Dismiss">
|
||||
class="btn btn-ghost btn-sm !text-red-600 dark:!text-red-400 hover:!text-red-800 dark:hover:!text-red-200"
|
||||
title="Dismiss" aria-label="Dismiss">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
} @else if (isEmpty()) {
|
||||
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<div class="text-center py-8 text-muted dark:text-muted">
|
||||
<p class="mb-4">No bookmarks yet</p>
|
||||
<p class="text-sm">Use the bookmark icon in the note toolbar to add one.</p>
|
||||
</div>
|
||||
@@ -115,7 +116,7 @@
|
||||
class="mb-1" />
|
||||
}
|
||||
@if (displayItems().length === 0) {
|
||||
<p class="text-sm text-gray-400 dark:text-gray-500 py-4 text-center">No bookmarks to display.</p>
|
||||
<p class="text-sm text-muted dark:text-muted py-4 text-center">No bookmarks to display.</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -124,20 +125,20 @@
|
||||
<!-- Conflict Modal -->
|
||||
@if (conflictInfo()) {
|
||||
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full mx-4 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">Conflict Detected</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
<div class="bg-card dark:bg-card rounded-lg shadow-xl max-w-md w-full mx-4 p-6">
|
||||
<h3 class="text-lg font-semibold text-main dark:text-gray-100 mb-3">Conflict Detected</h3>
|
||||
<p class="text-sm text-muted dark:text-muted mb-4">
|
||||
The bookmarks file has been modified externally. What would you like to do?
|
||||
</p>
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
(click)="resolveConflictReload()"
|
||||
class="btn-standard-primary flex-1">
|
||||
class="btn btn-solid btn-md flex-1">
|
||||
Reload from file
|
||||
</button>
|
||||
<button
|
||||
(click)="resolveConflictOverwrite()"
|
||||
class="btn-standard-danger flex-1">
|
||||
class="btn btn-outline btn-md flex-1">
|
||||
Overwrite file
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ import { BadgeCountComponent } from '../../app/shared/ui/badge-count.component';
|
||||
<div>
|
||||
<div
|
||||
(click)="onFolderClick(folder)"
|
||||
class="flex items-center cursor-pointer px-3 py-2 rounded-lg my-0.5 text-sm hover:bg-slate-500/10 dark:hover:bg-slate-200/10 transition"
|
||||
class="flex items-center cursor-pointer px-3 py-2 rounded-lg my-0.5 text-sm hover:bg-slate-500/10 dark:hover:bg-surface2/10 transition"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2 transition-transform text-obs-l-text-muted dark:text-obs-d-text-muted" [class.rotate-90]="folder.isOpen" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
@@ -45,7 +45,7 @@ import { BadgeCountComponent } from '../../app/shared/ui/badge-count.component';
|
||||
[class.bg-obs-l-bg-main]="selectedNoteId() === file.id"
|
||||
[class.dark:bg-obs-d-bg-main]="selectedNoteId() === file.id"
|
||||
[class.hover:bg-slate-500/10]="selectedNoteId() !== file.id"
|
||||
[class.dark:hover:bg-slate-200/10]="selectedNoteId() !== file.id"
|
||||
[class.dark:hover:bg-surface2/10]="selectedNoteId() !== file.id"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2 flex-shrink-0 text-obs-l-text-muted dark:text-obs-d-text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
|
||||
@@ -38,12 +38,12 @@ export interface GraphOptions {
|
||||
imports: [CommonModule, FormsModule, SearchInputWithAssistantComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="graph-options-panel bg-white dark:bg-gray-800 border-l border-gray-200 dark:border-gray-700 h-full overflow-y-auto">
|
||||
<div class="graph-options-panel bg-card dark:bg-card border-l border-border dark:border-border h-full overflow-y-auto">
|
||||
<div class="p-4 space-y-6">
|
||||
|
||||
<!-- Filters Section -->
|
||||
<section>
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3 flex items-center gap-2">
|
||||
<h3 class="text-sm font-semibold text-main dark:text-gray-100 mb-3 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||||
</svg>
|
||||
@@ -56,8 +56,8 @@ export interface GraphOptions {
|
||||
type="checkbox"
|
||||
[(ngModel)]="filters().showTags"
|
||||
(change)="emitOptionsChange()"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100">Tags</span>
|
||||
class="rounded border-border text-blue-600 focus:ring-ring dark:border-border dark:bg-surface2">
|
||||
<span class="text-sm text-main dark:text-main group-hover:text-main dark:group-hover:text-gray-100">Tags</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
@@ -65,8 +65,8 @@ export interface GraphOptions {
|
||||
type="checkbox"
|
||||
[(ngModel)]="filters().showAttachments"
|
||||
(change)="emitOptionsChange()"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100">Attachments</span>
|
||||
class="rounded border-border text-blue-600 focus:ring-ring dark:border-border dark:bg-surface2">
|
||||
<span class="text-sm text-main dark:text-main group-hover:text-main dark:group-hover:text-gray-100">Attachments</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
@@ -74,8 +74,8 @@ export interface GraphOptions {
|
||||
type="checkbox"
|
||||
[(ngModel)]="filters().existingFilesOnly"
|
||||
(change)="emitOptionsChange()"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100">Existing files only</span>
|
||||
class="rounded border-border text-blue-600 focus:ring-ring dark:border-border dark:bg-surface2">
|
||||
<span class="text-sm text-main dark:text-main group-hover:text-main dark:group-hover:text-gray-100">Existing files only</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2 cursor-pointer group">
|
||||
@@ -83,8 +83,8 @@ export interface GraphOptions {
|
||||
type="checkbox"
|
||||
[(ngModel)]="filters().showOrphans"
|
||||
(change)="emitOptionsChange()"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100">Orphans</span>
|
||||
class="rounded border-border text-blue-600 focus:ring-ring dark:border-border dark:bg-surface2">
|
||||
<span class="text-sm text-main dark:text-main group-hover:text-main dark:group-hover:text-gray-100">Orphans</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -96,14 +96,14 @@ export interface GraphOptions {
|
||||
[placeholder]="'Search files…'"
|
||||
[context]="'graph'"
|
||||
[showSearchIcon]="true"
|
||||
[inputClass]="'w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500'"
|
||||
[inputClass]="'w-full px-3 py-2 text-sm border border-border dark:border-border rounded-md bg-card dark:bg-surface2 text-main dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-ring'"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Groups Section -->
|
||||
<section>
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3 flex items-center gap-2">
|
||||
<h3 class="text-sm font-semibold text-main dark:text-gray-100 mb-3 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
@@ -112,7 +112,7 @@ export interface GraphOptions {
|
||||
|
||||
<div class="space-y-2 mb-3">
|
||||
@for (group of colorGroups(); track $index) {
|
||||
<div class="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
||||
<div class="flex items-center gap-2 p-2 bg-surface1 dark:bg-surface2/50 rounded-lg">
|
||||
<div
|
||||
[style.background-color]="getGroupColorPreview(group)"
|
||||
class="w-6 h-6 rounded-full border-2 border-white dark:border-gray-800 shadow-sm flex-shrink-0">
|
||||
@@ -122,11 +122,11 @@ export interface GraphOptions {
|
||||
[value]="group.query"
|
||||
(input)="updateGroupQuery($index, $any($event.target).value)"
|
||||
placeholder="Query (e.g., tag:#code)"
|
||||
class="flex-1 px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded"
|
||||
class="flex-1 px-2 py-1 text-xs bg-card dark:bg-card border border-border dark:border-border rounded"
|
||||
/>
|
||||
<button
|
||||
(click)="removeGroup($index)"
|
||||
class="p-1 text-gray-400 hover:text-red-500 transition-colors"
|
||||
class="p-1 text-muted hover:text-red-500 transition-colors"
|
||||
title="Remove group"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -147,7 +147,7 @@ export interface GraphOptions {
|
||||
|
||||
<!-- Display Section -->
|
||||
<section>
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3 flex items-center gap-2">
|
||||
<h3 class="text-sm font-semibold text-main dark:text-gray-100 mb-3 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
@@ -161,12 +161,12 @@ export interface GraphOptions {
|
||||
type="checkbox"
|
||||
[(ngModel)]="display().showArrows"
|
||||
(change)="emitOptionsChange()"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-gray-100">Arrows</span>
|
||||
class="rounded border-border text-blue-600 focus:ring-blue-500 dark:border-border dark:bg-surface2">
|
||||
<span class="text-sm text-main dark:text-main group-hover:text-main dark:group-hover:text-gray-100">Arrows</span>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<label class="flex items-center justify-between text-sm text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label class="flex items-center justify-between text-sm text-main dark:text-main mb-1">
|
||||
<span>Text fade threshold</span>
|
||||
<span class="font-mono text-xs">{{ display().textFadeThreshold }}</span>
|
||||
</label>
|
||||
@@ -176,11 +176,11 @@ export interface GraphOptions {
|
||||
max="100"
|
||||
[(ngModel)]="display().textFadeThreshold"
|
||||
(input)="emitOptionsChange()"
|
||||
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
class="w-full h-2 bg-surface2 dark:bg-surface2 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="flex items-center justify-between text-sm text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label class="flex items-center justify-between text-sm text-main dark:text-main mb-1">
|
||||
<span>Node size</span>
|
||||
<span class="font-mono text-xs">{{ display().nodeSize }}</span>
|
||||
</label>
|
||||
@@ -190,11 +190,11 @@ export interface GraphOptions {
|
||||
max="20"
|
||||
[(ngModel)]="display().nodeSize"
|
||||
(input)="emitOptionsChange()"
|
||||
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
class="w-full h-2 bg-surface2 dark:bg-surface2 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="flex items-center justify-between text-sm text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label class="flex items-center justify-between text-sm text-main dark:text-main mb-1">
|
||||
<span>Link thickness</span>
|
||||
<span class="font-mono text-xs">{{ display().linkThickness }}</span>
|
||||
</label>
|
||||
@@ -204,7 +204,7 @@ export interface GraphOptions {
|
||||
max="10"
|
||||
[(ngModel)]="display().linkThickness"
|
||||
(input)="emitOptionsChange()"
|
||||
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
class="w-full h-2 bg-surface2 dark:bg-surface2 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -221,7 +221,7 @@ export interface GraphOptions {
|
||||
<button
|
||||
type="button"
|
||||
(click)="forcesExpanded.set(!forcesExpanded())"
|
||||
class="w-full flex items-center justify-between text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">
|
||||
class="w-full flex items-center justify-between text-sm font-semibold text-main dark:text-gray-100 mb-3">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
@@ -242,7 +242,7 @@ export interface GraphOptions {
|
||||
@if (forcesExpanded()) {
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="flex items-center justify-between text-sm text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label class="flex items-center justify-between text-sm text-main dark:text-main mb-1">
|
||||
<span>Charge</span>
|
||||
<span class="font-mono text-xs">{{ forces().chargeStrength }}</span>
|
||||
</label>
|
||||
@@ -252,11 +252,11 @@ export interface GraphOptions {
|
||||
max="0"
|
||||
[(ngModel)]="forces().chargeStrength"
|
||||
(input)="emitOptionsChange()"
|
||||
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
class="w-full h-2 bg-surface2 dark:bg-surface2 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="flex items-center justify-between text-sm text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label class="flex items-center justify-between text-sm text-main dark:text-main mb-1">
|
||||
<span>Link distance</span>
|
||||
<span class="font-mono text-xs">{{ forces().linkDistance }}</span>
|
||||
</label>
|
||||
@@ -266,11 +266,11 @@ export interface GraphOptions {
|
||||
max="500"
|
||||
[(ngModel)]="forces().linkDistance"
|
||||
(input)="emitOptionsChange()"
|
||||
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
class="w-full h-2 bg-surface2 dark:bg-surface2 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="flex items-center justify-between text-sm text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label class="flex items-center justify-between text-sm text-main dark:text-main mb-1">
|
||||
<span>Center strength</span>
|
||||
<span class="font-mono text-xs">{{ forces().centerStrength.toFixed(2) }}</span>
|
||||
</label>
|
||||
@@ -281,7 +281,7 @@ export interface GraphOptions {
|
||||
step="0.01"
|
||||
[(ngModel)]="forces().centerStrength"
|
||||
(input)="emitOptionsChange()"
|
||||
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
class="w-full h-2 bg-surface2 dark:bg-surface2 rounded-lg appearance-none cursor-pointer accent-blue-600">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import { createFilteredGraphData, createGroupLegend, createFocusedGraphData } fr
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="flex flex-col h-full w-full bg-gray-50 dark:bg-gray-900">
|
||||
<div class="flex flex-col h-full w-full bg-surface1 dark:bg-main">
|
||||
<!-- Main graph area -->
|
||||
<div class="flex-1 relative">
|
||||
<app-graph-canvas
|
||||
@@ -53,10 +53,10 @@ import { createFilteredGraphData, createGroupLegend, createFocusedGraphData } fr
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleSettings()"
|
||||
class="absolute top-5 right-5 flex items-center justify-center w-11 h-11 rounded-full bg-white dark:bg-gray-800 shadow-lg hover:shadow-xl transition-all hover:rotate-90 z-50 border border-gray-200 dark:border-gray-700"
|
||||
class="absolute top-5 right-5 flex items-center justify-center w-11 h-11 rounded-full bg-card dark:bg-card shadow-lg hover:shadow-xl transition-all hover:rotate-90 z-50 border border-border dark:border-border"
|
||||
aria-label="Graph settings"
|
||||
title="Graph settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-gray-700 dark:text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-main dark:text-main" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
@@ -66,7 +66,7 @@ import { createFilteredGraphData, createGroupLegend, createFocusedGraphData } fr
|
||||
|
||||
<!-- Legend at bottom -->
|
||||
@if (legendItems().length > 0) {
|
||||
<div class="p-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="p-4 border-t border-border dark:border-border">
|
||||
<app-graph-legend
|
||||
[items]="legendItems()"
|
||||
(itemClicked)="onLegendItemClicked($event)">
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface GraphDisplayOptions {
|
||||
imports: [CommonModule],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="relative h-full w-full bg-gray-50 dark:bg-gray-900">
|
||||
<div class="relative h-full w-full bg-surface1 dark:bg-main">
|
||||
<svg #graphSvg class="w-full h-full">
|
||||
<defs>
|
||||
<marker
|
||||
@@ -99,7 +99,7 @@ export interface GraphDisplayOptions {
|
||||
</svg>
|
||||
|
||||
<!-- Info overlay -->
|
||||
<div class="absolute top-4 left-4 bg-white dark:bg-gray-800 px-3 py-2 rounded-lg shadow-md text-xs text-gray-700 dark:text-gray-300">
|
||||
<div class="absolute top-4 left-4 bg-card dark:bg-card px-3 py-2 rounded-lg shadow-md text-xs text-main dark:text-main">
|
||||
<div><strong>Nodes:</strong> {{ simulatedNodes().length }}</div>
|
||||
<div><strong>Links:</strong> {{ edges().length }}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Component, Input, OnChanges, SimpleChanges, signal, computed, inject, effect, CUSTOM_ELEMENTS_SCHEMA, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, Input, OnChanges, SimpleChanges, signal, computed, inject, effect, CUSTOM_ELEMENTS_SCHEMA, Output, EventEmitter, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { MarkdownService } from '../../services/markdown.service';
|
||||
import { Note } from '../../types';
|
||||
import { DrawingsEditorComponent } from '../../app/features/drawings/drawings-editor.component';
|
||||
import { EditorStateService } from '../../services/editor-state.service';
|
||||
import { TocService } from '../../services/toc.service';
|
||||
|
||||
/**
|
||||
* Composant réutilisable pour afficher du contenu Markdown
|
||||
@@ -83,8 +84,9 @@ import { EditorStateService } from '../../services/editor-state.service';
|
||||
<!-- Markdown Content -->
|
||||
<div
|
||||
*ngIf="!isExcalidrawFile()"
|
||||
class="markdown-viewer__content prose prose-slate dark:prose-invert max-w-none"
|
||||
[innerHTML]="renderedHtml()">
|
||||
class="markdown-viewer__content"
|
||||
#contentRef>
|
||||
<div class="md-view prose themed-prose max-w-none" [innerHTML]="renderedHtml()"></div>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
@@ -187,10 +189,12 @@ import { EditorStateService } from '../../services/editor-state.service';
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class MarkdownViewerComponent implements OnChanges {
|
||||
export class MarkdownViewerComponent implements OnChanges, AfterViewInit {
|
||||
private markdownService = inject(MarkdownService);
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
private editorState = inject(EditorStateService);
|
||||
private toc = inject(TocService);
|
||||
@ViewChild('contentRef') private contentRef?: ElementRef<HTMLElement>;
|
||||
|
||||
/** Contenu markdown brut à afficher */
|
||||
@Input() content: string = '';
|
||||
@@ -252,6 +256,21 @@ export class MarkdownViewerComponent implements OnChanges {
|
||||
this.setupLazyLoading();
|
||||
}
|
||||
});
|
||||
|
||||
// After each HTML render, apply anchors and scrollspy
|
||||
effect(() => {
|
||||
// depend on renderedHtml() signal
|
||||
void this.renderedHtml();
|
||||
if (this.isExcalidrawFile()) return;
|
||||
// next tick to ensure DOM updated
|
||||
setTimeout(() => {
|
||||
const root = this.contentRef?.nativeElement as HTMLElement | undefined;
|
||||
if (!root) return;
|
||||
this.applyAnchors(root);
|
||||
this.toc.extract(root);
|
||||
this.toc.setupScrollSpy(root, 84);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
@@ -302,4 +321,37 @@ export class MarkdownViewerComponent implements OnChanges {
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
setTimeout(() => {
|
||||
if (this.isExcalidrawFile()) return;
|
||||
const root = this.contentRef?.nativeElement as HTMLElement | undefined;
|
||||
if (!root) return;
|
||||
this.applyAnchors(root);
|
||||
this.toc.extract(root);
|
||||
this.toc.setupScrollSpy(root, 84);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private applyAnchors(root: HTMLElement): void {
|
||||
const headings = Array.from(root.querySelectorAll('h1, h2, h3, h4, h5, h6')) as HTMLHeadingElement[];
|
||||
for (const h of headings) {
|
||||
if (!h.id) {
|
||||
// ensure id if missing
|
||||
h.id = (h.textContent || '').trim().toLowerCase()
|
||||
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/[^\w\s-]/g, '')
|
||||
.trim().replace(/\s+/g, '-').replace(/-+/g, '-') || 'section';
|
||||
}
|
||||
const exists = h.querySelector(':scope > a.heading-anchor');
|
||||
if (!exists) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'heading-anchor';
|
||||
a.href = `#${h.id}`;
|
||||
a.textContent = '#';
|
||||
a.setAttribute('aria-label', 'Anchor link');
|
||||
h.appendChild(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,23 +15,23 @@ export interface PreviewData {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div
|
||||
class="preview-card bg-white dark:bg-gray-800 rounded-2xl shadow-xl border border-gray-200 dark:border-gray-700 p-4 max-w-md overflow-hidden"
|
||||
class="preview-card bg-card dark:bg-card rounded-2xl shadow-xl border border-border dark:border-border p-4 max-w-md overflow-hidden"
|
||||
(mouseenter)="mouseEnter.emit()"
|
||||
(mouseleave)="mouseLeave.emit()">
|
||||
|
||||
<h2 class="font-bold text-lg mb-2 text-gray-900 dark:text-gray-100 truncate">
|
||||
<h2 class="font-bold text-lg mb-2 text-main dark:text-gray-100 truncate">
|
||||
{{ previewData().title }}
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="prose prose-sm dark:prose-invert max-w-none line-clamp-5 text-gray-700 dark:text-gray-300"
|
||||
class="prose prose-sm dark:prose-invert max-w-none line-clamp-5 text-main dark:text-main"
|
||||
[innerHTML]="sanitizedExcerpt()">
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex justify-end">
|
||||
<button
|
||||
(click)="openNote.emit()"
|
||||
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 transition-colors p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 transition-colors p-1 rounded hover:bg-surface1 dark:hover:bg-surface2"
|
||||
aria-label="Ouvrir la note">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
|
||||
@@ -32,7 +32,7 @@ import { SearchOptions } from '../../core/search/search-parser.types';
|
||||
<!-- Search icon -->
|
||||
<svg
|
||||
*ngIf="showSearchIcon"
|
||||
class="pointer-events-none absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-text-muted dark:text-gray-400 z-10"
|
||||
class="pointer-events-none absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-text-muted dark:text-muted z-10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -144,7 +144,7 @@ export class SearchBarComponent implements AfterViewInit, OnInit {
|
||||
@Input() context: string = 'vault';
|
||||
@Input() showExamples: boolean = true;
|
||||
@Input() showSearchIcon: boolean = true;
|
||||
@Input() inputClass: string = 'w-full rounded-full border border-border bg-bg-muted/70 py-2.5 pr-4 text-sm text-text-main placeholder:text-text-muted shadow-subtle focus:outline-none focus:ring-2 focus:ring-ring transition-all dark:border-gray-600 dark:bg-gray-800/80 dark:text-gray-100 dark:placeholder-gray-400 dark:focus:ring-blue-500';
|
||||
@Input() inputClass: string = 'w-full rounded-full border border-border bg-bg-muted/70 py-2.5 pr-4 text-sm text-text-main placeholder:text-text-muted shadow-subtle focus:outline-none focus:ring-2 focus:ring-ring transition-all dark:border-border dark:bg-card/80 dark:text-gray-100 dark:placeholder-gray-400 dark:focus:ring-ring';
|
||||
@Input() initialQuery: string = '';
|
||||
@Input() caseSensitiveDefault: boolean = false;
|
||||
@Input() regexDefault: boolean = false;
|
||||
|
||||
@@ -31,7 +31,7 @@ import { SearchPreferencesService } from '../../core/search/search-preferences.s
|
||||
<div class="relative">
|
||||
<svg
|
||||
*ngIf="showSearchIcon"
|
||||
class="pointer-events-none absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-text-muted dark:text-gray-400"
|
||||
class="pointer-events-none absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-text-muted dark:text-muted"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -97,7 +97,7 @@ export class SearchInputWithAssistantComponent implements AfterViewInit, OnInit
|
||||
@Input() value: string = '';
|
||||
@Input() context: string = 'default';
|
||||
@Input() showSearchIcon: boolean = true;
|
||||
@Input() inputClass: string = 'w-full rounded-full border border-border bg-bg-muted/70 py-2.5 pr-4 text-sm text-text-main placeholder:text-text-muted shadow-subtle focus:outline-none focus:ring-2 focus:ring-ring transition-all dark:border-gray-600 dark:bg-gray-800/80 dark:text-gray-100 dark:placeholder-gray-400 dark:focus:ring-blue-500';
|
||||
@Input() inputClass: string = 'w-full rounded-full border border-border bg-bg-muted/70 py-2.5 pr-4 text-sm text-text-main placeholder:text-text-muted shadow-subtle focus:outline-none focus:ring-2 focus:ring-ring transition-all dark:border-border dark:bg-card/80 dark:text-gray-100 dark:placeholder-gray-400 dark:focus:ring-ring';
|
||||
@Input() showExamples: boolean = true;
|
||||
|
||||
@Output() valueChange = new EventEmitter<string>();
|
||||
|
||||
@@ -39,9 +39,9 @@ import { parseSearchQuery } from '../../core/search/search-parser';
|
||||
imports: [CommonModule, FormsModule, SearchBarComponent, SearchResultsComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="flex flex-col h-full bg-bg-primary dark:bg-gray-900">
|
||||
<div class="flex flex-col h-full bg-bg-primary dark:bg-main">
|
||||
<!-- Search bar -->
|
||||
<div class="p-4 border-b border-border dark:border-gray-700">
|
||||
<div class="p-4 border-b border-border dark:border-border">
|
||||
<app-search-bar
|
||||
[placeholder]="placeholder"
|
||||
[context]="context"
|
||||
@@ -58,10 +58,10 @@ import { parseSearchQuery } from '../../core/search/search-parser';
|
||||
|
||||
<!-- 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">
|
||||
<div class="flex flex-col gap-3 p-4 border-b border-border dark:border-border bg-bg-muted dark:bg-card">
|
||||
<!-- 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>
|
||||
<span class="text-sm text-text-main dark:text-main">Collapse results</span>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -69,14 +69,14 @@ import { parseSearchQuery } from '../../core/search/search-parser';
|
||||
(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 class="w-11 h-6 bg-muted 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-card 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>
|
||||
<span class="text-sm text-text-main dark:text-main">Show more context</span>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -84,14 +84,14 @@ import { parseSearchQuery } from '../../core/search/search-parser';
|
||||
(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 class="w-11 h-6 bg-muted 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-card 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>
|
||||
<span class="text-sm text-text-main dark:text-main">Explain search terms</span>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -99,19 +99,19 @@ import { parseSearchQuery } from '../../core/search/search-parser';
|
||||
(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 class="w-11 h-6 bg-muted 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-card rounded-full transition-transform peer-checked:translate-x-5"></div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
<!-- Explain search terms panel -->
|
||||
@if (explainSearchTerms && currentQuery().trim()) {
|
||||
<div class="p-4 border-b border-border dark:border-gray-700 bg-bg-primary/60 dark:bg-gray-900/60">
|
||||
<div class="p-4 border-b border-border dark:border-border bg-bg-primary/60 dark:bg-main/60">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wide text-text-muted mb-2">Match all of:</h4>
|
||||
<ul class="ml-1 space-y-1 text-xs">
|
||||
@for (line of explanationLines(); track $index) {
|
||||
<li class="text-text-muted dark:text-gray-400">{{ line }}</li>
|
||||
<li class="text-text-muted dark:text-muted">{{ line }}</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -123,11 +123,11 @@ import { parseSearchQuery } from '../../core/search/search-parser';
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-accent dark:border-blue-400"></div>
|
||||
<p class="text-sm text-text-muted dark:text-gray-400">Searching...</p>
|
||||
<p class="text-sm text-text-muted dark:text-muted">Searching...</p>
|
||||
</div>
|
||||
</div>
|
||||
} @else if (hasSearched() && results().length === 0) {
|
||||
<div class="flex flex-col items-center justify-center h-full text-text-muted dark:text-gray-400 p-8">
|
||||
<div class="flex flex-col items-center justify-center h-full text-text-muted dark:text-muted p-8">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mb-4 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
@@ -144,7 +144,7 @@ import { parseSearchQuery } from '../../core/search/search-parser';
|
||||
(noteOpen)="onNoteOpen($event)"
|
||||
/>
|
||||
} @else {
|
||||
<div class="flex flex-col items-center justify-center h-full text-text-muted dark:text-gray-400 p-8">
|
||||
<div class="flex flex-col items-center justify-center h-full text-text-muted dark:text-muted p-8">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mb-4 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
|
||||
@@ -35,17 +35,17 @@ type NavigationItem =
|
||||
@if (isOpen()) {
|
||||
<div
|
||||
#popover
|
||||
class="absolute top-full left-0 right-0 mt-1 bg-bg-primary dark:bg-gray-800 border border-border dark:border-gray-700 rounded-xl shadow-2xl z-50 overflow-hidden max-h-[360px]"
|
||||
class="absolute top-full left-0 right-0 mt-1 bg-bg-primary dark:bg-card border border-border dark:border-border rounded-xl shadow-2xl z-50 overflow-hidden max-h-[360px]"
|
||||
style="z-index: 9999;"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
<!-- Search Options -->
|
||||
<div class="p-3 border-b border-border dark:border-gray-700">
|
||||
<div class="p-3 border-b border-border dark:border-border">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wide text-text-main dark:text-gray-100">Search options</h4>
|
||||
<button
|
||||
(click)="showHelp = !showHelp"
|
||||
class="p-1 rounded-full hover:bg-bg-muted dark:hover:bg-gray-700 transition-colors"
|
||||
class="p-1 rounded-full hover:bg-bg-muted dark:hover:bg-surface2 transition-colors"
|
||||
title="Show help"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -55,15 +55,15 @@ type NavigationItem =
|
||||
</div>
|
||||
|
||||
@if (showHelp) {
|
||||
<div class="mb-2 p-2 bg-bg-muted dark:bg-gray-900 rounded-lg text-[11px] space-y-1.5">
|
||||
<div class="mb-2 p-2 bg-bg-muted dark:bg-main rounded-lg text-[11px] space-y-1.5">
|
||||
<div><strong>Operators:</strong></div>
|
||||
<div class="grid grid-cols-2 gap-1.5 text-text-muted dark:text-gray-400">
|
||||
<div><code class="px-1 py-0.5 bg-bg-primary dark:bg-gray-800 rounded">OR</code> Either term</div>
|
||||
<div><code class="px-1 py-0.5 bg-bg-primary dark:bg-gray-800 rounded">-term</code> Exclude</div>
|
||||
<div><code class="px-1 py-0.5 bg-bg-primary dark:bg-gray-800 rounded">"phrase"</code> Exact match</div>
|
||||
<div><code class="px-1 py-0.5 bg-bg-primary dark:bg-gray-800 rounded">term*</code> Wildcard</div>
|
||||
<div class="grid grid-cols-2 gap-1.5 text-text-muted dark:text-muted">
|
||||
<div><code class="px-1 py-0.5 bg-bg-primary dark:bg-card rounded">OR</code> Either term</div>
|
||||
<div><code class="px-1 py-0.5 bg-bg-primary dark:bg-card rounded">-term</code> Exclude</div>
|
||||
<div><code class="px-1 py-0.5 bg-bg-primary dark:bg-card rounded">"phrase"</code> Exact match</div>
|
||||
<div><code class="px-1 py-0.5 bg-bg-primary dark:bg-card rounded">term*</code> Wildcard</div>
|
||||
</div>
|
||||
<div><strong>Combine:</strong> <code class="px-1 py-0.5 bg-bg-primary dark:bg-gray-800 rounded">tag:#todo OR tag:#urgent</code></div>
|
||||
<div><strong>Combine:</strong> <code class="px-1 py-0.5 bg-bg-primary dark:bg-card rounded">tag:#todo OR tag:#urgent</code></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -71,20 +71,20 @@ type NavigationItem =
|
||||
<button
|
||||
*ngFor="let option of searchOptions(); let i = index"
|
||||
(click)="insertOption(option.prefix)"
|
||||
class="w-full text-left px-2.5 py-1.5 rounded-lg hover:bg-bg-muted dark:hover:bg-gray-700 transition-colors group text-sm"
|
||||
class="w-full text-left px-2.5 py-1.5 rounded-lg hover:bg-bg-muted dark:hover:bg-surface2 transition-colors group text-sm"
|
||||
[ngClass]="{
|
||||
'bg-bg-muted': isSelected('option', i),
|
||||
'dark:bg-gray-700': isSelected('option', i)
|
||||
'dark:bg-surface2': isSelected('option', i)
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="text-xs font-mono text-accent dark:text-blue-400 whitespace-nowrap">{{ option.prefix }}</code>
|
||||
<div class="flex-1 min-w-0 text-[13px] text-text-muted dark:text-gray-400 group-hover:text-text-main dark:group-hover:text-gray-100">
|
||||
<div class="flex-1 min-w-0 text-[13px] text-text-muted dark:text-muted group-hover:text-text-main dark:group-hover:text-gray-100">
|
||||
{{ option.description }}
|
||||
</div>
|
||||
<span
|
||||
*ngIf="showExamples && option.example"
|
||||
class="text-[11px] text-text-muted dark:text-gray-500 font-mono ml-2 truncate"
|
||||
class="text-[11px] text-text-muted dark:text-muted font-mono ml-2 truncate"
|
||||
>
|
||||
{{ option.example }}
|
||||
</span>
|
||||
@@ -95,12 +95,12 @@ type NavigationItem =
|
||||
|
||||
<!-- History -->
|
||||
@if (history().length > 0) {
|
||||
<div class="p-3 border-b border-border dark:border-gray-700">
|
||||
<div class="p-3 border-b border-border dark:border-border">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wide text-text-main dark:text-gray-100">History</h4>
|
||||
<button
|
||||
(click)="clearHistory()"
|
||||
class="text-[11px] text-text-muted dark:text-gray-400 hover:text-accent dark:hover:text-blue-400 transition-colors"
|
||||
class="text-[11px] text-text-muted dark:text-muted hover:text-accent dark:hover:text-blue-400 transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
@@ -109,13 +109,13 @@ type NavigationItem =
|
||||
<button
|
||||
*ngFor="let item of history(); let i = index"
|
||||
(click)="selectHistoryItem(item)"
|
||||
class="w-full text-left px-2.5 py-1.5 rounded-lg hover:bg-bg-muted dark:hover:bg-gray-700 transition-colors group text-sm"
|
||||
class="w-full text-left px-2.5 py-1.5 rounded-lg hover:bg-bg-muted dark:hover:bg-surface2 transition-colors group text-sm"
|
||||
[ngClass]="{
|
||||
'bg-bg-muted': isSelected('history', i),
|
||||
'dark:bg-gray-700': isSelected('history', i)
|
||||
'dark:bg-surface2': isSelected('history', i)
|
||||
}"
|
||||
>
|
||||
<span class="block truncate text-[13px] text-text-muted dark:text-gray-300 group-hover:text-text-main dark:group-hover:text-gray-100 font-mono">
|
||||
<span class="block truncate text-[13px] text-text-muted dark:text-main group-hover:text-text-main dark:group-hover:text-gray-100 font-mono">
|
||||
{{ item }}
|
||||
</span>
|
||||
</button>
|
||||
@@ -133,13 +133,13 @@ type NavigationItem =
|
||||
<button
|
||||
*ngFor="let suggestion of suggestions(); let i = index"
|
||||
(click)="insertSuggestion(suggestion)"
|
||||
class="w-full text-left px-2.5 py-1.5 rounded-lg hover:bg-bg-muted dark:hover:bg-gray-700 transition-colors text-sm"
|
||||
class="w-full text-left px-2.5 py-1.5 rounded-lg hover:bg-bg-muted dark:hover:bg-surface2 transition-colors text-sm"
|
||||
[ngClass]="{
|
||||
'bg-bg-muted': isSelected('suggestion', i),
|
||||
'dark:bg-gray-700': isSelected('suggestion', i)
|
||||
'dark:bg-surface2': isSelected('suggestion', i)
|
||||
}"
|
||||
>
|
||||
<span class="block truncate text-[13px] text-text-main dark:text-gray-200">{{ suggestion }}</span>
|
||||
<span class="block truncate text-[13px] text-text-main dark:text-main">{{ suggestion }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,14 +45,14 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
imports: [CommonModule, FormsModule],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="flex flex-col h-full bg-bg-primary dark:bg-gray-900">
|
||||
<div class="flex flex-col h-full bg-bg-primary dark:bg-main">
|
||||
<!-- Results header -->
|
||||
<div class="flex items-center justify-between p-4 border-b border-border dark:border-gray-700">
|
||||
<div class="flex items-center justify-between p-4 border-b border-border dark:border-border">
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="text-sm font-semibold text-text-main dark:text-gray-100">
|
||||
{{ totalResults() }} {{ totalResults() === 1 ? 'result' : 'results' }}
|
||||
@if (totalMatches() > 0) {
|
||||
<span class="text-text-muted dark:text-gray-400">
|
||||
<span class="text-text-muted dark:text-muted">
|
||||
({{ totalMatches() }} {{ totalMatches() === 1 ? 'match' : 'matches' }})
|
||||
</span>
|
||||
}
|
||||
@@ -61,11 +61,11 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
|
||||
<!-- Sort options -->
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-xs text-text-muted dark:text-gray-400">Sort:</label>
|
||||
<label class="text-xs text-text-muted dark:text-muted">Sort:</label>
|
||||
<select
|
||||
[(ngModel)]="sortBy"
|
||||
(change)="onSortChange()"
|
||||
class="text-xs px-2 py-1 rounded border border-border dark:border-gray-600 bg-bg-primary dark:bg-gray-800 text-text-main dark:text-gray-100"
|
||||
class="text-xs px-2 py-1 rounded border border-border dark:border-border bg-bg-primary dark:bg-card text-text-main dark:text-gray-100"
|
||||
>
|
||||
<option value="relevance">Relevance</option>
|
||||
<option value="name">Name</option>
|
||||
@@ -77,7 +77,7 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
<!-- Results list -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
@if (sortedGroups().length === 0) {
|
||||
<div class="flex flex-col items-center justify-center h-full text-text-muted dark:text-gray-400 p-8">
|
||||
<div class="flex flex-col items-center justify-center h-full text-text-muted dark:text-muted p-8">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mb-4 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
@@ -86,7 +86,7 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
} @else {
|
||||
<div class="divide-y divide-border dark:divide-gray-700">
|
||||
@for (group of sortedGroups(); track group.noteId) {
|
||||
<div class="hover:bg-bg-muted dark:hover:bg-gray-800 transition-colors">
|
||||
<div class="hover:bg-bg-muted dark:hover:bg-card transition-colors">
|
||||
<!-- File header -->
|
||||
<div
|
||||
class="flex items-center justify-between p-3 cursor-pointer"
|
||||
@@ -96,7 +96,7 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
<!-- Expand/collapse icon -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 text-text-muted dark:text-gray-400 transition-transform"
|
||||
class="h-4 w-4 text-text-muted dark:text-muted transition-transform"
|
||||
[class.rotate-90]="group.isExpanded"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -115,7 +115,7 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
<div class="text-sm font-medium text-text-main dark:text-gray-100 truncate">
|
||||
{{ group.fileName }}
|
||||
</div>
|
||||
<div class="text-xs text-text-muted dark:text-gray-400 truncate">
|
||||
<div class="text-xs text-text-muted dark:text-muted truncate">
|
||||
{{ group.filePath }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,23 +143,23 @@ type SortOption = 'relevance' | 'name' | 'modified';
|
||||
<div class="px-3 pb-3 pl-11 space-y-2">
|
||||
@for (match of group.matches; track $index) {
|
||||
<div
|
||||
class="p-2 rounded bg-bg-muted dark:bg-gray-800 hover:bg-bg-secondary dark:hover:bg-gray-700 cursor-pointer transition-colors"
|
||||
class="p-2 rounded bg-bg-muted dark:bg-card hover:bg-bg-secondary dark:hover:bg-surface2 cursor-pointer transition-colors"
|
||||
(click)="openNote(group.noteId, $event, match.line)"
|
||||
>
|
||||
<!-- Match type badge -->
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xs px-1.5 py-0.5 rounded bg-bg-primary dark:bg-gray-900 text-text-muted dark:text-gray-400 font-mono">
|
||||
<span class="text-xs px-1.5 py-0.5 rounded bg-bg-primary dark:bg-main text-text-muted dark:text-muted font-mono">
|
||||
{{ match.type }}
|
||||
</span>
|
||||
@if (match.line) {
|
||||
<span class="text-xs text-text-muted dark:text-gray-500">
|
||||
<span class="text-xs text-text-muted dark:text-muted">
|
||||
Line {{ match.line }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Match context with highlighting -->
|
||||
<div class="text-sm text-text-main dark:text-gray-200 font-mono leading-relaxed whitespace-pre-wrap">
|
||||
<div class="text-sm text-text-main dark:text-main font-mono leading-relaxed whitespace-pre-wrap">
|
||||
<span [innerHTML]="highlightMatch(match)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -113,7 +113,7 @@ interface MetadataEntry {
|
||||
[attr.title]="tocOpen() ? 'Cacher sommaire' : 'Afficher sommaire'"
|
||||
[attr.aria-label]="tocOpen() ? 'Cacher sommaire' : 'Afficher sommaire'"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" [ngClass]="tocOpen() ? 'text-accent' : 'text-text-muted'"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M13 8h5"/><path d="M13 12h5"/><path d="M13 16h5"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" [ngClass]="tocOpen() ? 'toc-toggle--active' : 'toc-toggle--idle'"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M13 8h5"/><path d="M13 12h5"/><path d="M13 16h5"/></svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -139,6 +139,13 @@ interface MetadataEntry {
|
||||
</button>
|
||||
@if (menuOpen()) {
|
||||
<div class="absolute right-0 mt-2 w-56 rounded-md border border-border bg-card shadow-subtle not-prose z-10">
|
||||
<button type="button" class="block w-full text-left px-3 py-2 hover:bg-muted" (click)="parametersRequested.emit(); closeMenu()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="inline h-4 w-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M12 1v6m0 6v6m-6-6h6m6 0h-6M4.93 4.93l4.24 4.24m5.66 5.66l4.24 4.24m-4.24-14.14l4.24-4.24M4.93 19.07l4.24-4.24"/>
|
||||
</svg>
|
||||
Parameters
|
||||
</button>
|
||||
<button type="button" class="block w-full text-left px-3 py-2 hover:bg-muted" (click)="legacyRequested.emit(); closeMenu()">🔧 Legacy</button>
|
||||
</div>
|
||||
}
|
||||
@@ -224,7 +231,7 @@ interface MetadataEntry {
|
||||
{{ getAuthorFromFrontmatter() ?? note().author ?? 'Auteur inconnu' }}
|
||||
</span>
|
||||
@if (hasState('favoris')) {
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('favoris') ? 'text-rose-500' : 'text-gray-400'" title="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}" role="img" aria-label="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}">
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('favoris') ? 'text-rose-500' : 'text-muted'" title="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}" role="img" aria-label="{{ state('favoris') ? 'Ajouté aux favoris' : 'Non favori' }}">
|
||||
@if (state('favoris')) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<path d="M19 14.5c1.5-1.5 2-4 0-6s-5-2-7 1c-2-3-5-3.5-7-1s-1.5 4.5 0 6l7 6z" />
|
||||
@@ -237,7 +244,7 @@ interface MetadataEntry {
|
||||
</span>
|
||||
}
|
||||
@if (hasState('publish')) {
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('publish') ? 'text-green-500' : 'text-gray-400'" title="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}" role="img" aria-label="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}">
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('publish') ? 'text-green-500' : 'text-muted'" title="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}" role="img" aria-label="{{ state('publish') ? 'Publié sur le web' : 'Non publié' }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M2 12h20" />
|
||||
@@ -246,7 +253,7 @@ interface MetadataEntry {
|
||||
</span>
|
||||
}
|
||||
@if (hasState('draft')) {
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('draft') ? 'text-yellow-500' : 'text-gray-400'" title="{{ state('draft') ? 'Brouillon actif' : 'Pas un brouillon' }}" role="img" aria-label="{{ state('draft') ? 'Brouillon actif' : 'Pas un brouillon' }}">
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('draft') ? 'text-yellow-500' : 'text-muted'" title="{{ state('draft') ? 'Brouillon actif' : 'Pas un brouillon' }}" role="img" aria-label="{{ state('draft') ? 'Brouillon actif' : 'Pas un brouillon' }}">
|
||||
@if (state('draft')) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 3h18v4H3z" />
|
||||
@@ -261,7 +268,7 @@ interface MetadataEntry {
|
||||
</span>
|
||||
}
|
||||
@if (hasState('template')) {
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('template') ? 'text-amber-500' : 'text-gray-400'" title="{{ state('template') ? 'Modèle' : 'Non modèle' }}" role="img" aria-label="{{ state('template') ? 'Modèle' : 'Non modèle' }}">
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('template') ? 'text-amber-500' : 'text-muted'" title="{{ state('template') ? 'Modèle' : 'Non modèle' }}" role="img" aria-label="{{ state('template') ? 'Modèle' : 'Non modèle' }}">
|
||||
@if (state('template')) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="4" width="18" height="14" rx="2" />
|
||||
@@ -276,7 +283,7 @@ interface MetadataEntry {
|
||||
</span>
|
||||
}
|
||||
@if (hasState('task')) {
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('task') ? 'text-indigo-500' : 'text-gray-400'" title="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}" role="img" aria-label="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}">
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('task') ? 'text-indigo-500' : 'text-muted'" title="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}" role="img" aria-label="{{ state('task') ? 'Tâche' : 'Pas une tâche' }}">
|
||||
@if (state('task')) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="4" width="18" height="16" rx="2" />
|
||||
@@ -290,7 +297,7 @@ interface MetadataEntry {
|
||||
</span>
|
||||
}
|
||||
@if (hasState('private')) {
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('private') ? 'text-purple-500' : 'text-gray-400'" title="{{ state('private') ? 'Privé' : 'Public' }}" role="img" aria-label="{{ state('private') ? 'Privé' : 'Public' }}">
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('private') ? 'text-purple-500' : 'text-muted'" title="{{ state('private') ? 'Privé' : 'Public' }}" role="img" aria-label="{{ state('private') ? 'Privé' : 'Public' }}">
|
||||
@if (state('private')) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" />
|
||||
@@ -305,7 +312,7 @@ interface MetadataEntry {
|
||||
</span>
|
||||
}
|
||||
@if (hasState('archive')) {
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('archive') ? 'text-amber-600' : 'text-gray-400'" title="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}" role="img" aria-label="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}">
|
||||
<span class="inline-flex items-center gap-1 transition-colors" [ngClass]="state('archive') ? 'text-amber-600' : 'text-muted'" title="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}" role="img" aria-label="{{ state('archive') ? 'Document archivé' : 'Document non archivé' }}">
|
||||
@if (state('archive')) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="22,12 18,12 18,8"/>
|
||||
@@ -358,6 +365,7 @@ export class NoteViewerComponent implements OnDestroy {
|
||||
addTagRequested = output<void>();
|
||||
fullScreenRequested = output<void>();
|
||||
legacyRequested = output<void>();
|
||||
parametersRequested = output<void>();
|
||||
fullScreenActive = input<boolean>(false);
|
||||
tocOpen = input<boolean>(false);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user