chore: update Angular cache and TypeScript build info

This commit is contained in:
2025-09-29 23:22:47 -04:00
parent b4a706930b
commit 67407f68f0
85 changed files with 10400 additions and 492 deletions
@@ -0,0 +1,136 @@
<div class="bookmarks-panel flex flex-col h-full bg-white dark:bg-gray-900">
<!-- Header -->
<div class="bookmarks-header flex-shrink-0 p-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Bookmarks</h2>
<div class="flex items-center gap-2">
<button
type="button"
class="btn btn-sm btn-primary"
(click)="createGroup()"
title="Créer un groupe">
+ Group
</button>
@if (saving()) {
<span class="text-xs text-blue-600 dark:text-blue-400 animate-pulse">Saving...</span>
}
</div>
</div>
<!-- Search -->
<div class="relative">
<input
type="text"
[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"
/>
@if (searchTerm()) {
<button
(click)="onSearchChange('')"
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
title="Clear search">
✕
</button>
}
</div>
</div>
<!-- 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="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
} @else if (error()) {
<div class="p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md">
<div class="flex items-start gap-2">
<span class="text-red-600 dark:text-red-400 font-semibold">Error:</span>
<span class="text-sm text-red-700 dark:text-red-300 flex-1">{{ error() }}</span>
<button
(click)="clearError()"
class="text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-200"
title="Dismiss">
✕
</button>
</div>
</div>
} @else if (isEmpty()) {
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
<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>
<div
class="bookmarks-tree mt-4 border-2 border-dashed border-blue-500/60 dark:border-blue-400/60 bg-blue-500/10 dark:bg-blue-400/10 text-blue-600 dark:text-blue-300 rounded-md min-h-[80px] flex items-center justify-center"
cdkDropList
[cdkDropListData]="displayItems()"
cdkDropListId="root"
[cdkDropListConnectedTo]="getDropListConnections('root')"
cdkDropListOrientation="vertical"
[cdkDropListDisabled]="dragDisabled"
(cdkDropListDropped)="handleRootDrop($event)">
<span class="text-sm font-medium">Drop items here</span>
</div>
} @else {
<div
class="bookmarks-tree"
cdkDropList
[cdkDropListData]="displayItems()"
cdkDropListId="root"
[cdkDropListConnectedTo]="getDropListConnections('root')"
cdkDropListOrientation="vertical"
[cdkDropListDisabled]="dragDisabled"
(cdkDropListDropped)="handleRootDrop($event)">
@if (!dragDisabled) {
<div class="mb-2 rounded-md border border-dashed border-blue-500/40 dark:border-blue-400/40 bg-blue-500/5 dark:bg-blue-400/5 px-3 py-2 text-xs font-medium text-blue-600 dark:text-blue-300 text-center">
Drop here to move to root
</div>
}
@for (node of displayItems(); track node.ctime) {
<app-bookmark-item
cdkDrag
[cdkDragDisabled]="dragDisabled"
[cdkDragData]="{ ctime: node.ctime, parentCtime: null }"
[node]="node"
[level]="0"
[dragDisabled]="dragDisabled"
[dropListIds]="dropListIds()"
(bookmarkClick)="onBookmarkClick($event)"
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>
}
</div>
}
</div>
<!-- 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">
The bookmarks file has been modified externally. What would you like to do?
</p>
<div class="flex gap-3">
<button
(click)="resolveConflictReload()"
class="flex-1 px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors">
Reload from file
</button>
<button
(click)="resolveConflictOverwrite()"
class="flex-1 px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md transition-colors">
Overwrite file
</button>
</div>
</div>
</div>
}
<!-- Modals placeholders - will be implemented separately -->
</div>
@@ -0,0 +1,46 @@
.bookmarks-panel {
height: 100%;
display: flex;
flex-direction: column;
}
.bookmarks-tree {
display: flex;
flex-direction: column;
gap: 1px;
}
/* Smooth scrolling */
.bookmarks-body {
scroll-behavior: smooth;
}
/* Custom scrollbar for dark mode */
.bookmarks-body::-webkit-scrollbar {
width: 8px;
}
.bookmarks-body::-webkit-scrollbar-track {
background-color: #f3f4f6;
}
:host-context(.dark) .bookmarks-body::-webkit-scrollbar-track {
background-color: #1f2937;
}
.bookmarks-body::-webkit-scrollbar-thumb {
background-color: #d1d5db;
border-radius: 9999px;
}
:host-context(.dark) .bookmarks-body::-webkit-scrollbar-thumb {
background-color: #4b5563;
}
.bookmarks-body::-webkit-scrollbar-thumb:hover {
background-color: #9ca3af;
}
:host-context(.dark) .bookmarks-body::-webkit-scrollbar-thumb:hover {
background-color: #6b7280;
}
@@ -0,0 +1,149 @@
/**
* Bookmarks Panel Component - Main UI for managing bookmarks
*/
import {
Component,
ChangeDetectionStrategy,
inject,
signal,
computed,
Output,
EventEmitter,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DragDropModule, CdkDragDrop } from '@angular/cdk/drag-drop';
import type { BookmarkNode } from '../../core/bookmarks/types';
import { BookmarksService } from '../../core/bookmarks/bookmarks.service';
import { BookmarkItemComponent } from '../bookmark-item/bookmark-item.component';
@Component({
selector: 'app-bookmarks-panel',
imports: [CommonModule, FormsModule, DragDropModule, BookmarkItemComponent],
templateUrl: './bookmarks-panel.component.html',
styleUrls: ['./bookmarks-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BookmarksPanelComponent {
private readonly bookmarksService = inject(BookmarksService);
@Output() bookmarkClick = new EventEmitter<BookmarkNode>();
// Local state
readonly searchTerm = signal('');
// Service state
readonly doc = this.bookmarksService.doc;
readonly filteredDoc = this.bookmarksService.filteredDoc;
readonly saving = this.bookmarksService.saving;
readonly loading = this.bookmarksService.loading;
readonly error = this.bookmarksService.error;
readonly conflictInfo = this.bookmarksService.conflictInfo;
readonly displayDoc = computed(() =>
this.searchTerm().trim() ? this.filteredDoc() : this.doc()
);
readonly displayItems = computed(() => this.displayDoc().items ?? []);
readonly dragDisabledSignal = computed(() => this.searchTerm().trim().length > 0);
readonly isEmpty = computed(() => this.displayItems().length === 0);
readonly dropListIds = computed(() => {
const ids: string[] = ['root'];
const collect = (items: BookmarkNode[]) => {
for (const item of items) {
if (item.type === 'group') {
ids.push(`group-${item.ctime}`);
if (item.items?.length) {
collect(item.items);
}
}
}
};
collect(this.displayItems());
return ids;
});
readonly trackNode = (index: number, node: BookmarkNode) => node.ctime ?? index;
onSearchChange(term: string): void {
this.searchTerm.set(term);
this.bookmarksService.setFilterTerm(term);
}
clearError(): void {
this.bookmarksService.clearError();
}
get dragDisabled(): boolean {
return this.dragDisabledSignal();
}
getDropListConnections(id: string): string[] {
return this.dropListIds().filter(existingId => existingId !== id);
}
createGroup(parentCtime: number | null = null): void {
const title = window.prompt('Nom du groupe');
if (!title) {
return;
}
this.bookmarksService.createGroup(title.trim(), parentCtime ?? null);
}
deleteGroup(ctime: number): void {
if (!window.confirm('Supprimer ce groupe et tous ses favoris ?')) {
return;
}
this.bookmarksService.deleteBookmark(ctime);
}
startCreateBookmarkInGroup(parentCtime: number): void {
const path = window.prompt('Chemin du fichier (ex: Notes/note.md)');
if (!path || !path.trim()) {
return;
}
const title = window.prompt('Titre (optionnel)')?.trim();
this.bookmarksService.createFileBookmark(path.trim(), title || undefined, parentCtime);
}
onBookmarkClick(bookmark: BookmarkNode): void {
this.bookmarkClick.emit(bookmark);
}
handleDrop(event: CdkDragDrop<BookmarkNode[]>, parentCtime: number | null): void {
if (this.dragDisabled) {
return;
}
const data = event.item.data as { ctime: number; parentCtime: number | null } | undefined;
if (!data || typeof data.ctime !== 'number') {
return;
}
// Skip if dropping into itself
if (parentCtime === data.ctime) {
return;
}
this.bookmarksService.moveBookmark(data.ctime, parentCtime, event.currentIndex);
}
handleRootDrop(event: CdkDragDrop<BookmarkNode[]>): void {
this.handleDrop(event, null);
}
async resolveConflictReload(): Promise<void> {
await this.bookmarksService.resolveConflictReload();
}
async resolveConflictOverwrite(): Promise<void> {
await this.bookmarksService.resolveConflictOverwrite();
}
}