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>
13 KiB
Story: Markdown Round-Trip — Export & Import TipTap ↔ Markdown
Epic: Epic 6 — Croissance & Activation (PLG) ID: 6-2-markdown-roundtrip Priority: Beta Blocker Status: review Depends on: Rich-text editor TipTap (
rich-text-editor.tsx✅) Ref brief:docs/brief-markdown-roundtrip.md
Contexte
TipTap/ProseMirror stocke le contenu en JSON natif. Il n'existe aucune sérialisation TipTap → Markdown ni Markdown → TipTap dans le code actuel.
@tiptap/extension-markdown n'est pas disponible sur npm public (extension TipTap Pro). L'approche choisie utilise :
turndown: HTML → Markdown (export)marked: Markdown → HTML → TipTap parse (import + paste)
Ces deux librairies sont déjà dans le registre npm. react-markdown est déjà installé mais sert uniquement au rendu. marked est disponible (18.x).
User Stories
US-MARKDOWN-1 : Export Markdown
En tant qu' utilisateur,
Je veux exporter ma note en fichier .md,
Afin de la réutiliser dans d'autres outils (Obsidian, VS Code, GitHub, etc.).
Critères d'acceptation :
- AC-1 : Un item "Exporter en Markdown" est visible dans le menu
…de la note - AC-2 : Au clic, un fichier
<titre-note>.mdest téléchargé dans le navigateur - AC-3 : Le fichier contient : titre en
# H1, headings, bold, italic, listes, tâches (- [x]), code inline/fenced, blockquotes, liens, tables - AC-4 : Un
liveBlockest exporté en commentaire HTML<!-- live-block: sourceNoteId#blockId --> - AC-5 : Un
structuredViewBlockest exporté en commentaire HTML<!-- structured-view: {...attrs} -->
US-MARKDOWN-2 : Paste Markdown → blocs TipTap
En tant qu' utilisateur, Je veux coller du Markdown dans l'éditeur et obtenir des blocs TipTap, Afin de ne pas avoir à reformater manuellement.
Critères d'acceptation :
- AC-6 : Coller un texte qui commence par
#,##,-,*,1.,`,>,**, ou|déclenche la conversion Markdown → HTML → TipTap - AC-7 : Un texte normal (sans marqueurs Markdown) est collé tel quel (pas de conversion parasite)
- AC-8 : Les tables Markdown sont converties en tables TipTap
- AC-9 : Les tâches
- [x]sont converties entaskItemcochés
US-MARKDOWN-3 : Import fichier .md
En tant qu' utilisateur,
Je veux importer un fichier .md dans une nouvelle note (ou écraser le contenu),
Afin de migrer du contenu depuis d'autres outils.
Critères d'acceptation :
- AC-10 : Un item "Importer Markdown" est visible dans le menu
…de la note (ou via un bouton dédié) - AC-11 : Au clic, un file picker s'ouvre filtré sur
.md - AC-12 : Après sélection, le contenu de la note est remplacé par le contenu du fichier parsé en TipTap
- AC-13 : Le titre de la note est mis à jour avec le premier
# H1du fichier importé (si présent)
Tasks / Subtasks
T1 — Installation des dépendances
- T1.1 — Installer
turndown+@types/turndown(HTML → Markdown) - T1.2 — Vérifier que
markedest déjà disponible ou installer si absent - T1.3 — Vérifier que
@types/markedest disponible
T2 — Créer lib/editor/markdown-export.ts
- T2.1 — Implémenter
tiptapHTMLToMarkdown(html: string): stringviaturndown- Règle custom
liveBlock: div avecdata-live-block→ commentaire HTML via sentinel + post-process - Règle custom
structuredViewBlock: div avecdata-structured-view-block→ commentaire HTML via sentinel - GFM tables activé (
turndown-plugin-gfm) - GFM task lists activé
- Règle custom
- T2.2 — Implémenter
markdownToHTML(md: string): stringviamarked- GFM activé, tables activées
- Retourner HTML propre pour injection TipTap via
editor.commands.setContent(html)
- T2.3 — Implémenter
looksLikeMarkdown(text: string): boolean- Détecte si un texte collé contient des marqueurs Markdown (heuristique)
- Regex :
#,-,*,1.,>,```,**,_,|,[...](...)
T3 — Route API export Markdown
- T3.1 — Implémenté côté client dans
note-editor-toolbar.tsx(pas de route serveur — choix UX : utilise l'état live de l'éditeur) - T3.2 — Titre en
# <note.title>préfixé dans le fichier.mdexporté
T4 — Action UI Export dans note-editor-toolbar.tsx
- T4.1 — Ajouter
DropdownMenuItem"Exporter en Markdown" dans le menu…(avec icôneFileDown) - T4.2 — Au clic :
editor.getHTML()→tiptapHTMLToMarkdown()→Blob→ download via URL.createObjectURL - T4.3 — Toast de succès/erreur
T5 — Paste Markdown dans l'éditeur
- T5.1 — Créer extension TipTap
MarkdownPasteExtensiondanslib/editor/markdown-paste-extension.ts- Hook sur
handlePaste: silooksLikeMarkdown(text)→markdownToHTML(text)→editor.commands.insertContent setTimeout(0)pour éviter conflits de transactions
- Hook sur
- T5.2 — Intégrer
MarkdownPasteExtensiondansextensions[]derich-text-editor.tsx
T6 — Import fichier .md dans note-editor-toolbar.tsx
- T6.1 — Ajouter
DropdownMenuItem"Importer Markdown" dans le menu…(icôneFileUp) - T6.2 — Input
<input type="file" accept=".md">caché avecmdImportInputRef - T6.3 — Handler
handleImportMarkdownFile: lire fichier →markdownToHTML()→actions.setContent(html) - T6.4 — Titre mis à jour via
extractMarkdownTitle()+actions.setTitle()
T7 — i18n (15 locales)
- T7.1 — Ajout clés dans
locales/en.jsonetlocales/fr.json(namespacerichTextEditor)exportMarkdown,importMarkdown,markdownExportSuccess,markdownExportError,markdownImportSuccess
- T7.2 — Clés ajoutées dans les 13 autres locales (de, es, it, pt, nl, pl, ru, zh, ja, ko, ar, fa, hi)
T8 — Tests unitaires
- T8.1 — Tests
lib/editor/markdown-export.ts: 40 tests — heading, bold, italic, list, code, blockquote, link, table, liveBlock, svBlock - T8.2 — Tests
looksLikeMarkdown: 12 cas positifs + négatifs
Dev Notes
Approche technique
Pourquoi pas @tiptap/extension-markdown ?
Non disponible sur npm public (404 Not Found). C'est une extension TipTap Pro.
Approche choisie : turndown + marked
turndown(7.x) : battle-tested, supporte les plugins GFM (tables, task lists viaturndown-plugin-gfm)marked(18.x) : fast, supporte GFM tables/tasks nativement
Export côté serveur vs client :
L'export via route API /api/notes/[id]/export?format=markdown permet de ne pas dépendre de l'état de l'éditeur côté client — fonctionne même si l'éditeur est fermé.
TipTap generateHTML côté serveur :
import { generateHTML } from '@tiptap/html'
Nécessite d'importer toutes les extensions utilisées. Une liste partagée dans lib/editor/tiptap-extensions-server.ts simplifie la maintenance.
MarkdownPasteExtension :
Extension légère qui s'insère dans la chaîne de handlePaste. Si le texte collé contient des marqueurs Markdown, on le convertit avant insertion. Sinon, on laisse TipTap gérer normalement.
Custom nodes HTML output :
liveBlockgénère<div data-type="live-block" data-source-note-id="..." data-block-id="..."></div>dansgetHTML()structuredViewBlockgénère<div data-type="structured-view-block" ...></div>turndownpeut détecter ces divs pardata-typeet les convertir en commentaires HTML
Import titre :
Regex sur la première ligne du fichier .md : ^#\s+(.+) → titre. Passé en callback au parent.
Fichiers clés existants
memento-note/components/rich-text-editor.tsx— éditeur principalmemento-note/components/note-actions.tsx— menu actions note (dropdown…)memento-note/lib/editor/— extensions TipTap existantesmemento-note/locales/— 15 fichiers JSON i18n
Patterns importants
- Actions dans le menu
…:DropdownMenuItemavec icône Lucide +t('key')i18n - Download client-side :
URL.createObjectURL(blob)+ clic programmatique +URL.revokeObjectURL - Tests unitaires :
tests/unit/avec Jest, importé via alias@/
Dev Agent Record
Implementation Plan
Approche finale : turndown + marked (l'extension officielle @tiptap/extension-markdown est indisponible sur npm public — TipTap Pro uniquement).
- Export : client-side via
editor.getHTML()→turndown→ Blob download (pas de route serveur — utilise l'état live) - Import :
FileReader+marked→editor.commands.setContent(html) - Paste : extension ProseMirror
handlePaste→looksLikeMarkdownheuristique →marked→insertContent - Custom nodes (liveBlock, structuredViewBlock) : pre-processing avec sentinels alphanumériques, post-processing avec commentaires HTML
Debug Log
- turndown + divs vides :
turndownignore les nœuds "blank" (pas de texte). Solution : pre-process HTML → remplacer divs custom par<p>MOMENTOBLOCKSNTINELXXX</p>, post-process le MD résultant pour remplacer par<!-- comment -->. - turndown escape underscores : le sentinel
__MOMENTO_BLOCK__est échappé en\_\_MOMENTO\_BLOCK\_\_. Solution : utiliser uniquement des caractères alphanumériques (MOMENTOBLOCKSENTINELLIVEBLOCK0). - TS2322 dans toolbar :
editor.commands.setContent(html, true)— le 2e argument estSetContentOptionspasbooleandans TipTap v3. Corrigé. - Fragment JSX :
<input type="file">caché hors du div principal — wrappé dans<>fragment. - handleConvertToRichtext : le remplacement d'import avait supprimé l'ouverture
const handleConvertToRichtext = async () => {. Restauré.
Completion Notes
- T1 ✅ :
turndown@7.2.4+turndown-plugin-gfm+@types/turndowninstallés.marked@18.0.3déjà présent. - T2 ✅ :
lib/editor/markdown-export.ts—tiptapHTMLToMarkdown,markdownToHTML,looksLikeMarkdown,extractMarkdownTitle. Gestion des nœuds custom via sentinels. - T3 ✅ : Export client-side (pas de route serveur).
richTextEditorRef.current?.getEditor().getHTML()→ markdown → Blob download. - T4 ✅ :
DropdownMenuItem"Exporter en Markdown" + "Importer Markdown" dansnote-editor-toolbar.tsx(menu…). - T5 ✅ :
lib/editor/markdown-paste-extension.tscréé + intégré dansrich-text-editor.tsx. - T6 ✅ : Import fichier
.mdavecmdImportInputRef+handleImportMarkdownFiledans toolbar. Titre auto-extrait. - T7 ✅ : 5 clés dans namespace
richTextEditorajoutées à toutes les 15 locales. - T8 ✅ : 40 tests unit passent (39 sur fonctions, 1 fix pour spacing turndown). Suite complète : 174/174.
File List
memento-note/package.json— ajoutturndown@7.2.4,turndown-plugin-gfm,@types/turndownmemento-note/lib/editor/markdown-export.ts— créé — helpers markdown roundtripmemento-note/lib/editor/markdown-paste-extension.ts— créé — extension TipTap paste Markdownmemento-note/components/note-editor/note-editor-toolbar.tsx— modifié — items Export/Import Markdown + handlers + import refmemento-note/components/rich-text-editor.tsx— modifié — import + intégrationMarkdownPasteExtensionmemento-note/types/global.d.ts— modifié — déclaration de types pourturndown-plugin-gfmmemento-note/locales/en.json— modifié — 5 clésrichTextEditor.export/importMarkdown*memento-note/locales/fr.json— modifié — idemmemento-note/locales/de.json— modifié — idemmemento-note/locales/es.json— modifié — idemmemento-note/locales/it.json— modifié — idemmemento-note/locales/pt.json— modifié — idemmemento-note/locales/nl.json— modifié — idemmemento-note/locales/pl.json— modifié — idemmemento-note/locales/ru.json— modifié — idemmemento-note/locales/zh.json— modifié — idemmemento-note/locales/ja.json— modifié — idemmemento-note/locales/ko.json— modifié — idemmemento-note/locales/ar.json— modifié — idemmemento-note/locales/fa.json— modifié — idemmemento-note/locales/hi.json— modifié — idemmemento-note/tests/unit/markdown-export.test.ts— créé — 40 tests unitairesdocs/story-markdown-roundtrip.md— créé — story complètedocs/sprint-status.yaml— modifié —6-2-markdown-roundtrip: in-progress → review
Change Log
| Date | Description |
|---|---|
| 2026-05-30 | Story créée à partir du brief docs/brief-markdown-roundtrip.md |
| 2026-05-30 | Implémentation complète — T1 à T8 (voir Dev Agent Record) |