# Bookmarks Technical Documentation ## Vue d'ensemble La fonctionnalité Bookmarks d'ObsiViewer permet de gérer des favoris compatibles à 100% avec Obsidian, en lisant et écrivant dans `.obsidian/bookmarks.json`. ## Architecture ### Couches ``` ┌─────────────────────────────────────┐ │ UI Components │ │ - BookmarksPanelComponent │ │ - BookmarkItemComponent │ │ - AddBookmarkModalComponent │ └─────────────┬───────────────────────┘ │ ┌─────────────▼───────────────────────┐ │ BookmarksService (Angular) │ │ - State management (Signals) │ │ - Business logic │ └─────────────┬───────────────────────┘ │ ┌─────────────▼───────────────────────┐ │ IBookmarksRepository │ │ ├─ FsAccessRepository (browser) │ │ ├─ ServerBridgeRepository (API) │ │ └─ InMemoryRepository (fallback) │ └─────────────┬───────────────────────┘ │ ┌─────────────▼───────────────────────┐ │ .obsidian/bookmarks.json │ └─────────────────────────────────────┘ ``` ## Structure de données ### Format JSON (Compatible Obsidian) ```json { "items": [ { "type": "file", "ctime": 1759241377289, "path": "notes/document.md", "title": "Mon Document" }, { "type": "group", "ctime": 1759202283361, "title": "Mes Projets", "items": [ { "type": "file", "ctime": 1759202288985, "path": "projets/projet-a.md" } ] } ], "rev": "abc123-456" } ``` ### Types TypeScript ```typescript type BookmarkType = 'group' | 'file' | 'search' | 'folder' | 'heading' | 'block'; interface BookmarkBase { type: BookmarkType; ctime: number; // Timestamp unique (ID) title?: string; // Titre optionnel } interface BookmarkFile extends BookmarkBase { type: 'file'; path: string; // Chemin relatif dans la vault } interface BookmarkGroup extends BookmarkBase { type: 'group'; items: BookmarkNode[]; // Enfants récursifs } type BookmarkNode = BookmarkFile | BookmarkGroup | ...; interface BookmarksDoc { items: BookmarkNode[]; rev?: string; // Pour détection de conflits } ``` ## Règles métier ### 1. Affichage des titres **Règle**: Si `title` manque, afficher le **basename** (nom de fichier sans dossier). ```typescript displayTitle = bookmark.title ?? basename(bookmark.path); // Exemple: "notes/projet/doc.md" → "doc.md" ``` **Implémentation**: `BookmarkItemComponent.displayText` getter. ### 2. Identifiants uniques **Règle**: Utiliser `ctime` (timestamp en millisecondes) comme ID unique. **Garantie d'unicité**: La fonction `ensureUniqueCTimes()` détecte et corrige les doublons. ### 3. Hiérarchie et drag & drop #### Opérations autorisées - ✅ Racine → Groupe (déposer un item dans un groupe) - ✅ Groupe → Racine (extraire un item d'un groupe) - ✅ Groupe A → Groupe B (déplacer entre groupes) - ✅ Réordonnancement au sein d'un conteneur #### Détection de cycles **Problème**: Empêcher de déposer un groupe dans lui-même ou ses descendants. **Solution**: La méthode `isDescendantOf()` vérifie récursivement la hiérarchie avant chaque déplacement. ```typescript private isDescendantOf(ancestorCtime: number): boolean { // Trouve l'ancêtre potentiel const ancestorNode = findNodeByCtime(doc.items, ancestorCtime); if (!ancestorNode) return false; // Vérifie si this.bookmark est dans ses descendants return checkDescendants(ancestorNode, this.bookmark.ctime); } ``` **Appel**: Dans `BookmarkItemComponent.onChildDrop()` avant `moveBookmark()`. ### 4. Zone "Drop here to move to root" **Problème initial**: La zone ne réagissait pas aux drops. **Solution**: - Ajout d'événements `cdkDropListEntered` et `cdkDropListExited` - Signal `isDraggingOverRoot` pour feedback visuel - Classes CSS dynamiques pour mettre en évidence la zone active ```html
No bookmarks yet
Use the bookmark icon to add one.