feat: add tag editor API endpoint and improve tag editing UI

This commit is contained in:
2025-10-17 09:14:01 -04:00
parent 4dbcf8ad81
commit e2775a3d43
12 changed files with 1133 additions and 150 deletions
@@ -15,9 +15,9 @@ import { CommonModule } from '@angular/common';
import { Note } from '../../../types';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { NotePreviewService, PreviewData } from '../../../services/note-preview.service';
import { NoteHeaderComponent } from '../../../app/features/note/components/note-header/note-header.component';
import { ClipboardService } from '../../../app/shared/services/clipboard.service';
import { ToastService } from '../../../app/shared/toast/toast.service';
import { TagsEditorComponent } from '../../../app/shared/tags-editor/tags-editor.component';
import { VaultService } from '../../../services/vault.service';
import { Subscription } from 'rxjs';
import mermaid from 'mermaid';
@@ -65,29 +65,21 @@ interface MetadataEntry {
@Component({
selector: 'app-note-viewer',
standalone: true,
imports: [CommonModule, TagsEditorComponent],
imports: [CommonModule, NoteHeaderComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="relative p-1 prose prose-lg dark:prose-invert max-w-none prose-p:leading-[1] prose-li:leading-[1] prose-blockquote:leading-[1]">
<div class="sr-only" role="status" aria-live="polite">{{ copyStatus() }}</div>
<!-- Compact Top Bar -->
<div class="flex items-center justify-between gap-2 pl-1 pr-2 py-1 mb-2 text-text-muted text-xs">
<div class="flex items-center gap-1">
<!-- Directory clickable -->
<button type="button" class="note-toolbar-icon note-toolbar-path hidden lg:inline-flex" (click)="directoryClicked.emit(getDirectoryFromPath(note().filePath))" title="Open directory">🗂️ {{ note().filePath }}</button>
<button type="button" class="note-toolbar-icon hidden lg:inline-flex" (click)="copyPath()" aria-label="Copier le chemin" title="Copier le chemin">
<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 width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16V6a2 2 0 0 1 2-2h10"/></svg>
</button>
<!-- Tags editor (read-only -> edit on click) -->
<app-tags-editor class="hidden md:flex ml-3"
[noteId]="note().id"
[tags]="note().tags"
(tagsChange)="onTagsChange($event)"
(commit)="commitTagsNow()"
></app-tags-editor>
</div>
<app-note-header class="flex-1 min-w-0"
[fullPath]="note().filePath"
[noteId]="note().id"
[tags]="note().tags ?? []"
(copyRequested)="copyPath()"
(openDirectory)="directoryClicked.emit(getDirectoryFromPath(note().filePath))"
(tagsChange)="onTagsChange($event)"
></app-note-header>
<div class="flex items-center gap-1">
<button
@@ -458,34 +450,14 @@ export class NoteViewerComponent implements OnDestroy {
});
}
private tagsSaveTimer: number | null = null;
private pendingTags: string[] | null = null;
// La sauvegarde des tags est maintenant gérée automatiquement par TagsEditorComponent
// Cette méthode met simplement à jour l'état local après sauvegarde
onTagsChange(next: string[]): void {
this.pendingTags = next.slice();
if (this.tagsSaveTimer !== null) {
window.clearTimeout(this.tagsSaveTimer);
this.tagsSaveTimer = null;
}
this.tagsSaveTimer = window.setTimeout(() => this.commitTagsNow(), 1000);
}
async commitTagsNow(): Promise<void> {
if (!this.pendingTags) return;
const note = this.note();
if (!note) return;
const tags = this.pendingTags.slice().map(t => `${t}`.trim()).filter(Boolean);
this.pendingTags = null;
try {
const ok = await this.vault.updateNoteTags(note.id, tags);
if (ok) {
this.toast.success('Tags enregistrés');
} else {
this.toast.error('Échec d’enregistrement des tags');
}
} catch {
this.toast.error('Échec d’enregistrement des tags');
}
// Mettre à jour l'état local (la note sera rafraîchie par le vault service)
// Pas besoin de sauvegarder ici, c'est déjà fait par TagsEditorComponent
}
async copyPath(): Promise<void> {
@@ -493,15 +465,15 @@ export class NoteViewerComponent implements OnDestroy {
try {
const ok = await this.clipboard.write(path);
if (ok) {
this.toast.success(path ? `Path copié — « ${path} »` : 'Path copié');
this.copyStatus.set('Path copié');
this.toast.success('📋 Chemin copié');
this.copyStatus.set('Chemin copié');
} else {
this.toast.error('Impossible de copier le path. Vérifiez les permissions du navigateur.');
this.copyStatus.set('Échec de la copie du path');
this.toast.error('Impossible de copier le chemin. Vérifiez les permissions du navigateur.');
this.copyStatus.set('Échec de la copie du chemin');
}
} catch {
this.toast.error('Impossible de copier le path. Vérifiez les permissions du navigateur.');
this.copyStatus.set('Échec de la copie du path');
this.toast.error('Impossible de copier le chemin. Vérifiez les permissions du navigateur.');
this.copyStatus.set('Échec de la copie du chemin');
}
}