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 } },