ObsiViewer/docs/MARKDOWN_EDITOR.md

443 lines
12 KiB
Markdown

# 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<void> {
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 `<html>`:
```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<void> {
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 `<html>`
- 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)