Editor de Texto Enriquecido Estilo Notion – Requisitos de Implementación

Este documento se encuentra junto al código y debe mantenerse actualizado cada vez que cambie la implementación del editor.

Planteamiento del Problema

  • El <MDXEditorComponent> existente envuelve @mdxeditor/editor, lo que nos proporciona edición clásica de Markdown.
  • Los autores desean una experiencia similar a Notion, basada en bloques: comandos de barra diagonal, arrastrar para reordenar bloques, listas de tareas, tablas, incrustaciones, etc.
  • El reemplazo debe integrarse limpiamente con los cargadores/acciones de React Router, continuar guardando borradores automáticamente y funcionar tanto en los flujos de creación como de edición.

Resumen de la Solución

  • Reemplazar MDXEditor con TipTap + ProseMirror – un editor de código abierto, sin interfaz gráfica, extensible, utilizado por Craft, GitLab, ChatGPT y otros.
  • Mantener un componente contenedor delgado (NotionEditor.tsx) que oculte los detalles específicos de TipTap y exponga una API familiar (value, onChange).
  • Persistir JSON o Markdown (matriz de decisión a continuación) en la columna existente blog_posts.content; los ayudantes de conversión mantienen la compatibilidad hacia atrás.

Paquetes para Añadir

pnpm --filter web add @tiptap/react @tiptap/starter-kit @tiptap/extension-placeholder \ @tiptap/extension-link @tiptap/extension-task-list @tiptap/extension-task-item \ @tiptap/extension-underline @tiptap/extension-table @tiptap/extension-table-row \ @tiptap/extension-table-cell @tiptap/extension-table-header \ @tiptap/extension-code-block-lowlight lowlight classnames

Extras opcionales (trabajo futuro): @hocuspocus/provider para sincronización en tiempo real, tiptap-extension-command para menú de barra diagonal, tiptap-extension-placeholder para IA.

Diseño de Archivos

apps/web/app/components/rich-text/ NotionEditor.tsx <-- contenedor reutilizable de TipTap (export default) EditorBubbleMenu.tsx <-- barra de herramientas de formato en línea EditorSlashMenu.tsx <-- menú de inserción de bloques editor-extensions.ts <-- fábrica que devuelve extensiones comunes serializer.ts <-- ayudantes JSON ⇆ Markdown (showdown / remark)

NOTA: mantener la carpeta existente mdx-editor/ hasta que se complete la migración; luego marcar como obsoleta.

Lista de Verificación de Implementación

  1. Crear componente editor ✅

    • Usar useEditor de @tiptap/react.
    • Cargar StarterKit + extensiones adicionales (definidas en editor-extensions.ts).
    • Exponer props: defaultContent, readOnly, onUpdate (devuelve tanto JSON como Markdown).
    • Aplicar clases Tailwind para el aspecto Notion (prose, focus:outline-none, etc.).
  2. BubbleMenu & SlashMenu ✅

    • BubbleMenu: negrita, cursiva, subrayado, enlace.
    • SlashMenu: /h1, /h2, /table, /code, /todo, /image.
    • Usar Headless UI / Radix Command para lista accesible (a11y).
  3. Integración de Autoguardado ✅

    • En triggerAutosave() reemplazar
      const currentContent = editor.getJSON();
    • Enviar como JSON.stringify(currentContent).
  4. Cambios en Rutas (posts/new & posts/[id]) ✅

    • Cambiar <MDXEditorComponent> → <NotionEditor>.
    • El <input name="content" /> oculto ahora contiene la carga útil serializada.
    • Mantener la lógica existente de Sheet, diálogos, autoguardado.
  5. Decisión de Base de Datos ✅

OpciónProsContras
Columna text, almacenar MarkdownSin cambio de esquemaSe pierde la riqueza de bloques (tablas, incrustaciones)
Columna jsonb, almacenar JSON de TipTapFidelidad completa, a prueba de futuroRequiere migración y serialización en lectura para publicaciones antiguas

Plan actual: migrar a jsonb pero entregar un convertidor de lectura/escritura de Markdown para evitar romper el blog público inmediatamente.

  1. Script de Migración ✅

    • Migración de Drizzle añadiendo una nueva columna content_json jsonb (nulable).
    • Relleno: content_json = markdownToTiptapJSON(content).
    • Eventualmente, eliminar la antigua columna content.
  2. Consideraciones de SSR y Compilación

    • TipTap usa window; envolver el hook en un guardia useClient o importar de forma diferida dentro de useEffect para rutas SSR.
    • Continuar cargando de forma diferida para mantener pequeño el tamaño del paquete.
  3. Accesibilidad y Atajos de Teclado

    • Cmd/Ctrl + b/i/u para marcas.
    • Cmd/Ctrl + s → abre el diálogo Guardar Borrador (ya existe).
    • Cmd/Ctrl + Shift + p → publicar.
  4. Pruebas y QA

    • Prueba unitaria de la fábrica de extensiones – devuelve la lista esperada.
    • Cypress e2e: crear publicación, agregar bloques, autoguardar, refrescar – el borrador persiste.
    • Regresión visual con Storybook (opcional).
  5. Mejoras Futuras

    • Cursores colaborativos con Y.js.
    • Modo de comentarios y sugerencias.
    • Comando de barra diagonal de IA para generación de contenido.
    • Vistas previas de incrustaciones (Twitter, Loom, Figma, …).

Fragmento de Referencia – NotionEditor.tsx

import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import Placeholder from "@tiptap/extension-placeholder"; import TaskList from "@tiptap/extension-task-list"; import TaskItem from "@tiptap/extension-task-item"; import Link from "@tiptap/extension-link"; export function NotionEditor({ defaultContent, onUpdate }) { const editor = useEditor({ extensions: [ StarterKit.configure({ codeBlock: false }), Placeholder.configure({ placeholder: "Escribe / para bloques" }), TaskList, TaskItem, Link.configure({ openOnClick: false }) ], content: defaultContent, onUpdate: ({ editor }) => { onUpdate?.({ json: editor.getJSON(), markdown: editor.getHTML() // o serializador personalizado }); } }); return ( <div className="prose leading-relaxed max-w-none border rounded-md p-4 focus:outline-none"> <EditorContent editor={editor} /> </div> ); }

Cronograma de Migración

SemanaTarea
1Estructurar componente editor y extensiones básicas
2Integrar en la ruta de nueva publicación, mantener MDXEditor lado a lado como respaldo
3Actualizar página de edición, habilitar conversión Markdown→JSON
4Migración de BD, relleno, eliminar código del editor antiguo

Mantenedor: @samishukri • Última actualización: 23/10/2026