Contents
Introducción
Este artículo ofrece un tutorial completo para WordPress sobre cómo escuchar eventos del editor y validar contenido usando JavaScript. Cubre tanto el editor clásico (TinyMCE) como el editor de bloques (Gutenberg), muestra ejemplos prácticos para detectar cambios, validar reglas (conteo de palabras, estructura de bloques, presencia de atributos, etc.), bloquear el guardado/publicación cuando la validación falla y añadir validaciones servidor-side para seguridad.
Resumen de conceptos
- Editor clásico (TinyMCE): escucha eventos de la instancia de TinyMCE o del textarea #content para detectar cambios.
- Editor de bloques (Gutenberg): usa la API de estados de Redux que expone WordPress (wp.data) y componentes del paquete edit-post para enganchar validaciones y paneles previos a la publicación.
- Validación cliente vs servidor: Validar en el cliente mejora UX (avisos inmediatos) pero siempre hay que repetir validaciones en servidor (hooks REST/submit) para seguridad y coherencia.
- Bloquear guardado/publicación: en Gutenberg se puede usar wp.data.dispatch(core/editor).lockPostSaving(…) y unlockPostSaving(…).
1) Editor clásico (TinyMCE) — escuchar y validar
En instalaciones que aún usan el editor clásico o Classic Editor plugin, TinyMCE es el editor WYSIWYG. Puedes enganchar eventos en la instancia de tinymce o bien escuchar cambios en el textarea. Ejemplos prácticos:
Escuchar cambios en TinyMCE
(function() { // Espera a que TinyMCE esté listo if ( typeof tinymce !== undefined ) { tinymce.PluginManager.add(mi_plugin_validacion, function(editor) { // Captura cambios en el editor editor.on(change keyup Undo Redo, function() { var contenido = editor.getContent({ format: raw }) validarContenidoClassico(contenido) }) }) // Si TinyMCE ya está inicializado en la página podemos enganchar directamente: if (tinymce.activeEditor) { tinymce.activeEditor.on(change, function() { var contenido = tinymce.activeEditor.getContent({ format: raw }) validarContenidoClassico(contenido) }) } } // Fallback para el textarea puro var textarea = document.getElementById(content) if (textarea) { textarea.addEventListener(input, function() { validarContenidoClassico(textarea.value) }) } function validarContenidoClassico(html) { // Ejemplo simple: comprobar mínimo 300 palabras var text = html.replace(/<[^>]>/g, ).replace(/s /g, ).trim() var palabras = text ? text.split( ).length : 0 if (palabras < 300) { // Mostrar aviso simple (puedes usar jQuery o WP admin notices) console.warn(El contenido tiene menos de 300 palabras: palabras) } else { console.log(Contenido OK: palabras palabras) } } })()
Consejos para el clásico
- Para mostrar mensajes en el admin usa la API de admin-notices (en PHP) o manipula el DOM para inyectar avisos.
- Ten en cuenta performance: evita operaciones costosas en cada keyup, usa debounce.
- Si usas plugins que cambian la forma del editor, prueba en varios escenarios.
2) Editor de bloques (Gutenberg) — patrones y APIs
Gutenberg expone una arquitectura basada en React Redux. Las utilidades más útiles para escuchar y validar son:
- wp.data.subscribe — para suscribirte a cambios en el store.
- wp.data.select(core/editor) / select(core/block-editor) — para leer estado y bloques.
- wp.data.dispatch(core/notices).createNotice — para mostrar notificaciones en UI.
- wp.data.dispatch(core/editor).lockPostSaving / unlockPostSaving — para impedir guardados/publicaciones hasta que la validación pase.
- Componentes como PluginPrePublishPanel (desde wp.editPost) para integrar comprobaciones antes de publicar.
Ejemplo: escuchar cambios y validar con wp.data.subscribe
( function( wp ) { var subscribe = wp.data.subscribe var select = wp.data.select var dispatch = wp.data.dispatch var lastContent = null var validationLocked = false // para no repetir locks function validar() { // Podemos tomar el HTML o los blocks var html = select(core/editor).getEditedPostContent() // Ejemplo: validar mínimo 200 palabras var text = html.replace(/<[^>]>/g, ).replace(/s /g, ).trim() var palabras = text ? text.split( ).length : 0 var errors = [] if (palabras < 200) { errors.push(El contenido debe tener al menos 200 palabras. Actualmente: palabras) } // Validación por bloques: ej. comprobar existencia de al menos un H2 var blocks = select(core/block-editor).getBlocks() var tieneH2 = blocks.some(function (b) { return b.name === core/heading (b.attributes b.attributes.level === 2) }) if (!tieneH2) { errors.push(Añade al menos un encabezado H2 para mejorar la estructura del contenido.) } return errors } subscribe( function() { var content = select(core/editor).getEditedPostContent() if (content === lastContent) { return } lastContent = content var errors = validar() if (errors.length) { // Bloquear guarda/publicación if (!validationLocked) { dispatch(core/editor).lockPostSaving(mi-validacion-plugin) validationLocked = true } // Crear una notificación con errores (evita duplicados gestionando IDs) dispatch(core/notices).createNotice( error, errors.join( — ), { isDismissible: true } ) } else { if (validationLocked) { dispatch(core/editor).unlockPostSaving(mi-validacion-plugin) validationLocked = false } } } ) } )( window.wp )
Explicación del ejemplo
- Usamos select(core/editor).getEditedPostContent() para obtener el HTML editado (puede contener bloques no serializados).
- select(core/block-editor).getBlocks() devuelve un arreglo de bloques que permite inspeccionar nombre y atributos (ideal para comprobar imágenes, alt, headings, etc.).
- Si la validación encuentra errores llamamos lockPostSaving con una llave identificadora cuando desaparecen, llamamos unlockPostSaving con la misma llave.
- Para informar al usuario usamos createNotice(error, ...).
Ejemplo: panel pre-publicación (PluginPrePublishPanel)
( function( wp ) { var registerPlugin = wp.plugins.registerPlugin var PluginPrePublishPanel = wp.editPost.PluginPrePublishPanel var el = wp.element.createElement var withSelect = wp.data.withSelect var withDispatch = wp.data.withDispatch var MyPrePublishPanel = withSelect(function(select) { return { blocks: select(core/block-editor).getBlocks() } })( function(props) { var blocks = props.blocks [] var tieneImagenDestacada = wp.data.select(core/editor).getEditedPostAttribute(featured_media) > 0 var errores = [] if (!tieneImagenDestacada) { errores.push(Falta imagen destacada.) } // Otra comprobación: al menos un párrafo de 100 palabras var html = wp.data.select(core/editor).getEditedPostContent() var text = html.replace(/<[^>]>/g, ).replace(/s /g, ).trim() var palabras = text ? text.split( ).length : 0 if (palabras < 100) { errores.push(Mínimo 100 palabras en el post.) } return el( PluginPrePublishPanel, { title: Comprobaciones previas a publicar }, el(div, {}, errores.length ? errores.map(function(e){ return el(p, { style: { color: red } }, e) }) : el(p, {}, Todo OK. Puedes publicar.) ) ) }) registerPlugin(mi-prepublish-panel, { render: MyPrePublishPanel }) } )( window.wp )
3) Inspeccionar y recorrer bloques (caso avanzado)
A menudo conviene analizar en profundidad la jerarquía de bloques (innerBlocks) para validar, por ejemplo, que todas las imágenes tengan atributo alt o que los bloques personalizados contengan metadata.
function recorrerBloques(blocks, callback) { blocks.forEach(function(block) { callback(block) if (block.innerBlocks block.innerBlocks.length) { recorrerBloques(block.innerBlocks, callback) } }) } // Ejemplo de uso: var blocks = wp.data.select(core/block-editor).getBlocks() var errores = [] recorrerBloques(blocks, function(b) { if (b.name === core/image) { var alt = b.attributes b.attributes.alt ? b.attributes.alt.trim() : if (!alt) { errores.push(Una imagen sin texto alternativo detectada.) } } }) // errores ahora contiene las incidencias
4) Evitar falsos positivos y mejorar performance
- Usa debounce al ejecutar validaciones costosas (por ejemplo parseo completo) para no ejecutar en cada pulsación.
- Evita mostrar notificaciones duplicadas: gestiona IDs o comprueba la existencia antes de crear una nueva notificación.
- Cuando inspecciones blocks, usar select(core/block-editor).getBlocks() es más fiable que parsear HTML manualmente en la mayoría de los casos.
- Guarda el estado de validación en una closure o store para evitar recálculos innecesarios.
5) Validación servidor-side (muy importante)
Las validaciones del cliente mejoran la experiencia, pero no sustituyen la validación en servidor. WordPress permite interceptar las peticiones REST para verificar el contenido antes de insertar/actualizar.
Ejemplo PHP: rest_pre_insert_post
post_content ) ? prepared_post->post_content : // Quitar etiquetas y contar palabras text = wp_strip_all_tags( content ) words = str_word_count( text ) if ( words < 50 ) { return new WP_Error( contenido_demasiado_corto, El contenido es demasiado corto. Mínimo 50 palabras., array( status => 400 ) ) } // Validación adicional: bloquear ciertas etiquetas if ( preg_match( /no están permitidas en el contenido., array( status => 400 ) ) } return prepared_post } ?>
6) Encolar tu script JS para el editor de bloques (PHP)
Sube y registra tu script para editar-post o bloque usando las dependencias correctas.
7) Buenas prácticas y recomendaciones finales
- Siempre repetir validaciones en servidor: cualquier validación crítica debe estar en PHP (hooks REST, save_post, rest_pre_insert_post, rest_pre_update). El cliente es solo UX.
- Usar unlock/lock con llave propia: pasa siempre la misma llave para lockPostSaving y unlockPostSaving.
- Debounce y throttling: para validar contenido grande, aplica debounce al subscribe o cuando escuchas keyups.
- Notificaciones claras: informa siempre qué se debe corregir y, si es posible, cómo corregirlo.
- Compatibilidad: prueba con plugins de caché, SEO y constructores que pueden alterar contenido o hooks del editor.
- Internacionalización: si tu plugin se publica, usa funciones de i18n en el lado PHP y soporta traducciones en strings JS.
Conclusión
Escuchar eventos en el editor y validar contenido en WordPress combina técnicas distintas según el editor: TinyMCE para el clásico y la API de wp.data componentes para Gutenberg. Implementa validaciones cliente para mejorar la UX, pero siempre complementa con validación servidor-side. Usa los mecanismos de bloqueo (lockPostSaving) para impedir guardados y notificaciones para orientar al autor. Con las funciones y ejemplos mostrados podrás crear comprobaciones robustas (estructura de encabezados, alt en imágenes, conteo de palabras, prohibición de etiquetas inseguras, etc.) y ofrecer una experiencia coherente y segura.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |