# Brief Technique : US-EDITOR-MARKDOWN — Round-Trip TipTap ↔ Markdown > **ID:** US-EDITOR-MARKDOWN > **Priorité:** Beta blocker (confirmé) > **Dépendances:** Rich-text editor TipTap existant (`rich-text-editor.tsx`) > **Complexité estimée:** Moyenne (2–5 jours selon approche choisie) --- ## Contexte TipTap/ProseMirror stocke le contenu en **JSON natif** (format ProseMirror Doc). Actuellement : - L'éditeur **lit et écrit du JSON** en base de données - `marked`, `react-markdown`, `remark-gfm` sont installés mais utilisés uniquement pour **l'affichage** de contenu Markdown externe (Web Clipper, import) - Il **n'existe aucune sérialisation TipTap → Markdown** ni **Markdown → TipTap** dans le code actuel L'objectif est un **round-trip fidèle** : ``` Note TipTap (JSON) → Export Markdown → Re-import → JSON identique (byte-for-byte sur les éléments supportés) ``` --- ## Analyse des Options ### Option A — `@tiptap/extension-markdown` (recommandé ✅) **Package :** `@tiptap/extension-markdown` (officiel TipTap, payant pour certaines extensions avancées — vérifier licence) **Principe :** - Extension TipTap officielle qui ajoute une méthode `.storage.markdown.getMarkdown()` sur l'éditeur - Import Markdown via `editor.commands.setContent(markdownString, { parseOptions: { markdown: true } })` - Supporte GFM (GitHub Flavored Markdown) : tableaux, listes de tâches, code fences **Avantages :** - Intégration native TipTap — zéro friction - Maintenu par l'équipe TipTap - Support des nœuds custom via `markdownSerializer` sur chaque extension **Inconvénients :** - Certaines extensions sont sous licence TipTap Pro ($149/mois) - Les nœuds custom de Memento (`liveBlock`, `structuredViewBlock`) nécessitent des serializers manuels - Round-trip parfait impossible pour ces nœuds (dégradation gracieuse : placeholder HTML comment) **Implémentation :** ```typescript // Installation npm install @tiptap/extension-markdown // Dans rich-text-editor.tsx import { Markdown } from '@tiptap/extension-markdown' const editor = useEditor({ extensions: [ // ... extensions existantes ... Markdown.configure({ html: false, // Désactiver HTML brut dans le MD tightLists: true, // Listes compactes tightListClass: 'tight', bulletListMarker: '-', linkify: false, breaks: false, transformPastedText: true, // Coller du Markdown → conversion auto transformCopiedText: false, }), ], }) // Export const markdown = editor.storage.markdown.getMarkdown() // Import editor.commands.setContent(markdownString) ``` --- ### Option B — `prosemirror-markdown` (alternative robuste ✅) **Package :** `prosemirror-markdown` (officiel ProseMirror) **Principe :** - Bibliothèque bas niveau qui fournit un `MarkdownSerializer` et un `MarkdownParser` - S'intègre à TipTap via le schéma ProseMirror sous-jacent - Utilise `remark` (déjà installé) pour le parsing **Avantages :** - Open source, pas de licence Pro - Contrôle total du serializer (chaque nœud est défini explicitement) - Utilisé en production par de nombreux éditeurs (GitLab, Linear) **Inconvénients :** - Plus verbeux — chaque extension TipTap nécessite son `toMarkdown` et `fromMarkdown` - Effort initial plus élevé (~2 jours de mapping) - À maintenir à chaque nouvelle extension ajoutée **Implémentation :** ```typescript import { MarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown' import { MarkdownParser } from 'prosemirror-markdown' import markdownit from 'markdown-it' // Serializer — mapper chaque nœud TipTap const momentoSerializer = new MarkdownSerializer( { ...defaultMarkdownSerializer.nodes, // Nœuds Memento custom liveBlock: (state, node) => { state.write(``) state.closeBlock(node) }, structuredViewBlock: (state, node) => { state.write(``) state.closeBlock(node) }, }, defaultMarkdownSerializer.marks ) // Export const markdown = momentoSerializer.serialize(editor.state.doc) // Parser const md = markdownit('commonmark', { html: true }) const parser = new MarkdownParser(editor.schema, md, { /* token map */ }) const doc = parser.parse(markdownString) editor.commands.setContent(doc.toJSON()) ``` --- ### Option C — `Milkdown` (remplacement complet ❌ déconseillé) **Milkdown** est un éditeur Markdown-first qui remplace TipTap/ProseMirror. Le migrer vers Milkdown signifie : - Réécriture complète de `rich-text-editor.tsx` (~800 lignes) - Perte de toutes les extensions custom (Living Blocks, Structured Views, Smart Paste, etc.) - Délai estimé : 2-4 semaines **Verdict : ❌ Trop risqué, trop long pour la beta.** --- ## Recommandation ### Court terme (beta) : **Option A** (`@tiptap/extension-markdown`) Raisons : 1. Intégration en **1 journée** dans l'éditeur existant 2. Couvre 95% des cas d'usage (texte, listes, headings, code, tables, tâches) 3. Le `transformPastedText: true` résout aussi un bug UX courant (coller du Markdown brut) 4. Les nœuds Memento non-supportés sont préservés via commentaires HTML (dégradation gracieuse) ### Long terme : **Option B** en complément Pour les cas avancés (export propre des nœuds custom, CI de round-trip byte-for-byte), implémenter Option B en remplacement d'Option A une fois la beta stabilisée. --- ## Scope de l'US-EDITOR-MARKDOWN (Beta) ### Ce qui est inclus | Feature | Priorité | |---------|----------| | Export note → fichier `.md` téléchargeable | 🔴 P0 | | Coller du Markdown → conversion auto en blocs TipTap | 🔴 P0 | | Import note depuis fichier `.md` | 🟡 P1 | | Copy note as Markdown (dans le presse-papier) | 🟡 P1 | | Round-trip fidèle pour : headings, bold, italic, lists, tasks, code, blockquote, links, tables | 🔴 P0 | | Dégradation gracieuse pour `liveBlock` et `structuredViewBlock` (commentaire HTML) | 🟡 P1 | ### Ce qui est exclu (post-beta) - Round-trip byte-for-byte des nœuds Memento custom - Édition native en mode source Markdown (raw text editor) - Sync bidirectionnelle temps réel Markdown ↔ TipTap --- ## Fichiers à créer / modifier | Fichier | Action | |---------|--------| | `memento-note/package.json` | Ajouter `@tiptap/extension-markdown` | | `components/rich-text-editor.tsx` | Ajouter extension `Markdown` à la config TipTap | | `lib/editor/markdown-export.ts` | Créer — helper `tiptapDocToMarkdown(doc)` et `markdownToTiptapDoc(md)` | | `components/note-actions.tsx` | Ajouter action "Exporter en Markdown" | | `app/api/notes/[id]/export/route.ts` | Créer — `GET /api/notes/:id/export?format=markdown` | | `locales/en.json` + `fr.json` | Ajouter clés `editor.exportMarkdown`, `editor.importMarkdown`, `editor.pasteMarkdown` | --- ## Critères d'acceptation **Given** j'ai une note avec du contenu riche (headings, listes, code, tasks) **When** je clique "Exporter en Markdown" **Then** je télécharge un fichier `.md` avec le contenu fidèlement sérialisé **Given** je copie du Markdown depuis un éditeur externe **When** je colle dans l'éditeur Memento **Then** le Markdown est automatiquement converti en blocs TipTap correspondants (pas du texte brut) **Given** j'importe un fichier `.md` **When** le parsing est terminé **Then** les headings/listes/code/tables sont correctement représentés dans l'éditeur **Given** ma note contient un `liveBlock` (bloc vivant) **When** j'exporte en Markdown **Then** le bloc vivant est exporté en commentaire HTML `` sans erreur --- ## Estimation | Tâche | Durée estimée | |-------|--------------| | Installation + config `@tiptap/extension-markdown` | 2h | | Helper `lib/editor/markdown-export.ts` | 2h | | Route API export + action UI | 2h | | Import fichier `.md` via modal | 3h | | Tests manuels round-trip + fix edge cases | 4h | | i18n (EN + FR) | 1h | | **Total** | **~2 jours** |