Files
NightSpire/web-next/lib/forum-sanitize.ts
T
Inna 56f39ef0b4 Foro: permitir enlaces de wowhead en los posts (tooltip, color, icono)
Al escribir un post se puede pegar un enlace de wowhead y sale con su tooltip,
color por calidad e icono, igual que en el resto de la web (lo pinta el script
global tooltips.js del layout, que actúa sobre los enlaces a wowhead.com y sobre
data-wowhead).

El saneador de posts (lib/forum-sanitize.ts) lo impedía; ahora:
- conserva data-wowhead (en <a> y <span>) y class, que el tooltip necesita;
- normaliza los enlaces de wowhead a la rama /wotlk/ manteniendo el subdominio
  (www=inglés, es=español…), que es lo que fija el idioma del tooltip. Así un
  enlace pegado en retail muestra igualmente el tooltip de WotLK;
- abre esos enlaces en pestaña nueva, como los del resto del sitio.
Los enlaces que no son de wowhead se quedan igual.

Verificado en producción: un enlace retail pegado en un post se guarda como
.../wotlk/item=… y la página del tema carga tooltips.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 12:52:40 +00:00

67 lines
2.6 KiB
TypeScript

import sanitizeHtml from 'sanitize-html'
// Tipos de entidad de wowhead que aceptan tooltip (para normalizar el enlace).
const WOWHEAD_TYPES = 'item|spell|quest|npc|achievement|object|faction|currency|itemset|title'
/**
* Normaliza un enlace de wowhead para que el tooltip salga en la rama WotLK.
* - Mantiene el subdominio (www=inglés, es=español…), que es lo que fija el idioma
* del tooltip cuando no hay data-wowhead.
* - Fuerza el segmento `/wotlk/` (si el usuario pega un enlace retail, el tooltip
* saldría de retail; así siempre es WotLK).
* Devuelve el href tal cual si no reconoce un `<tipo>=<id>`.
*/
function normalizeWowheadHref(href: string): string {
const m = /^https?:\/\/([a-z]+)\.wowhead\.com\/.*?\b(?:wotlk\/)?(?:(item|spell|quest|npc|achievement|object|faction|currency|itemset|title)=(\d+))/i.exec(
href,
)
if (!m) return href
const [, sub, type, id] = m
return `https://${sub}.wowhead.com/wotlk/${type.toLowerCase()}=${id}`
}
// Misma allowlist que forum/sanitize.py (nh3) para el HTML de los mensajes.
export function cleanPostHtml(html: string): string {
if (!html) return ''
return sanitizeHtml(html, {
allowedTags: [
'p', 'br', 'hr', 'span', 'div',
'strong', 'b', 'em', 'i', 'u', 's', 'strike', 'sub', 'sup',
'ul', 'ol', 'li', 'blockquote', 'code', 'pre',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'a', 'img',
'table', 'thead', 'tbody', 'tr', 'th', 'td',
],
allowedAttributes: {
// data-wowhead + class dejan que los enlaces de wowhead muestren tooltip,
// color por calidad e icono (los pinta el script global tooltips.js del layout).
a: ['href', 'title', 'target', 'data-wowhead', 'class'],
img: ['src', 'alt', 'title', 'width', 'height'],
span: ['style', 'data-wowhead'],
div: ['style'],
td: ['colspan', 'rowspan'],
th: ['colspan', 'rowspan'],
},
allowedSchemes: ['http', 'https', 'mailto'],
transformTags: {
a: (tagName, attribs) => {
const out: Record<string, string> = { ...attribs, rel: 'noopener noreferrer nofollow' }
if (out.href && /\.wowhead\.com\//i.test(out.href)) {
out.href = normalizeWowheadHref(out.href)
// abrir en pestaña nueva, como los enlaces de wowhead del resto del sitio
out.target = '_blank'
}
return { tagName, attribs: out }
},
},
})
}
/** Longitud del texto plano (sin etiquetas) — para validar longitud mínima. */
export function plainLength(html: string): number {
return sanitizeHtml(html || '', { allowedTags: [], allowedAttributes: {} })
.replace(/&nbsp;/g, ' ')
.replace(/\s+/g, ' ')
.trim().length
}