feat: add Meilisearch backend integration with Docker Compose setup and Excalidraw support
This commit is contained in:
+69
-106
@@ -175,108 +175,36 @@
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
@switch (activeView()) {
|
||||
@case ('files') {
|
||||
<div class="px-2 pt-2 pb-2">
|
||||
<input
|
||||
type="search"
|
||||
[value]="fileExplorerFilter()"
|
||||
(input)="fileExplorerFilter.set(($any($event.target).value || '').toString())"
|
||||
placeholder="Filtrer fichiers et dossiers..."
|
||||
class="w-full rounded-full border border-border bg-card px-3 py-1.5 text-sm text-text-main placeholder:text-text-muted"
|
||||
aria-label="Filtrer arborescence"
|
||||
/>
|
||||
</div>
|
||||
<app-file-explorer
|
||||
[nodes]="filteredFileTree()"
|
||||
[nodes]="filteredExplorerTree()"
|
||||
[selectedNoteId]="selectedNoteId()"
|
||||
(fileSelected)="selectNote($event)"
|
||||
(fileSelected)="onFileNodeSelected($event)"
|
||||
></app-file-explorer>
|
||||
}
|
||||
@case ('tags') {
|
||||
<app-tags-view [tags]="filteredTags()" (tagSelected)="handleTagClick($event)"></app-tags-view>
|
||||
<app-tags-view [tags]="allTags()" (tagSelected)="handleTagClick($event)"></app-tags-view>
|
||||
}
|
||||
|
||||
@case ('search') {
|
||||
<div class="space-y-4 p-3">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="relative">
|
||||
<app-search-input-with-assistant
|
||||
[value]="sidebarSearchTerm()"
|
||||
(valueChange)="updateSearchTerm($event)"
|
||||
(submit)="onSearchSubmit($event)"
|
||||
[placeholder]="'Rechercher dans la voûte...'"
|
||||
[context]="'vault-sidebar'"
|
||||
[showSearchIcon]="true"
|
||||
[showExamples]="false"
|
||||
[inputClass]="'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'"
|
||||
/>
|
||||
</div>
|
||||
@if (activeTagDisplay(); as tagDisplay) {
|
||||
<div class="flex items-center justify-between rounded-lg border border-border bg-card px-3 py-2">
|
||||
<div class="flex items-center gap-2 text-sm font-small text-text-main">
|
||||
<span>🔖</span>
|
||||
<span class="truncate">{{ tagDisplay }}</span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-ghost" (click)="clearTagFilter()">Effacer</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between px-2">
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wide text-text-muted">Résultats</h3>
|
||||
<span class="text-xs text-text-muted">{{ searchResults().length }}</span>
|
||||
</div>
|
||||
@if (searchResults().length > 0) {
|
||||
<ul class="space-y-1">
|
||||
@for (note of searchResults(); track note.id) {
|
||||
<li
|
||||
(click)="selectNote(note.id)"
|
||||
class="cursor-pointer rounded-lg border border-transparent bg-card px-3 py-2 transition hover:border-border hover:bg-bg-muted"
|
||||
[ngClass]="{ 'border-border bg-bg-muted': selectedNoteId() === note.id }"
|
||||
>
|
||||
<div class="truncate text-sm font-semibold text-text-main">{{ note.title }}</div>
|
||||
<div class="truncate text-xs text-text-muted">{{ note.content.substring(0, 100) }}</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
} @else {
|
||||
<p class="rounded-lg border border-dashed border-border px-3 py-3 text-sm text-text-muted">Aucun résultat pour cette recherche.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (calendarSelectionLabel() || calendarSearchState() !== 'idle' || calendarResults().length > 0 || calendarSearchError()) {
|
||||
<div class="space-y-3 rounded-xl border border-border bg-card p-3 shadow-subtle">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-text-main">Résultats du calendrier</h3>
|
||||
@if (calendarSelectionLabel(); as selectionLabel) {
|
||||
<p class="text-xs text-text-muted">{{ selectionLabel }}</p>
|
||||
}
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-sm btn-ghost"
|
||||
(click)="clearCalendarResults()"
|
||||
aria-label="Effacer les résultats du calendrier"
|
||||
>
|
||||
Effacer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (calendarSearchState() === 'loading') {
|
||||
<div class="rounded-lg bg-bg-muted px-3 py-2 text-xs text-text-muted">Recherche en cours...</div>
|
||||
} @else if (calendarSearchError(); as calError) {
|
||||
<div class="rounded-lg border border-red-500/40 bg-red-500/10 px-3 py-2 text-xs text-red-500 dark:border-red-500/30 dark:bg-red-500/15 dark:text-red-400">{{ calError }}</div>
|
||||
} @else if (calendarResults().length === 0) {
|
||||
<div class="rounded-lg bg-bg-muted px-3 py-2 text-xs text-text-muted">Sélectionnez une date dans le calendrier pour voir les notes correspondantes.</div>
|
||||
} @else {
|
||||
<ul class="space-y-2">
|
||||
@for (file of calendarResults(); track file.id) {
|
||||
<li>
|
||||
<button
|
||||
(click)="selectNote(file.id)"
|
||||
class="w-full rounded-lg border border-transparent bg-card px-3 py-2 text-left transition hover:border-border hover:bg-bg-muted"
|
||||
>
|
||||
<div class="truncate text-sm font-semibold text-text-main">{{ file.title }}</div>
|
||||
<div class="mt-1 flex flex-wrap gap-2 text-xs text-text-muted">
|
||||
<span>Créé : {{ file.createdAt | date:'mediumDate' }}</span>
|
||||
<span>Modifié : {{ file.updatedAt | date:'mediumDate' }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="h-full p-2">
|
||||
<app-search-panel
|
||||
[placeholder]="'Rechercher dans la voûte...'"
|
||||
[context]="'vault-sidebar'"
|
||||
[initialQuery]="sidebarSearchTerm()"
|
||||
(noteOpen)="selectNote($event.noteId)"
|
||||
(searchTermChange)="onSidebarSearchTermChange($event)"
|
||||
(optionsChange)="onSidebarOptionsChange($event)"
|
||||
></app-search-panel>
|
||||
</div>
|
||||
}
|
||||
@case ('calendar') {
|
||||
@@ -363,6 +291,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden items-center gap-2 lg:flex">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-icon btn-ghost"
|
||||
(click)="createNewDrawing()"
|
||||
aria-label="Nouveau dessin Excalidraw"
|
||||
title="Nouveau dessin"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
|
||||
<path d="M12 5v14" />
|
||||
<path d="M5 12h14" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-icon btn-ghost disabled:opacity-40 disabled:pointer-events-none"
|
||||
@@ -440,6 +380,18 @@
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between lg:gap-6">
|
||||
<div class="flex items-center gap-2 lg:hidden">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-icon btn-ghost"
|
||||
(click)="createNewDrawing()"
|
||||
aria-label="Nouveau dessin Excalidraw"
|
||||
title="Nouveau dessin"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
|
||||
<path d="M12 5v14" />
|
||||
<path d="M5 12h14" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-icon btn-ghost disabled:opacity-40 disabled:pointer-events-none"
|
||||
@@ -506,6 +458,7 @@
|
||||
[context]="'vault-header'"
|
||||
[showSearchIcon]="true"
|
||||
[inputClass]="'w-full rounded-full border border-border bg-bg-muted/70 pl-11 pr-4 py-2.5 text-sm text-text-main placeholder:text-text-muted shadow-subtle focus:outline-none focus:ring-2 focus:ring-ring'"
|
||||
(optionsChange)="onHeaderSearchOptionsChange($event)"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@@ -527,19 +480,29 @@
|
||||
</app-graph-view-container-v2>
|
||||
</div>
|
||||
} @else {
|
||||
@if (selectedNote(); as note) {
|
||||
<app-note-viewer
|
||||
[note]="note"
|
||||
[noteHtmlContent]="renderedNoteContent()"
|
||||
[allNotes]="vaultService.allNotes()"
|
||||
(noteLinkClicked)="selectNote($event)"
|
||||
(tagClicked)="handleTagClick($event)"
|
||||
(wikiLinkActivated)="handleWikiLink($event)"
|
||||
></app-note-viewer>
|
||||
@if (activeView() === 'drawings') {
|
||||
@if (currentDrawingPath()) {
|
||||
<app-drawings-editor [path]="currentDrawingPath()!"></app-drawings-editor>
|
||||
} @else {
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<p class="text-text-muted">Aucun dessin sélectionné</p>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<p class="text-text-muted">Sélectionnez une note pour commencer</p>
|
||||
</div>
|
||||
@if (selectedNote(); as note) {
|
||||
<app-note-viewer
|
||||
[note]="note"
|
||||
[noteHtmlContent]="renderedNoteContent()"
|
||||
[allNotes]="vaultService.allNotes()"
|
||||
(noteLinkClicked)="selectNote($event)"
|
||||
(tagClicked)="handleTagClick($event)"
|
||||
(wikiLinkActivated)="handleWikiLink($event)"
|
||||
></app-note-viewer>
|
||||
} @else {
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<p class="text-text-muted">Sélectionnez une note pour commencer</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user