CreaRack-SL

Cuaderno · Multiusuario y 7 nuevas funciones (PR#56)

Cuaderno · Multiusuario y 7 nuevas funciones (PR#56)

PR#56 mergeado el 2026-05-20 por @Esquembri. Extiende el módulo Cuaderno con sharing colaborativo entre los miembros del staff y añade 6 herramientas de edición adicionales pedidas por Edu. Trabaja sobre la tabla notebook_notes ya existente.


1. Sharing multiusuario

Modelo de datos (migration 0032)

ALTER TABLE notebook_notes ADD COLUMN shared_with TEXT NOT NULL DEFAULT '';
ALTER TABLE notebook_notes ADD COLUMN version     INTEGER NOT NULL DEFAULT 1;
Valor de shared_withSemántica
'' (vacío)Nota privada — solo el owner la ve
'ALL'Compartida con todo el staff
'edu@esfericlabs.com,dani@esfericlabs.com'CSV de emails específicos

La lógica de acceso en todos los handlers sigue el mismo patrón:

const canAccess =
  row.owner_id === me ||
  row.shared_with === 'ALL' ||
  (row.shared_with !== '' && row.shared_with.includes(me));

Nota de seguridad: el LIKE %email% se considera seguro porque los emails @esfericlabs.com son únicos y sin solapes de substring con el staff actual (3 personas). Documentado en el comentario de 0032_notebook_sharing.sql.

Permisos de operación

AcciónOwnerCollaborator
GET nota✅✅
PUT contenido/título/pin/archivado✅✅
PUT shared_with✅❌ (403)
DELETE nota✅❌ (404)
POST imagen✅✅

UX en frontend

  • Dropdown “Compartir” en toolbar: opciones Privada / Todo equipo / Edu / Dani / Txell.
  • Badge en sidebar: 👤 Dani si la nota es ajena, 🔗 si es propia y compartida.
  • Banner en header del editor: “Compartida por X” (nota ajena) · “Compartida · Edu, Dani” (propia compartida).

Detección de ediciones concurrentes

  • Polling cada 30 segundos al endpoint GET /api/notebook/:id/version.
  • Si el version devuelto es mayor al que el editor tiene en memoria → banner amarillo de aviso (“Otro miembro guardó cambios”).
  • Modelo last-write-wins: no hay merge automático; la última escritura gana.

2. Undo / Redo — botones visibles

Botones ↶ ↷ en la toolbar del editor. Invocan undo/redo de @codemirror/commands. CodeMirror ya tenía Ctrl+Z ilimitado; los botones añaden descubribilidad para usuarios no técnicos.


3. Toggle autocorrector

  • Botón Aa en toolbar.
  • Activa/desactiva los atributos de contenido spellcheck, autocorrect y autocapitalize via Compartment + EditorView.contentAttributes de CodeMirror.
  • Estado persistido en localStorage por usuario/dispositivo.

4. Tachado + marca Urgente

FunciónSintaxis MarkdownRenderizado
Tachado~~texto~~texto
Urgente!!texto!!<mark class="urgent">⚠ texto</mark> (rojo translúcido + borde izquierdo)
  • El tachado ya existía; se mejoró su tooltip (“ideal para tareas hechas”).
  • !!texto!! es una extensión custom de marked añadida en este PR.

5. Emoji picker

  • Componente EmojiPicker.tsx (~180 LOC, ~3 KB, sin dependencias externas).
  • ~100 emojis en 6 categorías: Caras / Manos / Estados / Trabajo / Tech / Comida.
  • Buscador por nombre de categoría. Cierra con Escape o click fuera.
  • Accesible: role="dialog", aria-label, autoFocus en el input de búsqueda.

6. Fix layout — botones header desbordando

Ajuste CSS puntual en .cuaderno-header-compact .tools-btn:

/* Antes */  padding: 0.5rem 1rem;   font-size: 0.82rem;
/* Ahora  */  padding: 0.32rem 0.7rem; font-size: 0.74rem;

Los botones de la toolbar ahora encajan en una sola línea en viewports compactos.


7. Insertar imágenes embebidas

Flujo completo de subida e inserción de imágenes en notas:

Botón 🖼 → file input → POST /api/notebook/:id/images
  → validación MIME + tamaño (5MB max)
  → PUT en R2 bucket TASK_ATTACHMENTS (prefix notebook/)
  → INSERT en notebook_attachments
  → inserta ![alt](/api/notebook-images/{id}) en el editor

MIME whitelist: image/png, image/jpeg, image/webp, image/gif.

Borrado en cascada: al eliminar una nota, el handler DELETE recupera primero todas las r2_key de notebook_attachments y las borra de R2, antes de ejecutar el DELETE SQL (la FK ON DELETE CASCADE limpia las rows pero R2 quedaría huérfano sin este paso).


Endpoints nuevos / modificados

MétodoRutaHandlerCambio
GET/api/notebookfunctions/api/notebook/index.tsModificado: lista propias + compartidas
POST/api/notebookfunctions/api/notebook/index.tsModificado: acepta shared_with
GET/PUT/DELETE/api/notebook/:idfunctions/api/notebook/[id]/index.tsReemplaza [id].ts eliminado; añade shared_with, version
GET/api/notebook/:id/versionfunctions/api/notebook/[id]/version.tsNuevo — polling de versión
POST/api/notebook/:id/imagesfunctions/api/notebook/[id]/images.tsNuevo — subida de imágenes
GET/api/notebook-images/:idfunctions/api/notebook-images/[id].tsNuevo — stream de imagen con check de acceso

El fichero functions/api/notebook/[id].ts fue eliminado y sustituido por functions/api/notebook/[id]/index.ts para soportar subrutas (images, version).


Dependencias de infraestructura

RecursoUso
D1 notebook_notesTabla principal — extendida con shared_with + version
D1 notebook_attachmentsNueva tabla — ver [[entity—notebook—table—notebook-attachments]]
R2 TASK_ATTACHMENTSBucket compartido con tareas; prefix notebook/ para las imágenes de notas

Véase también

  • [[entity—notebook—table—notebook-attachments]]
  • [[entity—notebook—endpoint—notebook-images]]