chore: update Angular cache and TypeScript build info files

This commit is contained in:
2025-09-30 21:37:49 -04:00
parent 81d7f1f7c2
commit fe4c968367
29 changed files with 2719 additions and 98 deletions
+1
View File
@@ -16,6 +16,7 @@
[noteTitle]="selectedNote()?.title || ''"
(close)="closeBookmarkModal()"
(save)="onBookmarkSave($event)"
(delete)="onBookmarkDelete($event)"
></app-add-bookmark-modal>
}
<!-- Navigation latérale desktop -->
+7 -2
View File
@@ -10,13 +10,13 @@ import { ThemeService } from './app/core/services/theme.service';
// Components
import { FileExplorerComponent } from './components/file-explorer/file-explorer.component';
import { NoteViewerComponent, WikiLinkActivation } from './components/note-viewer/note-viewer.component';
import { NoteViewerComponent, WikiLinkActivation } from './components/tags-view/note-viewer/note-viewer.component';
import { GraphViewComponent } from './components/graph-view/graph-view.component';
import { TagsViewComponent } from './components/tags-view/tags-view.component';
import { MarkdownCalendarComponent } from './components/markdown-calendar/markdown-calendar.component';
import { RawViewOverlayComponent } from './shared/overlays/raw-view-overlay.component';
import { BookmarksPanelComponent } from './components/bookmarks-panel/bookmarks-panel.component';
import { AddBookmarkModalComponent, type BookmarkFormData } from './components/add-bookmark-modal/add-bookmark-modal.component';
import { AddBookmarkModalComponent, type BookmarkFormData, type BookmarkDeleteEvent } from './components/add-bookmark-modal/add-bookmark-modal.component';
import { BookmarksService } from './core/bookmarks/bookmarks.service';
// Types
@@ -614,6 +614,11 @@ export class AppComponent implements OnDestroy {
this.closeBookmarkModal();
}
onBookmarkDelete(event: BookmarkDeleteEvent): void {
this.bookmarksService.removePathEverywhere(event.path);
this.closeBookmarkModal();
}
onBookmarkNavigate(bookmark: any): void {
if (bookmark.type === 'file' && bookmark.path) {
// Find note by matching filePath
@@ -63,17 +63,31 @@
</div>
<!-- Actions -->
<div class="flex items-center justify-end gap-3 mt-6">
<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">
Cancel
</button>
<button
(click)="onSave()"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors">
Save
</button>
<div class="flex items-center justify-between gap-3 mt-6">
<!-- Left side: Delete button (if bookmark exists) -->
<div>
@if (pathExistsInBookmarks()) {
<button
(click)="onDelete()"
class="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md transition-colors">
Delete
</button>
}
</div>
<!-- Right side: Cancel & Save buttons -->
<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">
Cancel
</button>
<button
(click)="onSave()"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors">
Save
</button>
</div>
</div>
</div>
</div>
@@ -23,6 +23,10 @@ export interface BookmarkFormData {
groupCtime: number | null;
}
export interface BookmarkDeleteEvent {
path: string;
}
@Component({
selector: 'app-add-bookmark-modal',
imports: [CommonModule, FormsModule],
@@ -39,6 +43,7 @@ export class AddBookmarkModalComponent {
@Output() close = new EventEmitter<void>();
@Output() save = new EventEmitter<BookmarkFormData>();
@Output() delete = new EventEmitter<BookmarkDeleteEvent>();
readonly path = signal('');
readonly title = signal('');
@@ -67,6 +72,30 @@ export class AddBookmarkModalComponent {
});
readonly isEditMode = computed(() => this.existingBookmark !== null);
/**
* Check if current path exists in bookmarks
*/
readonly pathExistsInBookmarks = computed(() => {
const currentPath = this.path();
if (!currentPath) return false;
const doc = this.bookmarksService.doc();
return this.findBookmarkByPath(doc.items, currentPath) !== null;
});
private findBookmarkByPath(items: any[], path: string): any {
for (const item of items) {
if (item.type === 'file' && item.path === path) {
return item;
}
if (item.type === 'group' && item.items) {
const found = this.findBookmarkByPath(item.items, path);
if (found) return found;
}
}
return null;
}
ngOnInit(): void {
this.path.set(this.notePath);
@@ -116,4 +145,15 @@ export class AddBookmarkModalComponent {
this.close.emit();
}
}
onDelete(): void {
const pathValue = this.path().trim();
if (!pathValue) return;
if (!confirm(`Supprimer "${pathValue}" des favoris ?`)) {
return;
}
this.delete.emit({ path: pathValue });
}
}
@@ -90,35 +90,54 @@
}
</div>
<!-- Children -->
@if (isGroup && isExpanded()) {
<!-- 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]"
class="ml-6 border-l border-gray-200 dark:border-gray-700 pl-2 min-h-[40px] transition-colors"
[class.border-blue-500]="isDraggingOver()"
[class.dark:border-blue-400]="isDraggingOver()"
[class.bg-blue-500/5]="isDraggingOver()"
[class.dark:bg-blue-400/5]="isDraggingOver()"
cdkDropList
[cdkDropListData]="children"
[cdkDropListConnectedTo]="getDropListConnections()"
[cdkDropListConnectedTo]="connectedDropLists()"
[cdkDropListDisabled]="dragDisabled"
[cdkDropListSortingDisabled]="false"
[cdkDropListId]="dropListId"
cdkDropListOrientation="vertical"
(cdkDropListDropped)="onChildDrop($event)">
@for (child of children; track trackByCtime($index, child)) {
<app-bookmark-item
cdkDrag
[cdkDragDisabled]="dragDisabled"
[cdkDragData]="{ ctime: child.ctime, parentCtime: bookmark.ctime }"
[node]="child"
[level]="level + 1"
[dragDisabled]="dragDisabled"
[dropListIds]="dropListIds"
(bookmarkClick)="bookmarkClick.emit($event)"
class="mt-1" />
}
@if (children.length === 0) {
<div class="py-2 px-3 text-xs text-gray-400 dark:text-gray-500 italic">
Drop items here
</div>
(cdkDropListDropped)="onChildDrop($event)"
(cdkDropListEntered)="onDragEntered()"
(cdkDropListExited)="onDragExited()">
<!-- Children (only show if expanded) -->
@if (isExpanded()) {
@for (child of children; track trackByCtime($index, child)) {
<app-bookmark-item
cdkDrag
[cdkDragDisabled]="dragDisabled"
[cdkDragData]="{ ctime: child.ctime, parentCtime: bookmark.ctime }"
[node]="child"
[level]="level + 1"
[dragDisabled]="dragDisabled"
[dropListIds]="dropListIds"
(bookmarkClick)="bookmarkClick.emit($event)"
class="mt-1" />
}
}
<!-- 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">
Drop items here
</div>
}
@if (!isExpanded()) {
<div class="py-1 px-2 text-xs text-gray-400 dark:text-gray-500 italic opacity-50">
Drop here ({{ children.length }} items)
</div>
}
</div>
</div>
}
</div>
@@ -11,12 +11,16 @@ import {
inject,
signal,
forwardRef,
ViewChild,
AfterViewInit,
OnDestroy,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { DragDropModule, CdkDragDrop } from '@angular/cdk/drag-drop';
import { DragDropModule, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
import type { BookmarkNode, BookmarkGroup } from '../../core/bookmarks/types';
import { BookmarksService } from '../../core/bookmarks/bookmarks.service';
import { BookmarksPanelComponent } from '../bookmarks-panel/bookmarks-panel.component';
import { DropListRegistryService } from '../../core/services/drop-list-registry.service';
@Component({
selector: 'app-bookmark-item',
@@ -25,9 +29,10 @@ import { BookmarksPanelComponent } from '../bookmarks-panel/bookmarks-panel.comp
styleUrls: ['./bookmark-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BookmarkItemComponent {
export class BookmarkItemComponent implements AfterViewInit, OnDestroy {
private readonly bookmarksService = inject(BookmarksService);
private readonly panel = inject(BookmarksPanelComponent, { optional: true });
private readonly dropRegistry = inject(DropListRegistryService);
@Input({ required: true }) node!: BookmarkNode;
@Input() level = 0;
@@ -38,6 +43,10 @@ export class BookmarkItemComponent {
readonly showMenu = signal(false);
readonly isExpanded = signal(true);
readonly isDraggingOver = signal(false);
@ViewChild(CdkDropList, { static: false })
private dropListRef?: CdkDropList<BookmarkNode[]>;
get bookmark(): BookmarkNode {
return this.node;
@@ -68,12 +77,20 @@ export class BookmarkItemComponent {
get displayText(): string {
const node = this.bookmark;
// For groups, use title or fallback
if (node.type === 'group') {
return node.title || '(Sans nom)';
}
// If title is provided, use it
if (node.title) {
return node.title;
}
// For files/folders: use basename only (not full path)
if (node.type === 'file' || node.type === 'folder') {
return node.path;
return this.getBasename(node.path);
}
if (node.type === 'search') {
@@ -81,10 +98,20 @@ export class BookmarkItemComponent {
}
if (node.type === 'heading' || node.type === 'block') {
return `${node.path} > ${node.subpath}`;
return `${this.getBasename(node.path)} > ${node.subpath}`;
}
return 'Untitled';
return '(Sans titre)';
}
/**
* Extract basename from path (last segment after /)
*/
private getBasename(path: string): string {
if (!path) return '';
const normalized = path.replace(/\\/g, '/');
const segments = normalized.split('/');
return segments[segments.length - 1] || path;
}
get isGroup(): boolean {
@@ -110,26 +137,117 @@ export class BookmarkItemComponent {
return `group-${this.bookmark.ctime}`;
}
getDropListConnections(): string[] {
return this.dropListIds.filter(id => id !== this.dropListId);
ngAfterViewInit(): void {
// Register this group's drop list instance (only for groups)
if (this.isGroup && this.dropListRef) {
this.dropRegistry.register(this.dropListId, this.dropListRef);
}
}
ngOnDestroy(): void {
if (this.isGroup) {
this.dropRegistry.unregister(this.dropListId);
}
}
connectedDropLists(): CdkDropList[] {
// Return live list of other drop lists (instances) including root
return this.dropRegistry.listExcept(this.dropListId);
}
onChildDrop(event: CdkDragDrop<BookmarkNode[]>): void {
this.isDraggingOver.set(false);
console.log('=== CHILD DROP ATTEMPT ===');
console.log('Target group:', this.displayText, '(ctime:', this.bookmark.ctime, ')');
console.log('Event data:', event);
console.log('Drag data:', event.item.data);
if (this.dragDisabled || !this.isGroup) {
console.log('❌ Drop blocked: dragDisabled or not a group');
return;
}
const data = event.item.data as { ctime: number; parentCtime: number | null } | undefined;
if (!data || typeof data.ctime !== 'number') {
console.warn('❌ Invalid drag data in child drop:', data);
return;
}
console.log('✅ Drag data is valid:', data);
// Can't drop into itself
if (data.ctime === this.bookmark.ctime) {
console.warn('❌ Cannot drop group into itself');
return;
}
// Check if trying to drop an ancestor into a descendant (cycle detection)
// We want to check if the item being moved (data.ctime) is a descendant of the target group (this.bookmark.ctime)
if (this.isDescendantOf(data.ctime)) {
console.warn('❌ Cannot move a parent into its own descendant');
return;
}
console.log('✅ All validations passed, calling moveBookmark');
console.log('Child drop event:', {
itemCtime: data.ctime,
fromParent: data.parentCtime,
toParent: this.bookmark.ctime,
toParentTitle: this.displayText,
newIndex: event.currentIndex,
sameContainer: event.previousContainer === event.container
});
this.bookmarksService.moveBookmark(data.ctime, this.bookmark.ctime, event.currentIndex);
}
onDragEntered(): void {
if (this.isGroup) {
console.log('Drag entered group:', this.displayText, '(', this.dropListId, ')');
this.isDraggingOver.set(true);
}
}
onDragExited(): void {
if (this.isGroup) {
console.log('Drag exited group:', this.displayText, '(', this.dropListId, ')');
this.isDraggingOver.set(false);
}
}
/**
* Check if current node is a descendant of the given ctime
*/
private isDescendantOf(targetCtime: number): boolean {
const checkAncestors = (node: BookmarkNode): boolean => {
if (node.ctime === targetCtime) {
return true;
}
if (node.type === 'group') {
return node.items.some(child => checkAncestors(child));
}
return false;
};
const doc = this.bookmarksService.doc();
const findNode = (items: BookmarkNode[]): BookmarkNode | null => {
for (const item of items) {
if (item.ctime === this.bookmark.ctime) {
return item;
}
if (item.type === 'group') {
const found = findNode(item.items);
if (found) return found;
}
}
return null;
};
const targetNode = findNode(doc.items);
return targetNode ? checkAncestors(targetNode) : false;
}
toggleExpand(): void {
if (this.isGroup) {
@@ -64,28 +64,41 @@
</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"
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 transition-colors"
[class.bg-blue-500/20]="isDraggingOverRoot()"
[class.dark:bg-blue-400/20]="isDraggingOverRoot()"
cdkDropList
#rootDropList="cdkDropList"
[cdkDropListData]="displayItems()"
cdkDropListId="root"
[cdkDropListConnectedTo]="getDropListConnections('root')"
[cdkDropListConnectedTo]="connectedDropListsForRoot()"
cdkDropListOrientation="vertical"
[cdkDropListDisabled]="dragDisabled"
(cdkDropListDropped)="handleRootDrop($event)">
(cdkDropListDropped)="handleRootDrop($event)"
(cdkDropListEntered)="onDragEnterRoot()"
(cdkDropListExited)="onDragExitRoot()">
<span class="text-sm font-medium">Drop items here</span>
</div>
} @else {
<div
class="bookmarks-tree"
cdkDropList
#rootDropList="cdkDropList"
[cdkDropListData]="displayItems()"
cdkDropListId="root"
[cdkDropListConnectedTo]="getDropListConnections('root')"
[cdkDropListConnectedTo]="connectedDropListsForRoot()"
cdkDropListOrientation="vertical"
[cdkDropListDisabled]="dragDisabled"
(cdkDropListDropped)="handleRootDrop($event)">
(cdkDropListDropped)="handleRootDrop($event)"
(cdkDropListEntered)="onDragEnterRoot()"
(cdkDropListExited)="onDragExitRoot()">
@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">
<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 transition-colors sticky top-0 z-10"
[class.bg-blue-500/20]="isDraggingOverRoot()"
[class.dark:bg-blue-400/20]="isDraggingOverRoot()"
[class.border-blue-500]="isDraggingOverRoot()"
[class.dark:border-blue-400]="isDraggingOverRoot()">
Drop here to move to root
</div>
}
@@ -10,12 +10,16 @@ import {
computed,
Output,
EventEmitter,
ViewChild,
AfterViewInit,
OnDestroy,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DragDropModule, CdkDragDrop } from '@angular/cdk/drag-drop';
import { DragDropModule, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
import type { BookmarkNode } from '../../core/bookmarks/types';
import { BookmarksService } from '../../core/bookmarks/bookmarks.service';
import { DropListRegistryService } from '../../core/services/drop-list-registry.service';
import { BookmarkItemComponent } from '../bookmark-item/bookmark-item.component';
@Component({
@@ -25,8 +29,9 @@ import { BookmarkItemComponent } from '../bookmark-item/bookmark-item.component'
styleUrls: ['./bookmarks-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BookmarksPanelComponent {
export class BookmarksPanelComponent implements AfterViewInit, OnDestroy {
private readonly bookmarksService = inject(BookmarksService);
private readonly dropRegistry = inject(DropListRegistryService);
@Output() bookmarkClick = new EventEmitter<BookmarkNode>();
@@ -49,6 +54,10 @@ export class BookmarksPanelComponent {
readonly dragDisabledSignal = computed(() => this.searchTerm().trim().length > 0);
readonly isEmpty = computed(() => this.displayItems().length === 0);
readonly isDraggingOverRoot = signal(false);
@ViewChild('rootDropList', { read: CdkDropList })
private rootDropListRef!: CdkDropList<BookmarkNode[]>;
readonly dropListIds = computed(() => {
const ids: string[] = ['root'];
@@ -86,6 +95,22 @@ export class BookmarksPanelComponent {
return this.dropListIds().filter(existingId => existingId !== id);
}
ngAfterViewInit(): void {
// Register the root drop list instance
if (this.rootDropListRef) {
this.dropRegistry.register('root', this.rootDropListRef);
}
}
ngOnDestroy(): void {
this.dropRegistry.unregister('root');
}
connectedDropListsForRoot(): CdkDropList[] {
// Return every drop list except root
return this.dropRegistry.listExcept('root');
}
createGroup(parentCtime: number | null = null): void {
const title = window.prompt('Nom du groupe');
if (!title) {
@@ -119,25 +144,56 @@ export class BookmarksPanelComponent {
handleDrop(event: CdkDragDrop<BookmarkNode[]>, parentCtime: number | null): void {
if (this.dragDisabled) {
console.log('❌ Drop blocked: dragDisabled');
return;
}
console.log('=== PANEL DROP ATTEMPT ===');
console.log('Target parentCtime:', parentCtime);
console.log('Event data:', event);
console.log('Drag data:', event.item.data);
const data = event.item.data as { ctime: number; parentCtime: number | null } | undefined;
if (!data || typeof data.ctime !== 'number') {
console.warn('❌ Invalid drag data:', data);
return;
}
// Skip if dropping into itself
if (parentCtime === data.ctime) {
console.warn('❌ Cannot drop into itself');
return;
}
console.log('✅ All validations passed, calling moveBookmark');
console.log('Drop event:', {
itemCtime: data.ctime,
fromParent: data.parentCtime,
toParent: parentCtime,
newIndex: event.currentIndex,
sameContainer: event.previousContainer === event.container,
containerId: event.container.id,
previousContainerId: event.previousContainer?.id,
dropListIds: this.dropListIds()
});
// Move the bookmark
this.bookmarksService.moveBookmark(data.ctime, parentCtime, event.currentIndex);
}
handleRootDrop(event: CdkDragDrop<BookmarkNode[]>): void {
this.isDraggingOverRoot.set(false);
this.handleDrop(event, null);
}
onDragEnterRoot(): void {
this.isDraggingOverRoot.set(true);
}
onDragExitRoot(): void {
this.isDraggingOverRoot.set(false);
}
async resolveConflictReload(): Promise<void> {
await this.bookmarksService.resolveConflictReload();
@@ -12,7 +12,7 @@ import {
signal,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { Note } from '../../types';
import { Note } from '../../../types';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import mermaid from 'mermaid';
+25
View File
@@ -266,6 +266,31 @@ export class BookmarksService {
}
}
/**
* Remove all bookmarks with a specific path (useful for file bookmarks)
*/
removePathEverywhere(path: string): void {
const doc = this._doc();
const removeByPath = (items: BookmarkNode[]): BookmarkNode[] => {
return items.filter(item => {
if (item.type === 'file' && item.path === path) {
return false; // Remove this item
}
if (item.type === 'group') {
// Recursively filter children
item.items = removeByPath(item.items);
return true; // Keep the group
}
return true; // Keep other items
});
};
const updated = { ...doc, items: removeByPath([...doc.items]) };
this._doc.set(updated);
this._isDirty.set(true);
}
/**
* Move a bookmark
*/
+12 -1
View File
@@ -265,12 +265,23 @@ export function moveNode(
// Can't move a node into itself or its descendants
if (newParentCtime !== null && isDescendant(found.node, newParentCtime)) {
console.warn('Cannot move a node into its own descendant');
return doc;
}
const nodeClone = cloneNode(found.node);
const oldParentCtime = found.parent ? found.parent.ctime : null;
const oldIndex = found.index;
// If moving within the same parent, adjust index
let adjustedIndex = newIndex;
if (oldParentCtime === newParentCtime && oldIndex < newIndex) {
// When removing from earlier position, indices shift down
adjustedIndex = newIndex - 1;
}
let updated = removeNode(doc, nodeCtime);
updated = addNode(updated, nodeClone, newParentCtime, newIndex);
updated = addNode(updated, nodeClone, newParentCtime, adjustedIndex);
return updated;
}
@@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { CdkDropList } from '@angular/cdk/drag-drop';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class DropListRegistryService {
private lists = new Map<string, CdkDropList<any>>();
private updates$ = new BehaviorSubject<void>(undefined);
get changes() {
return this.updates$.asObservable();
}
register(id: string, list: CdkDropList<any>): void {
this.lists.set(id, list);
this.updates$.next();
}
unregister(id: string): void {
if (this.lists.delete(id)) {
this.updates$.next();
}
}
get(id: string): CdkDropList<any> | undefined {
return this.lists.get(id);
}
listExcept(id: string): CdkDropList<any>[] {
return Array.from(this.lists.entries())
.filter(([key]) => key !== id)
.map(([, val]) => val);
}
listAll(): CdkDropList<any>[] {
return Array.from(this.lists.values());
}
}