120 lines
5.8 KiB
Markdown
120 lines
5.8 KiB
Markdown
# Limpieza de HTML embebido (Publicaciones y Entrelíneas)
|
|
|
|
Notas técnicas sobre el saneamiento de HTML que llega desde el CMS/índice de búsqueda
|
|
y se renderiza con `v-html` en los paneles de detalle. Documenta el problema, la causa
|
|
raíz y el fix aplicado en cada caso, para poder depurar o mejorar esto más adelante.
|
|
|
|
## Contexto general
|
|
|
|
Varios documentos (publicaciones, entrelíneas) traen un campo `html` que en teoría es
|
|
un fragmento de texto formateado, pero en la práctica viene "contaminado" con marcado
|
|
que no debería estar ahí: estilos de Word, anchos fijos, o incluso HTML ya renderizado
|
|
de otro componente que fue copiado/pegado en Directus por error. Ese marcado extra
|
|
puede romper el layout porque se inyecta directo en el DOM de la app (clases de
|
|
Tailwind, `data-*`, tablas de Word con `width` en `cm`/`pt`, etc.).
|
|
|
|
Cada vista que hace `v-html` de estos campos tiene su propia función de limpieza
|
|
(basada en regex, no en un parser DOM) justo antes de renderizar.
|
|
|
|
---
|
|
|
|
## Caso 1 — `PublicationDetail.vue`: HTML pegado desde Word
|
|
|
|
**Archivo:** [`app/components/PublicationDetail.vue`](../app/components/PublicationDetail.vue)
|
|
|
|
**Síntoma:** tablas/celdas con overflow horizontal o contenido comprimido en el panel
|
|
de detalle de publicaciones.
|
|
|
|
**Causa raíz:** el HTML exportado/pegado desde Word trae:
|
|
- Bloques `<style>` completos con reglas `mso-*`.
|
|
- Anchos fijos en unidades absolutas (`width: 15.5cm`, `pt`, etc.) en tablas y celdas,
|
|
que no responden al layout del contenedor.
|
|
|
|
**Fix:** función `cleanWordHtml()` (cerca de la línea 85) aplicada al `v-html` del
|
|
párrafo:
|
|
- Elimina bloques `<style>`.
|
|
- Elimina declaraciones `width: <número><cm|mm|pt|px|em|rem|in|pc>`.
|
|
- Colapsa saltos de línea literales a un espacio.
|
|
|
|
Reforzado con CSS en `.paragraph-html` (`:deep(p|span|li|td|th)` → `white-space:
|
|
normal`, tablas a `width: 100%` / `table-layout: auto`, celdas y divs a `width: auto` /
|
|
`max-width: 100%`) para cubrir estilos que la regex no puede tocar.
|
|
|
|
---
|
|
|
|
## Caso 2 — `EntrelineaDetail.vue`: HTML contaminado con clases de otro componente
|
|
|
|
**Archivo:** [`app/components/entrelineas/EntrelineaDetail.vue`](../app/components/entrelineas/EntrelineaDetail.vue)
|
|
|
|
**Síntoma:** el texto de la entrelínea se mostraba con **una palabra por línea**,
|
|
dejando la mayor parte del panel en blanco.
|
|
|
|
**Causa raíz (confirmada consultando el documento directo en Typesense):** el campo
|
|
`html` de ese registro no contenía un `<p>` por palabra (esa fue la hipótesis inicial,
|
|
descartada). Contenía el HTML **ya renderizado de `PublicationDetail.vue`** pegado por
|
|
error en Directus, incluyendo:
|
|
- Atributos de scoping de Vue (`data-v-d5ee3d80`).
|
|
- `data-paragraph-number="37"`.
|
|
- Clases de Tailwind reales: `grid grid-cols-1fr items-start gap-2 mb-2
|
|
grid-cols-[20px_1fr]`.
|
|
|
|
Como esas clases son utilidades globales de Tailwind, se aplicaban igual dentro de
|
|
`EntrelineaDetail`. El contenedor quedaba como grid de 2 columnas (`20px 1fr`) y, al
|
|
tener un solo hijo, el auto-placement lo metía en la columna de **20px** → todo el
|
|
párrafo se comprimía a un ancho mínimo y cada palabra terminaba en su propia línea al
|
|
hacer wrap.
|
|
|
|
**Cómo se verificó:** se consultó el documento crudo directo en Typesense:
|
|
|
|
```bash
|
|
curl -s "$NUXT_PUBLIC_TYPESENSE_URL/multi_search" \
|
|
-H "X-TYPESENSE-API-KEY: $NUXT_PUBLIC_TYPESENSE_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"searches":[{"collection":"entrelineas","q":"*","filter_by":"id:=<ID_DEL_DOC>","include_fields":"*"}]}'
|
|
```
|
|
|
|
Esto es útil para cualquier bug futuro de renderizado: primero confirmar qué HTML
|
|
crudo hay realmente en el índice antes de asumir la causa.
|
|
|
|
**Fix:** función `formatEntrelineaText()` (cerca de la línea 56), agrega dos
|
|
reemplazos antes de los ya existentes:
|
|
- Elimina cualquier atributo `class="..."` / `class='...'`.
|
|
- Elimina cualquier atributo `data-*="..."` / `data-*='...'`.
|
|
|
|
Esto neutraliza clases o atributos de scoping filtrados desde cualquier otra fuente,
|
|
sin tocar los `style` inline (que sí son necesarios: cursiva, colores, fuente del
|
|
documento original).
|
|
|
|
---
|
|
|
|
## Limitaciones conocidas
|
|
|
|
- Ambas limpiezas son **basadas en regex**, no en un parser DOM real. Cubren los casos
|
|
vistos hasta ahora, pero no garantizan sanear cualquier HTML arbitrario (por ejemplo,
|
|
no tocan `style="display: grid; ..."` puesto inline, solo `width` en unidades
|
|
absolutas).
|
|
- La lógica está **duplicada** entre `cleanWordHtml` (Publicaciones) y
|
|
`formatEntrelineaText` (Entrelíneas). Si aparece un caso nuevo, hay que recordar
|
|
aplicarlo en los dos lugares (o consolidarlos, ver abajo).
|
|
- El origen del problema está en los datos (Directus / proceso de carga), no en el
|
|
frontend. Estas funciones son un parche en el punto de renderizado, no una
|
|
corrección en la fuente.
|
|
|
|
## Ideas para mejorar esto a futuro
|
|
|
|
1. **Consolidar en un solo util compartido**, por ejemplo en
|
|
[`app/utils/textUtilities.ts`](../app/utils/textUtilities.ts) o un nuevo
|
|
`app/utils/htmlSanitizer.ts`, con funciones nombradas por lo que hacen
|
|
(`stripStyleBlocks`, `stripAbsoluteWidths`, `stripClassAndDataAttrs`,
|
|
`collapseNewlines`) y componerlas según necesite cada vista, en vez de tener dos
|
|
funciones casi idénticas.
|
|
2. **Agregar tests de regresión** con fixtures de HTML "sucio" ya vistos en producción
|
|
(el de Word con `mso-*`, el de la entrelínea contaminada con clases de grid) para
|
|
que un cambio futuro no reintroduzca estos bugs silenciosamente.
|
|
3. **Sanear en el origen** (al indexar en Typesense o al guardar en Directus) en lugar
|
|
de en cada punto de render, para que cualquier consumidor futuro del campo `html`
|
|
(snippets en listas, exportaciones, etc.) reciba datos ya limpios.
|
|
4. Si se detectan más casos de contaminación cruzada entre componentes, vale la pena
|
|
revisar el flujo de carga de contenido en Directus para encontrar dónde se está
|
|
pegando HTML renderizado en vez de HTML fuente.
|