Volver a la wiki

Cuaderno: Storage canónico Markdown + exportación TXT/MD/HTML

Resumen

A partir del commit 6a3b279 (2026-05-08), el módulo Cuaderno (sección Tareas y Notas del Workspace) cambia su formato de persistencia interno de JSON TipTap a Markdown como formato canónico, y añade exportación en tres formatos: texto plano, Markdown y HTML autónomo.


Storage canónico: de JSON a Markdown

Motivación

El formato anterior serializaba el contenido de cada nota como JSON.stringify(editor.getJSON()), el objeto interno de TipTap. Esto creaba acoplamiento fuerte con la versión del editor y hacía el contenido opaco en la base de datos D1.

El nuevo formato serializa con tiptap-markdown.getMarkdown(), obteniendo Markdown estándar legible directamente en D1.

Limitación conocida y aceptada: el color de texto no tiene representación en Markdown estándar. Si un usuario había aplicado colores personalizados, ese atributo se pierde en la primera re-serialización. El resto del formato es lossless.

Migración soft (backward-compat)

No se requiere migración SQL. La detección del formato legacy es por heurística:

// NoteEditor.tsx — initialContent
const raw = note.content?.trim() ?? '';
if (!raw || raw === '{}') return '';
if (raw.startsWith('{')) {
  try { return JSON.parse(raw); } catch { /* fallthrough */ }
}
return raw; // ya es Markdown

Auto-save (NoteEditor.tsx)

onUpdate: ({ editor }) => {
  const md = (editor.storage as { markdown?: { getMarkdown: () => string } }).markdown;
  const content = md ? md.getMarkdown() : '';
  await onPersist({ content, word_count: wordCount });
}

El word_count sigue calculándose con editor.getText() (texto sin formato).


Exportación — 3 formatos

El componente ExportModal.tsx gestiona los tres formatos mediante un editor fantasma (ghost editor, editable: false) que parsea el storage sin renderizarlo en pantalla.

FormatoExtensiónMIMEMétodo
Texto plano.txttext/plain;charset=utf-8editor.getText()
Markdown.mdtext/markdown;charset=utf-8Storage canónico directo
HTML standalone.htmltext/html;charset=utf-8marked.parse(markdown) + wrapper

Flujo interno del ghost editor

// Detecta formato del storage para inicializar el ghost editor
const initialContent = useMemo(() => {
  const raw = note.content?.trim() ?? '';
  if (!raw || raw === '{}') return '';
  if (raw.startsWith('{')) {
    try { return JSON.parse(raw); } catch { /* fallthrough */ }
  }
  return raw; // Markdown
}, [note.content]);

El Markdown canónico se obtiene:

Exportación HTML con marked (GFM)

La biblioteca marked@18.0.3 convierte el Markdown canónico a HTML con soporte GFM completo:

marked.use({ gfm: true, breaks: false });
// ...
return marked.parse(markdown, { async: false }) as string;

El HTML generado se envuelve en un documento standalone mediante wrapHtml():

PDF: La exportación a PDF no se gestiona programáticamente. Se recomienda Ctrl+P → “Guardar como PDF” desde el archivo HTML descargado o desde el navegador.

Soporte GFM en el HTML exportado

Elemento GFMSoporte
Tablas✅ border-collapse, cabecera gris
Listas de tareas✅ clases contains-task-list (marked estándar)
Strikethrough✅ nativo GFM
Bloques de código✅ <pre><code> con fondo gris
Citas✅ border-left + color muted
Separadores ---✅ <hr> con estilo

Dependencia añadida

"marked": "^18.0.3"

Archivos afectados

ArchivoCambio
src/components/notebook/NoteEditor.tsxStorage canónico → Markdown en auto-save
src/components/notebook/ExportModal.tsxNuevo formato TXT, conversión MD→HTML con marked, backward-compat
package.jsonNueva dep marked@18.0.3
pnpm-lock.yamlLock de marked y sus snaps
public/search-index.jsonRe-indexación automática

Consideraciones operativas


Véase también

Subir