Story 6-2 — Markdown roundtrip export/import: - lib/editor/markdown-export.ts: tiptapHTMLToMarkdown, markdownToHTML, looksLikeMarkdown - lib/editor/markdown-paste-extension.ts: TipTap extension paste Markdown → blocs - note-editor-toolbar.tsx: export .md + import .md (file picker) - rich-text-editor.tsx: intégration MarkdownPasteExtension - 40 tests unitaires markdown-export.test.ts Story 6-3 — Brainstorm PPTX + Canvas: - lib/brainstorm/export-pptx.ts: génération PPTX 5 slides (pptxgenjs) - app/api/brainstorm/[sessionId]/export-pptx/route.ts: route POST protégée - brainstorm-page.tsx: bouton PPTX, auto-select session, fix emoji, fix router.replace - wave-canvas.tsx: fitTrigger recentrage, légende bas-droite Onboarding activation wizard (Story 6-1): - components/onboarding/: wizard multi-étapes, hints éditeur - app/api/onboarding/: route PATCH onboarding - prisma/migrations: champs onboarding user Locales: 15 langues mises à jour (brainstorm, markdown, onboarding keys) Sprint: 6-1 done, 6-2 review, 6-3 review Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
7.9 KiB
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-gfmsont 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
markdownSerializersur chaque extension
Inconvénients :
- Certaines extensions sont sous licence TipTap Pro ($149/mois)
- Les nœuds custom de Momento (
liveBlock,structuredViewBlock) nécessitent des serializers manuels - Round-trip parfait impossible pour ces nœuds (dégradation gracieuse : placeholder HTML comment)
Implémentation :
// 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
MarkdownSerializeret unMarkdownParser - 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
toMarkdownetfromMarkdown - Effort initial plus élevé (~2 jours de mapping)
- À maintenir à chaque nouvelle extension ajoutée
Implémentation :
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 Momento custom
liveBlock: (state, node) => {
state.write(`<!-- live-block: ${node.attrs.sourceNoteId}#${node.attrs.blockId} -->`)
state.closeBlock(node)
},
structuredViewBlock: (state, node) => {
state.write(`<!-- structured-view: ${JSON.stringify(node.attrs)} -->`)
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 :
- Intégration en 1 journée dans l'éditeur existant
- Couvre 95% des cas d'usage (texte, listes, headings, code, tables, tâches)
- Le
transformPastedText: truerésout aussi un bug UX courant (coller du Markdown brut) - Les nœuds Momento 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 Momento 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 Momento 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 <!-- live-block: ... --> 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 |