From 799341a1a4e030b2f78c637a95b49aeb55865022 Mon Sep 17 00:00:00 2001 From: adevopg Date: Thu, 16 Jul 2026 13:26:09 +0000 Subject: [PATCH] =?UTF-8?q?Foro:=20=C3=ADtems=20de=20wowhead=20sin=20parpa?= =?UTF-8?q?deo=20y=20con=20color=20de=20calidad=20correcto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dos problemas restantes con los enlaces de wowhead: 1) Texto blanco en vez del color de calidad. theme.css solo define q3..q6, así que un ítem de calidad 0/1/2 (p.ej. verde=q2) se quedaba sin color y salía blanco. Se completan .q0/.q1/.q2/.q7 en forum.css (colores estándar de WoW) con !important para ganar al color del enlace. Además el saneador deja los enlaces de wowhead SIN nofollow (el script de wowhead ignora los nofollow al colorear). 2) Parpadeo de «item=xxx» al recargar. Salía porque el texto del enlace era un marcador que wowhead renombraba de forma asíncrona. Ahora el botón resuelve el NOMBRE y la CALIDAD en el servidor (nuevo /api/forum/wowhead, que consulta el endpoint de tooltip de wowhead en la rama WotLK y el idioma de la web) e inserta el enlace ya con su nombre real y su clase qN: sin parpadeo y ya coloreado. Si la API falla, se cae al modo anterior (data-wh-rename-link). El editor añade los colores de calidad a su content_style para que el preview salga igual. El icono lo sigue poniendo wowhead (iconizeLinks) tanto en el editor como al postear. Verificado en producción: el endpoint devuelve nombre (es), calidad e icono; el CSS sirve q2; un post con nombre real + q2 se guarda sin nofollow. Co-Authored-By: Claude Opus 4.8 (1M context) --- web-next/app/api/forum/wowhead/route.ts | 33 +++++++++++++++++++++++ web-next/app/forum.css | 12 +++++++++ web-next/components/ForumEditor.tsx | 36 ++++++++++++++++++------- web-next/lib/forum-sanitize.ts | 11 +++++--- 4 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 web-next/app/api/forum/wowhead/route.ts diff --git a/web-next/app/api/forum/wowhead/route.ts b/web-next/app/api/forum/wowhead/route.ts new file mode 100644 index 0000000..c0e164b --- /dev/null +++ b/web-next/app/api/forum/wowhead/route.ts @@ -0,0 +1,33 @@ +// Resuelve nombre, calidad e icono de una entidad de wowhead (rama WotLK) para que +// el editor del foro pueda insertar el enlace ya con su nombre real y su color de +// calidad, sin el parpadeo de «item=xxx». Usa el endpoint de tooltip de wowhead +// (el mismo que consume tooltips.js) con dataEnv=8 = WotLK Classic. + +const TYPES = new Set(['item', 'spell', 'quest', 'npc', 'achievement', 'object']) +// Código de locale de wowhead: 0 = inglés, 6 = español. +const WH_LOCALE: Record = { es: 6, en: 0 } + +export async function GET(request: Request) { + const url = new URL(request.url) + const type = String(url.searchParams.get('type') || 'item') + const id = String(url.searchParams.get('id') || '').replace(/\D/g, '') + const locale = String(url.searchParams.get('locale') || 'es') + if (!TYPES.has(type) || !id) return Response.json({ name: null }, { status: 400 }) + + const wl = WH_LOCALE[locale] ?? 0 + try { + const r = await fetch(`https://nether.wowhead.com/tooltip/${type}/${id}?dataEnv=8&locale=${wl}`, { + // cache un día: los nombres/calidades no cambian + next: { revalidate: 86400 }, + }) + if (!r.ok) return Response.json({ name: null }) + const d = (await r.json()) as { name?: string; quality?: number; icon?: string } + return Response.json({ + name: d.name ?? null, + quality: typeof d.quality === 'number' ? d.quality : null, + icon: d.icon ?? null, + }) + } catch { + return Response.json({ name: null }) + } +} diff --git a/web-next/app/forum.css b/web-next/app/forum.css index 82793c8..72c3d6c 100644 --- a/web-next/app/forum.css +++ b/web-next/app/forum.css @@ -201,6 +201,18 @@ h3.post_field dd { margin: 0; } SU color de calidad e icono, sin subrayado, para que se vean como en el tooltip. */ .topic_post .right_side .post_container a:not([data-wowhead]) { color: var(--forum-gold-soft); text-decoration: underline; } .topic_post .right_side .post_container a[data-wowhead] { text-decoration: none; } + +/* Colores de calidad de WoW. theme.css solo trae q3..q6, así que aquí se completan + q0..q2 y q7 (con !important para ganar al color del enlace del post). Sin esto, + p.ej. un ítem verde (q2) se quedaba sin color y salía blanco. */ +.post_container a.q0, .q0 { color: #9d9d9d !important; } +.post_container a.q1, .q1 { color: #ffffff !important; } +.post_container a.q2, .q2 { color: #1eff00 !important; } +.post_container a.q3 { color: #0070dd !important; } +.post_container a.q4 { color: #a335ee !important; } +.post_container a.q5 { color: #ff8000 !important; } +.post_container a.q6 { color: #e6cc80 !important; } +.post_container a.q7, .q7 { color: #00ccff !important; } .post_deleted { opacity: .5; } ul.post_controls { display: flex; justify-content: flex-end; align-items: center; gap: 8px; list-style: none; padding: 15px; margin: auto 0 0; } diff --git a/web-next/components/ForumEditor.tsx b/web-next/components/ForumEditor.tsx index e12a8f2..ba303f5 100644 --- a/web-next/components/ForumEditor.tsx +++ b/web-next/components/ForumEditor.tsx @@ -66,6 +66,11 @@ export function ForumEditor({ statusbar: false, skin: 'oxide-dark', content_css: 'dark', + // colores de calidad de WoW dentro del editor (para el preview de wowhead) + content_style: + 'a{text-decoration:none}' + + '.q0{color:#9d9d9d}.q1{color:#fff}.q2{color:#1eff00}.q3{color:#0070dd}' + + '.q4{color:#a335ee}.q5{color:#ff8000}.q6{color:#e6cc80}.q7{color:#00ccff}', branding: false, promotion: false, mobile: { menubar: true }, @@ -131,7 +136,7 @@ export function ForumEditor({ { type: 'cancel', text: L.cancel }, { type: 'submit', text: L.insert, primary: true }, ], - onSubmit: (api) => { + onSubmit: async (api) => { const data = api.getData() as { wtype: WowheadType; wid: string; wtext: string } const id = String(data.wid).trim().replace(/\D/g, '') if (!id) { @@ -142,16 +147,27 @@ export function ForumEditor({ const href = wowheadUrl(type, id, locale) const wh = wowheadData(type, id, locale) const custom = data.wtext.trim() - // Sin texto: wowhead pone el nombre real (y color+icono) gracias a - // data-wh-rename-link; el `type=id` es solo un marcador hasta que - // corre el script. Con texto: se respeta el que escriba el usuario. - const rename = custom ? '' : ' data-wh-rename-link="true"' - const label = esc(custom || `${type}=${id}`) - editor.insertContent( - `${label} `, - ) api.close() - // pinta el ítem recién insertado en el editor (icono/nombre/calidad) + + // Resolver nombre + calidad en el servidor para insertar el enlace + // YA con su nombre real y su color (clase qN), sin el parpadeo de + // «item=xxx». Si falla, se cae al modo data-wh-rename-link. + let name = custom + let qClass = '' + try { + const res = await fetch(`/api/forum/wowhead?type=${type}&id=${id}&locale=${locale}`) + const info = (await res.json()) as { name: string | null; quality: number | null } + if (!name && info.name) name = info.name + if (typeof info.quality === 'number' && info.quality >= 0) qClass = ` q${info.quality}` + } catch { + /* wowhead no disponible: se usa el fallback */ + } + const rename = name ? '' : ' data-wh-rename-link="true"' + const label = esc(name || `${type}=${id}`) + editor.insertContent( + `${label} `, + ) + // icono en el editor (el nombre y el color ya van puestos) setTimeout(refreshEditorWowhead, 60) }, }) diff --git a/web-next/lib/forum-sanitize.ts b/web-next/lib/forum-sanitize.ts index fd48780..7a4a035 100644 --- a/web-next/lib/forum-sanitize.ts +++ b/web-next/lib/forum-sanitize.ts @@ -48,11 +48,14 @@ export function cleanPostHtml(html: string): string { allowedSchemes: ['http', 'https', 'mailto'], transformTags: { a: (tagName, attribs) => { - const out: Record = { ...attribs, rel: 'noopener noreferrer nofollow' } - if (out.href && /\.wowhead\.com\//i.test(out.href)) { + 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) - // abrir en pestaña nueva, como los enlaces de wowhead del resto del sitio - out.target = '_blank' + out.target = '_blank' // pestaña nueva, como el resto del sitio } return { tagName, attribs: out } },