CreaRack-SL

Cuaderno — 6 mejoras: color persist, tema MD, multi-pane, duplicar, hint contextual, heading parcial

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 — importa TextStyle, Color desde ./extensions
  • src/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 configurableCSS 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

KeyGridPanes
1x11 columna1
2x12 columnas2
1x21 columna, 2 filas2
2x22×24
3x13 columnas1

El layout se elige desde un segmento de botones en la barra superior (NotebookPage).

Comportamiento

  • El estado se modela como Pane[] donde cada Pane = { 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:

BloqueHint
Tablamensaje específico
Code blockmensaje específico
Listamensaje específico
Blockquotemensaje específico
Linkmensaje específico
H1 / H2 / H3mensaje específico
taskListmensaje 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

  • NoteEditor recibe nueva prop onOpenTheme: () => void para delegar apertura del modal al padre.
  • ExportModal ahora recibe note: NotebookNote directamente (antes exporting: boolean + activeNote).
  • handleDelete se reemplaza por handleDeleteFromSidebar(note) + handleDeleteCurrent(paneIdx).
  • handlePersist se reemplaza por makePersister(paneIdx) (closure por pane).

Véase también

  • [[entity—workspace—component—themeconfigmodal]]
  • [[workspace—que-es-workspace]]