c4961879df
Dos ajustes sobre los enlaces de wowhead: 1) Color en el post. forum.css teñía TODOS los enlaces del post de dorado y los subrayaba, con más especificidad que la clase de calidad de wowhead, así que el nombre salía dorado/azul en vez del color de calidad. Ahora la regla dorada excluye los enlaces con data-wowhead (`a:not([data-wowhead])`) y a esos se les quita el subrayado, para que se vean con su color de calidad como en el tooltip. 2) Vista previa en el editor. TinyMCE vive en un iframe con su propio documento, al que no llega el tooltips.js de la página, por eso en el editor salía item=41599 en crudo. Se carga el script de wowhead dentro del iframe (con renameLinks:true) y se refresca tras insertar, así el ítem se ve con icono, nombre y calidad ya al redactar. No afecta a lo que se guarda: los formularios envían el HTML del estado de React (lo insertado, limpio), no la mutación visual del iframe. Verificado: el CSS servido excluye los enlaces de wowhead del dorado; la inyección del script en el iframe queda compilada en el bundle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
165 lines
6.8 KiB
TypeScript
165 lines
6.8 KiB
TypeScript
'use client'
|
|
|
|
import { Editor } from '@tinymce/tinymce-react'
|
|
import { useLocale } from 'next-intl'
|
|
import { wowheadUrl, wowheadData, type WowheadType } from '@/lib/wowhead'
|
|
|
|
/**
|
|
* Editor TinyMCE del foro (community, self-hosted desde /tinymce — ver
|
|
* scripts/copy-tinymce.mjs). Mismo editor que el foro original de FusionCMS, con su
|
|
* skin oscura y su lista de plugins, adaptado a React como componente controlado:
|
|
* el HTML vive en el estado del padre (value/onChange) y se envía por fetch, en vez
|
|
* del `tinyMCE.triggerSave()` + jQuery del forum.js original.
|
|
*
|
|
* `licenseKey: 'gpl'` deja claro que es la edición community (GPL), no la de nube.
|
|
* El HTML que produce se sanea SIEMPRE en el servidor con nh3 (lib/forum-sanitize),
|
|
* nunca se confía en el cliente.
|
|
*
|
|
* Botón «Wowhead»: abre un diálogo (tipo + ID + texto) e inserta un enlace de
|
|
* wowhead con su data-wowhead en el idioma de la web, para que salga el tooltip,
|
|
* el color por calidad y el icono (lo pinta el script global tooltips.js).
|
|
*/
|
|
|
|
const WOWHEAD_TYPES: { value: WowheadType; es: string; en: string }[] = [
|
|
{ value: 'item', es: 'Ítem', en: 'Item' },
|
|
{ value: 'spell', es: 'Hechizo', en: 'Spell' },
|
|
{ value: 'quest', es: 'Misión', en: 'Quest' },
|
|
{ value: 'npc', es: 'PNJ', en: 'NPC' },
|
|
{ value: 'achievement', es: 'Logro', en: 'Achievement' },
|
|
{ value: 'object', es: 'Objeto', en: 'Object' },
|
|
]
|
|
|
|
function esc(s: string): string {
|
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
}
|
|
|
|
export function ForumEditor({
|
|
value,
|
|
onChange,
|
|
height = 400,
|
|
}: {
|
|
value: string
|
|
onChange: (html: string) => void
|
|
height?: number
|
|
}) {
|
|
const locale = useLocale()
|
|
const es = locale !== 'en'
|
|
const L = {
|
|
title: 'Wowhead',
|
|
tooltip: es ? 'Insertar enlace de Wowhead' : 'Insert a Wowhead link',
|
|
type: es ? 'Tipo' : 'Type',
|
|
id: 'ID',
|
|
text: es ? 'Texto (vacío = nombre automático)' : 'Text (empty = auto name)',
|
|
insert: es ? 'Insertar' : 'Insert',
|
|
cancel: es ? 'Cancelar' : 'Cancel',
|
|
}
|
|
|
|
return (
|
|
<Editor
|
|
tinymceScriptSrc="/tinymce/tinymce.min.js"
|
|
licenseKey="gpl"
|
|
value={value}
|
|
onEditorChange={(content) => onChange(content)}
|
|
init={{
|
|
height,
|
|
menubar: false,
|
|
statusbar: false,
|
|
skin: 'oxide-dark',
|
|
content_css: 'dark',
|
|
branding: false,
|
|
promotion: false,
|
|
mobile: { menubar: true },
|
|
plugins:
|
|
'preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount emoticons',
|
|
toolbar:
|
|
'bold italic strikethrough forecolor backcolor emoticons | link wowhead image media | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | removeformat',
|
|
image_advtab: true,
|
|
setup: (editor) => {
|
|
// Vista previa: carga el script de tooltips de wowhead DENTRO del iframe
|
|
// del editor para que el enlace insertado se vea con icono, nombre y color
|
|
// de calidad, igual que al postear. No afecta a lo que se guarda: los
|
|
// formularios envían el HTML del estado de React (lo insertado, limpio),
|
|
// no la mutación visual que wowhead hace en el iframe.
|
|
const refreshEditorWowhead = () => {
|
|
try {
|
|
const wp = (editor.getWin() as unknown as { $WowheadPower?: { refreshLinks?: () => void } })
|
|
.$WowheadPower
|
|
wp?.refreshLinks?.()
|
|
} catch {
|
|
/* preview no crítico */
|
|
}
|
|
}
|
|
editor.on('init', () => {
|
|
try {
|
|
const doc = editor.getDoc()
|
|
const win = editor.getWin() as unknown as { whTooltips?: unknown }
|
|
win.whTooltips = { colorLinks: true, iconizeLinks: true, renameLinks: true }
|
|
if (!doc.getElementById('wh-tooltips-editor')) {
|
|
const s = doc.createElement('script')
|
|
s.id = 'wh-tooltips-editor'
|
|
s.src = 'https://wow.zamimg.com/js/tooltips.js'
|
|
doc.head.appendChild(s)
|
|
}
|
|
// procesa el contenido inicial (posts en edición) cuando el script cargue
|
|
setTimeout(refreshEditorWowhead, 400)
|
|
setTimeout(refreshEditorWowhead, 1200)
|
|
} catch {
|
|
/* preview no crítico */
|
|
}
|
|
})
|
|
editor.ui.registry.addButton('wowhead', {
|
|
text: 'Wowhead',
|
|
tooltip: L.tooltip,
|
|
onAction: () => {
|
|
editor.windowManager.open({
|
|
title: L.title,
|
|
body: {
|
|
type: 'panel',
|
|
items: [
|
|
{
|
|
type: 'listbox',
|
|
name: 'wtype',
|
|
label: L.type,
|
|
items: WOWHEAD_TYPES.map((t) => ({ value: t.value, text: es ? t.es : t.en })),
|
|
},
|
|
{ type: 'input', name: 'wid', label: L.id },
|
|
{ type: 'input', name: 'wtext', label: L.text },
|
|
],
|
|
},
|
|
initialData: { wtype: 'item', wid: '', wtext: '' },
|
|
buttons: [
|
|
{ type: 'cancel', text: L.cancel },
|
|
{ type: 'submit', text: L.insert, primary: true },
|
|
],
|
|
onSubmit: (api) => {
|
|
const data = api.getData() as { wtype: WowheadType; wid: string; wtext: string }
|
|
const id = String(data.wid).trim().replace(/\D/g, '')
|
|
if (!id) {
|
|
api.close()
|
|
return
|
|
}
|
|
const type = (data.wtype || 'item') as WowheadType
|
|
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(
|
|
`<a href="${href}" data-wowhead="${esc(wh)}"${rename} target="_blank" rel="noopener noreferrer">${label}</a> `,
|
|
)
|
|
api.close()
|
|
// pinta el ítem recién insertado en el editor (icono/nombre/calidad)
|
|
setTimeout(refreshEditorWowhead, 60)
|
|
},
|
|
})
|
|
},
|
|
})
|
|
},
|
|
}}
|
|
/>
|
|
)
|
|
}
|