Cuaderno — 6 mejoras (mayo 2026)
Commit e230534 introduce seis mejoras sustanciales al módulo Cuaderno de CreaRack Workspace. Todas las mejoras son del lado cliente (React + Tiptap); el backend de notas no cambia.
1. Persistencia de colores en Markdown (extensions.ts)
Problema anterior: Tiptap serializa el contenido a Markdown usando tiptap-markdown. Esta librería descartaba silenciosamente las marks de TextStyle (el mecanismo de color inline), de modo que los colores aplicados a texto no sobrevivían al cerrar y reabrir una nota.
Solución: Se introduce src/components/notebook/extensions.ts con un TextStyle extendido que sobreescribe addStorage().markdown.serialize para emitir <span style="color: #xxx">…</span> al serializar. Los archivos .md resultantes son Markdown estándar con HTML inline —compatibles con cualquier renderizador que acepte HTML.
Archivos afectados:
src/components/notebook/extensions.ts(nuevo)src/components/notebook/NoteEditor.tsx— importaTextStyle, Colordesde./extensionssrc/components/notebook/ExportModal.tsx— ídem, para preservar colores en exportación
2. Tema MD configurable (ThemeConfigModal.tsx)
Nuevo modal accesible desde el botón «Tema MD» en la toolbar del editor.
| Elemento configurable | CSS var generada |
|---|---|
| Heading 1 | --cuaderno-color-h1 |
| Heading 2 | --cuaderno-color-h2 |
| Heading 3 | --cuaderno-color-h3 |
| Negrita | --cuaderno-color-strong |
| Código inline | --cuaderno-color-code |
| Bloque de código | --cuaderno-color-code-block |
| Enlaces | --cuaderno-color-link |
| Cita (blockquote) | --cuaderno-color-blockquote |
| Marcadores de lista | --cuaderno-color-marker |
Persistencia: localStorage con clave workspace.notebook.theme. La preferencia es exclusivamente visual del navegador — no se graba en las notas ni altera los archivos exportados (los .md siguen siendo plano sin colores).
Preview en vivo: applyTheme() se llama en cada cambio de color mientras el modal está abierto. Al cancelar, se restaura el tema anterior.
3. Editor multi-pane
Layouts disponibles
| Key | Grid | Panes |
|---|---|---|
1x1 | 1 columna | 1 |
2x1 | 2 columnas | 2 |
1x2 | 1 columna, 2 filas | 2 |
2x2 | 2×2 | 4 |
3x1 | 3 columnas | 1 |
El layout se elige desde un segmento de botones en la barra superior (NotebookPage).
Comportamiento
- El estado se modela como
Pane[]donde cadaPane = { noteId: number | null; active: NotebookNote | null }. - El pane activo se marca con click (
onMouseDown). Cuando hay >1 pane, el activo muestra un outline azul. - Abrir una nota desde el sidebar siempre carga en el pane activo (
openInActivePane(noteId)). - Al cambiar de layout, los panes sobrantes se truncan; los faltantes se rellenan con
EMPTY_PANE. - Cada pane tiene su propio
makePersister(paneIdx)— el autosave no interfiere entre panes.
4. Duplicar y eliminar desde el sidebar
NoteRow ahora es un <div role="button"> con un mini-toolbar que aparece en hover (hovering prop):
- ⧉ Duplicar: clona contenido y
word_count, prefija(copia)al título. La nota duplicada se abre en el pane activo. - ✕ Eliminar: pide confirmación. Si la nota estaba abierta en algún pane, ese pane queda vacío (
EMPTY_PANE).
El hover se gestiona con hoveringRow: number | null en NotebookPage para evitar estado distribuido.
5. Hint contextual en toolbar
La toolbar muestra un texto de ayuda que cambia según el tipo de bloque donde está el cursor:
| Bloque | Hint |
|---|---|
| Tabla | mensaje específico |
| Code block | mensaje específico |
| Lista | mensaje específico |
| Blockquote | mensaje específico |
| Link | mensaje específico |
| H1 / H2 / H3 | mensaje específico |
| taskList | mensaje específico |
| (default) | "Escribe / para abrir el menú" |
6. H1/H2/H3 sobre selección parcial
Antes, aplicar un heading convertía el bloque entero. Ahora, si la selección es no vacía y no abarca el bloque entero, el bloque se divide en tres nodos:
[texto previo] → párrafo normal
[texto seleccionado] → heading aplicado
[texto posterior] → párrafo normal
Si la selección está vacía o abarca la línea entera, el comportamiento es el anterior (heading de bloque completo).
Cambios de interfaz destacados
NoteEditorrecibe nueva proponOpenTheme: () => voidpara delegar apertura del modal al padre.ExportModalahora recibenote: NotebookNotedirectamente (antesexporting: boolean + activeNote).handleDeletese reemplaza porhandleDeleteFromSidebar(note)+handleDeleteCurrent(paneIdx).handlePersistse reemplaza pormakePersister(paneIdx)(closure por pane).
Véase también
- [[entity—workspace—component—themeconfigmodal]]
- [[workspace—que-es-workspace]]