# Mode Édition Markdown - Documentation Technique ## 🎯 Vue d'ensemble Le mode édition Markdown permet aux utilisateurs d'éditer directement leurs fichiers Markdown dans ObsiViewer avec un éditeur professionnel basé sur **CodeMirror 6**. L'éditeur est chargé dynamiquement (lazy loading) uniquement lors du passage en mode édition pour préserver les performances. ## 📁 Architecture ### Structure des fichiers ``` src/ ├── app/ │ └── features/ │ └── editor/ │ ├── markdown-editor.module.ts # Module lazy-loadable │ ├── markdown-editor.component.ts # Composant principal de l'éditeur │ └── editor-can-deactivate.guard.ts # Guard de navigation ├── services/ │ └── editor-state.service.ts # Service de gestion d'état ├── components/ │ ├── markdown-viewer/ │ │ └── markdown-viewer.component.ts # Bouton "Éditer" ajouté │ └── smart-file-viewer/ │ └── smart-file-viewer.component.ts # Intégration éditeur └── styles/ └── codemirror.css # Styles globaux CodeMirror ``` ### Flux de données ``` ┌─────────────────────┐ │ markdown-viewer │ │ (Bouton "Éditer") │ └──────────┬──────────┘ │ editModeRequested(path, content) ▼ ┌─────────────────────┐ │ smart-file-viewer │ │ onEditModeRequested │ └──────────┬──────────┘ │ enterEditMode(path, content) ▼ ┌─────────────────────┐ │ editor-state.service│ │ (mode: 'edit') │ └──────────┬──────────┘ │ effect() ▼ ┌─────────────────────┐ │ smart-file-viewer │ │ loadEditor() │ └──────────┬──────────┘ │ dynamic import ▼ ┌─────────────────────┐ │ markdown-editor │ │ (CodeMirror 6) │ └─────────────────────┘ ``` ## 🔧 Composants clés ### 1. EditorStateService (`editor-state.service.ts`) Service singleton qui gère l'état global du mode édition. **État géré:** ```typescript interface EditorState { mode: 'view' | 'edit'; // Mode actuel currentPath: string | null; // Chemin du fichier en cours d'édition isDirty: boolean; // Modifications non sauvegardées content: string | null; // Contenu en cours d'édition } ``` **Méthodes principales:** - `enterEditMode(path, content)` - Passe en mode édition - `exitEditMode()` - Quitte le mode édition - `setDirty(isDirty)` - Marque le contenu comme modifié - `updateContent(content)` - Met à jour le contenu - `markAsSaved()` - Réinitialise le flag dirty après sauvegarde - `canExit()` - Vérifie si on peut quitter sans perdre de données ### 2. MarkdownEditorComponent (`markdown-editor.component.ts`) Composant standalone qui encapsule CodeMirror 6. **Features:** - ✅ Édition Markdown avec coloration syntaxique - ✅ Support YAML front-matter - ✅ Line numbers, active line highlighting - ✅ Bracket matching, auto-closing - ✅ Search & replace (Ctrl/Cmd+F) - ✅ History (undo/redo) - ✅ Word wrap toggle - ✅ Autosave après 5s d'inactivité - ✅ Thème dark/light synchronisé - ✅ Responsive mobile **Inputs:** - `initialPath: string` - Chemin du fichier - `initialContent: string` - Contenu initial **Toolbar:** - **Save** (Ctrl/Cmd+S) - Sauvegarde via VaultService - **Wrap** - Toggle word wrap - **Undo** - Annuler (Ctrl/Cmd+Z) - **Redo** - Refaire (Ctrl/Cmd+Y) - **Close** - Quitter l'éditeur (avec confirmation si dirty) ### 3. SmartFileViewerComponent (Intégration) Gère le basculement entre mode lecture et mode édition. **Lazy Loading:** ```typescript private async loadEditor(): Promise { const { MarkdownEditorComponent } = await import( '../../app/features/editor/markdown-editor.component' ); this.editorComponentRef = this.editorContainer.createComponent( MarkdownEditorComponent ); // Set inputs... } ``` **Avantages:** - CodeMirror 6 n'est chargé que lors du passage en mode édition - Pas d'impact sur les performances en mode lecture - Destruction automatique du composant lors de la sortie d'édition ### 4. EditorCanDeactivateGuard (`editor-can-deactivate.guard.ts`) Guard de navigation Angular pour empêcher la perte de données. **Protection:** - Navigation Angular Router - Fermeture/rafraîchissement du navigateur (beforeunload) **Usage dans les routes:** ```typescript { path: 'note/:id', component: NoteViewerComponent, canDeactivate: [EditorCanDeactivateGuard] } ``` ## 🎨 Styles et thème ### Variables CSS (`codemirror.css`) Les thèmes sont définis via des variables CSS qui s'adaptent automatiquement au mode dark/light du site: ```css :root { --cm-bg: #ffffff; --cm-text: #1e293b; --cm-cursor: #3b82f6; --cm-selection-bg: #bfdbfe; --cm-active-line-bg: #f1f5f9; /* ... */ } .dark { --cm-bg: #1e293b; --cm-text: #e2e8f0; --cm-cursor: #60a5fa; /* ... */ } ``` ### Détection du thème Le composant observe les changements de classe `dark` sur ``: ```typescript private setupThemeObserver(): void { const observer = new MutationObserver(() => { const darkMode = document.documentElement.classList.contains('dark'); this.isDarkTheme.set(darkMode); this.reconfigureTheme(); }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); } ``` ## 💾 Sauvegarde ### VaultService Integration Le composant utilise la méthode privée `saveMarkdown()` du VaultService: ```typescript async save(): Promise { const content = this.editorView?.state.doc.toString() || ''; const success = await (this.vaultService as any).saveMarkdown( this.filePath(), content ); if (success) { this.isDirty.set(false); this.editorStateService.markAsSaved(); this.toastService.success('Saved successfully'); } else { this.toastService.error('Failed to save'); } } ``` ### Autosave L'autosave est déclenché après 5 secondes d'inactivité: ```typescript private scheduleAutosave(): void { if (this.autosaveTimer) { clearTimeout(this.autosaveTimer); } this.autosaveTimer = setTimeout(() => { if (this.isDirty() && !this.isSaving()) { this.save(); } }, 5000); } ``` ## ⌨️ Raccourcis clavier | Raccourci | Action | |-----------|--------| | `Ctrl/Cmd + S` | Sauvegarder | | `Ctrl/Cmd + F` | Rechercher | | `Ctrl/Cmd + Z` | Annuler | | `Ctrl/Cmd + Y` | Refaire | | `Esc` | Fermer la recherche | | `Tab` | Indenter | | `Shift + Tab` | Désindenter | ## 📱 Responsive Mobile ### Toolbar compacte ```css @media (max-width: 640px) { .btn-editor { padding: 0.375rem 0.5rem; } .btn-editor span { display: none; /* Cache les labels, garde les icônes */ } } ``` ### Éditeur optimisé ```css @media (max-width: 640px) { .cm-editor { font-size: 13px !important; } .cm-content { padding: 0.75rem; } } ``` ## 🧪 Tests manuels ### Checklist de validation - [ ] **Bouton "Éditer"** - [ ] Visible à gauche de "Open in Full Screen" - [ ] Alignement correct - [ ] États hover/active - [ ] Non visible pour fichiers Excalidraw - [ ] **Passage en mode édition** - [ ] Transition fluide sans glitch - [ ] Contenu chargé correctement - [ ] Front-matter YAML préservé - [ ] Cursor positionné - [ ] **Édition** - [ ] Coloration syntaxique Markdown - [ ] Line numbers - [ ] Word wrap toggle - [ ] Search (Ctrl+F) fonctionne - [ ] Undo/Redo fonctionnent - [ ] Autosave après 5s - [ ] **Sauvegarde** - [ ] Ctrl+S sauvegarde - [ ] Toast "Saved successfully" - [ ] Flag dirty réinitialisé - [ ] Erreur I/O gérée (toast d'erreur) - [ ] **Navigation guard** - [ ] Confirmation si modifications non sauvegardées - [ ] Pas de confirmation si sauvegardé - [ ] beforeunload (fermeture navigateur) - [ ] **Thème Dark/Light** - [ ] Basculement dynamique - [ ] Couleurs correctes - [ ] Contraste lisible - [ ] **Responsive Mobile** - [ ] Toolbar compacte (≤ 390px) - [ ] Boutons visibles - [ ] Scroll fluide - [ ] Clavier n'occulte pas toolbar - [ ] **Performance** - [ ] Pas de lag en mode lecture - [ ] Lazy loading confirmé (devtools Network) - [ ] Note lourde (>5000 lignes) éditable ## 🚀 Utilisation ### Pour l'utilisateur 1. Ouvrir une note Markdown en mode lecture 2. Cliquer sur le bouton "Éditer" (icône crayon) dans la toolbar 3. L'éditeur CodeMirror 6 se charge 4. Éditer le contenu 5. Sauvegarder avec Ctrl+S ou le bouton "Save" 6. Cliquer sur "Close" pour revenir en mode lecture ### Pour le développeur **Ajouter une extension CodeMirror:** ```typescript // Dans markdown-editor.component.ts, méthode initializeEditor() const initialState = EditorState.create({ extensions: [ // ... extensions existantes myCustomExtension(), // Ajouter ici ] }); ``` **Personnaliser le thème:** Modifier `src/styles/codemirror.css` et ajuster les variables CSS. **Ajouter un bouton dans la toolbar:** Éditer le template de `markdown-editor.component.ts`. ## 🐛 Debugging ### Console logs utiles - `[SmartFileViewer] Editor loaded successfully` - Éditeur chargé - `[SmartFileViewer] Editor unloaded` - Éditeur déchargé - `[MarkdownViewer] Cannot edit: no file path` - Pas de path ### Problèmes courants **1. L'éditeur ne se charge pas** - Vérifier que `EditorStateService.isEditMode()` est true - Vérifier dans la console les erreurs d'import dynamique - Vérifier que le ViewContainerRef est disponible **2. Le thème ne change pas** - Vérifier que la classe `dark` est bien sur `` - Vérifier que le MutationObserver est actif - Inspecter les variables CSS dans DevTools **3. La sauvegarde échoue** - Vérifier le path du fichier - Vérifier les permissions serveur - Vérifier les logs serveur (API `/api/files?path=...`) ## 📦 Dépendances ### Packages installés ```json { "@codemirror/view": "^6.x", "@codemirror/state": "^6.x", "@codemirror/language": "^6.x", "@codemirror/lang-markdown": "^6.x", "@codemirror/commands": "^6.x", "@codemirror/search": "^6.x", "@codemirror/autocomplete": "^6.x", "@codemirror/lint": "^6.x", "@codemirror/legacy-modes": "^6.x", "@lezer/highlight": "^1.x" } ``` ### Bundle size impact - **Mode lecture:** 0 KB (lazy loading) - **Mode édition:** ~150 KB (CodeMirror + extensions) ## 🔮 Évolutions futures - [ ] Preview split (côte à côte Markdown/Rendu) - [ ] Formatage rapide (bold, italic, list) via toolbar - [ ] Snippets personnalisés - [ ] Vim mode (via @replit/codemirror-vim) - [ ] Collaborative editing (via Y.js) - [ ] Export PDF depuis l'éditeur - [ ] Statistiques (nombre de mots, temps d'édition) ## 📝 Notes techniques ### Pourquoi CodeMirror 6 ? - Architecture modulaire (extensions) - Performance optimale (virtual scrolling) - TypeScript first - Thème CSS facilement personnalisable - Communauté active ### Alternatives considérées - **Monaco Editor** - Trop lourd (2+ MB), conçu pour IDE - **Ace Editor** - API moins moderne, maintenance limitée - **ProseMirror** - Orienté WYSIWYG, pas Markdown brut --- **Date de création:** 2025-01-20 **Version:** 1.0.0 **Auteur:** Lead Frontend (Angular 20 + Tailwind)