Foro: botón «Wowhead» en la barra de TinyMCE

En el editor de crear/responder/editar aparece un botón «Wowhead» que abre un
diálogo (tipo: ítem/hechizo/misión/PNJ/logro/objeto, ID y texto opcional) e
inserta el enlace de wowhead ya listo: con su href a la rama /wotlk/ en el
subdominio del idioma de la web y su data-wowhead, para que salga el tooltip, el
color por calidad y el icono (lo pinta el script global tooltips.js).

Reutiliza wowheadUrl/wowheadData de lib/wowhead (mismo formato que los enlaces de
wowhead del resto del sitio). Si no se pone texto, se usa `<tipo>=<id>`. El ID se
limpia a dígitos. El HTML resultante lo sigue saneando el servidor.

Verificado: el registro del botón, los textos ES/EN del diálogo y el toolbar con
«wowhead» quedan compilados en el bundle; la página del editor responde.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 12:57:16 +00:00
parent 56f39ef0b4
commit 6f0c39025f
+78 -1
View File
@@ -1,6 +1,8 @@
'use client' 'use client'
import { Editor } from '@tinymce/tinymce-react' 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 * Editor TinyMCE del foro (community, self-hosted desde /tinymce — ver
@@ -12,7 +14,25 @@ import { Editor } from '@tinymce/tinymce-react'
* `licenseKey: 'gpl'` deja claro que es la edición community (GPL), no la de nube. * `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), * El HTML que produce se sanea SIEMPRE en el servidor con nh3 (lib/forum-sanitize),
* nunca se confía en el cliente. * 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}
export function ForumEditor({ export function ForumEditor({
value, value,
onChange, onChange,
@@ -22,6 +42,18 @@ export function ForumEditor({
onChange: (html: string) => void onChange: (html: string) => void
height?: number 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 (opcional)' : 'Text (optional)',
insert: es ? 'Insertar' : 'Insert',
cancel: es ? 'Cancelar' : 'Cancel',
}
return ( return (
<Editor <Editor
tinymceScriptSrc="/tinymce/tinymce.min.js" tinymceScriptSrc="/tinymce/tinymce.min.js"
@@ -40,8 +72,53 @@ export function ForumEditor({
plugins: plugins:
'preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount emoticons', 'preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount emoticons',
toolbar: toolbar:
'bold italic strikethrough forecolor backcolor emoticons | link image media | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | removeformat', 'bold italic strikethrough forecolor backcolor emoticons | link wowhead image media | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | removeformat',
image_advtab: true, image_advtab: true,
setup: (editor) => {
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 label = data.wtext.trim() || `${type}=${id}`
const href = wowheadUrl(type, id, locale)
const wh = wowheadData(type, id, locale)
editor.insertContent(
`<a href="${href}" data-wowhead="${esc(wh)}" target="_blank" rel="noopener noreferrer">${esc(label)}</a>&nbsp;`,
)
api.close()
},
})
},
})
},
}} }}
/> />
) )