chore: update Angular cache and TypeScript build info files
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
[noteTitle]="selectedNote()?.title || ''"
|
||||
(close)="closeBookmarkModal()"
|
||||
(save)="onBookmarkSave($event)"
|
||||
(delete)="onBookmarkDelete($event)"
|
||||
></app-add-bookmark-modal>
|
||||
}
|
||||
<!-- Navigation latérale desktop -->
|
||||
|
||||
@@ -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();
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user