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 `=`. */ 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). // data-wh-rename-link deja que wowhead ponga el NOMBRE del ítem/misión/etc. // automáticamente (sobrescribe el renameLinks:false global, que existe para no // pisar los nombres en español de la tienda). data-wh-icon-size ajusta el icono. a: ['href', 'title', 'target', 'data-wowhead', 'data-wh-rename-link', 'data-wh-icon-size', '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 = { ...attribs } const isWowhead = Boolean(out.href && /\.wowhead\.com\//i.test(out.href)) // Los enlaces de wowhead SIN nofollow: el script de wowhead ignora los // nofollow al colorear/iconizar, y así se comportan como en la tienda. out.rel = isWowhead ? 'noopener noreferrer' : 'noopener noreferrer nofollow' if (isWowhead) { out.href = normalizeWowheadHref(out.href) out.target = '_blank' // pestaña nueva, como el resto del sitio } 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(/ /g, ' ') .replace(/\s+/g, ' ') .trim().length }