Disparar JavaScript al guardar en Gutenberg (Editor de Bloques)
Tengo un metabox y quiero activar algún código JavaScript cuando se guarda un post (para refrescar la página en este caso de uso).
En el Editor Clásico, esto se puede hacer mediante un simple redireccionamiento enganchado a save_post
(con alta prioridad).
Pero como Gutenberg convierte el proceso de guardado para metaboxes existentes en llamadas AJAX individuales, ahora necesita ser JavaScript. Entonces, ¿cómo puedo:
Escuchar un evento donde todos los procesos de guardado estén completos y luego activar el JavaScript? Si es así, ¿cómo se llama este evento? ¿Existe alguna referencia a estos eventos? O
Activar JavaScript dentro del proceso AJAX de guardado del metabox, que luego pueda verificar el estado del proceso de guardado de la página principal antes de continuar?

No estoy seguro si hay una mejor manera, pero estoy escuchando subscribe
en lugar de agregar un event listener al botón:
wp.data.subscribe(function () {
var isSavingPost = wp.data.select('core/editor').isSavingPost();
var isAutosavingPost = wp.data.select('core/editor').isAutosavingPost();
if (isSavingPost && !isAutosavingPost) {
// Aquí va tu código AJAX ......
}
})
Documentación oficial de los datos del Editor de Entradas: https://wordpress.org/gutenberg/handbook/designers-developers/developers/data/data-core-editor/

esto se ve mucho más limpio, solo por curiosidad ¿de dónde viene el método subscribe
? ¿es parte de la función wp.data
? No lo veo mencionado en la documentación.

Sí, subscribe
es un método del módulo wp.data. Abre la consola al editar una publicación con Gutenberg y ejecuta wp.data
. Esto muestra todos los métodos disponibles del módulo de datos.

¡bien hecho por encontrar esto! es una lástima que la documentación de Gutenberg esté tan oscuramente organizada y no tenga suficientes ejemplos. además, la expectativa de que los desarrolladores sepan y/o quieran aprender métodos de React es realmente demasiado... Estoy seguro de que puede ahorrar mucho tiempo si ya lo conoces, pero es una verdadera pérdida de tiempo si no lo sabes; me tomó horas solo para averiguar cómo acceder a algo útil en el modelo wp.data
. vuelvo a PHP (y al editor clásico) para mí.

¡Gracias por compartir esto! ¿Cómo puedo interceptar y detener la actualización/publicación del post basado en una condición?

Parece que este método también activa el código cuando un usuario hace clic en el botón "Mover a la papelera" (el estado del post cambia a "trash" y el valor de isSavingPost es "true" independientemente de esto). Además, un solo clic en "Actualizar" activó el código de suscripción 3 veces en mi caso. Terminé escuchando los clics en .editor-post-publish-button, .editor-post-save-draft y .editor-post-preview.

Este fragmento de código es genial pero se activará múltiples veces, el siguiente hilo explica cómo evitar que esto suceda: https://github.com/WordPress/gutenberg/issues/5975#issuecomment-483488988

Bien, esta es una solución mucho más improvisada de lo que quería, pero funciona...
Aquí hay una forma ligeramente simplificada y abstracta de hacerlo según mi código, por si alguien necesita hacer lo mismo (estoy seguro de que más plugins lo necesitarán en un futuro cercano).
var reload_check = false; var publish_button_click = false;
jQuery(document).ready(function($) {
add_publish_button_click = setInterval(function() {
$publish_button = jQuery('.edit-post-header__settings .editor-post-publish-button');
if ($publish_button && !publish_button_click) {
publish_button_click = true;
$publish_button.on('click', function() {
var reloader = setInterval(function() {
if (reload_check) {return;} else {reload_check = true;}
postsaving = wp.data.select('core/editor').isSavingPost();
autosaving = wp.data.select('core/editor').isAutosavingPost();
success = wp.data.select('core/editor').didPostSaveRequestSucceed();
console.log('Guardando: '+postsaving+' - Autoguardado: '+autosaving+' - Éxito: '+success);
if (postsaving || autosaving || !success) {classic_reload_check = false; return;}
clearInterval(reloader);
value = document.getElementById('metabox_input_id').value;
if (value == 'trigger_value') {
if (confirm('Se requiere recargar la página. ¿Recargar ahora?')) {
window.location.href = window.location.href+'&refreshed=1';
}
}
}, 1000);
});
}
}, 500);
});
...solo hay que cambiar metabox_input_id
y trigger_value
según sea necesario. :-)

Esto fue útil, el único ejemplo de referencia que pude encontrar para acceder a la jerarquía de módulos JavaScript de Gutenberg: https://github.com/front/gutenberg-js

Si alguien todavía está interesado, encontré una forma simple de ejecutar algo justo DESPUÉS de que el editor de bloques (Gutenberg) termine de publicar/actualizar una entrada:
const editor = window.wp.data.dispatch('core/editor')
const savePost = editor.savePost
editor.savePost = function (options) {
options = options || {}
return savePost(options)
.then(() => {
// Hacer algo después de que la entrada se guarde de forma asíncrona.
console.log('La entrada fue guardada.')
if (!options.isAutosave) {
// Esto no es un guardado automático.
}
})
}
Básicamente, el fragmento de código anterior sobrescribe la función nativa savePost()
.
Así que la sobrescribes con tu propia función, llamas a savePost()
nuevamente dentro de ella y aprovechas la promesa devuelta simplemente usando then
.

Para activar la acción (en este caso una solicitud Ajax) DESPUÉS de que el guardado del post esté COMPLETO, puedes usar un intervalo para esperar hasta que isSavingPost devuelva false nuevamente.
let intervalCheckPostIsSaved;
let ajaxRequest;
wp.data.subscribe(function () {
let editor = wp.data.select('core/editor');
if (editor.isSavingPost()
&& !editor.isAutosavingPost()
&& editor.didPostSaveRequestSucceed()) {
if (!intervalCheckPostIsSaved) {
intervalCheckPostIsSaved = setInterval(function () {
if (!wp.data.select('core/editor').isSavingPost()) {
if (ajaxRequest) {
ajaxRequest.abort();
}
ajaxRequest = $.ajax({
url: ajaxurl,
type: 'POST',
data: {},
success: function (data) {
ajaxRequest = null;
}
});
clearInterval(intervalCheckPostIsSaved);
intervalCheckPostIsSaved = null;
}
}, 800);
}
}
});

Necesitas recoger la función unsubscribe de la suscripción y llamarla para evitar llamadas múltiples.
const unsubscribe = wp.data.subscribe(function () {
let select = wp.data.select('core/editor');
var isSavingPost = select.isSavingPost();
var isAutosavingPost = select.isAutosavingPost();
var didPostSaveRequestSucceed = select.didPostSaveRequestSucceed();
if (isSavingPost && !isAutosavingPost && didPostSaveRequestSucceed) {
console.log("isSavingPost && !isAutosavingPost && didPostSaveRequestSucceed");
unsubscribe();
// tu AJAX AQUÍ();
}
});

Esto parece funcionar bien, excepto que aún se activa ANTES de que se complete el guardado del post. Mi código colocado debajo de "unsubscribe()" se ejecuta antes de que WP inicie el guardado del post mediante AJAX. ¿Existe alguna solución para ejecutar código después de que WP haya terminado al 100% con el guardado/actualización del post?

Puedo confirmar que se activa antes de que termine de guardarse el post. He añadido una nueva respuesta que creo que podría funcionar para activarse solo después de que el post haya terminado de guardarse: https://wordpress.stackexchange.com/a/390543/171971

Escribí una publicación en mi blog sobre esto - https://thewpvoyage.com/how-to-detect-when-a-post-is-done-saving-in-wordpress-gutenberg/
Puedes usar el siguiente hook:
import { useBlockProps } from '@wordpress/block-editor';
import { useRef, useState, useEffect } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
/**
* Retorna `true` si el post ha terminado de guardarse, `false` en caso contrario.
*
* @returns {Boolean}
*/
const useAfterSave = () => {
const [ isPostSaved, setIsPostSaved ] = useState( false );
const isPostSavingInProgress = useRef( false );
const { isSavingPost, isAutosavingPost } = useSelect( ( __select ) => {
return {
isSavingPost: __select( 'core/editor' ).isSavingPost(),
isAutosavingPost: __select( 'core/editor' ).isAutosavingPost(),
}
} );
useEffect( () => {
if ( ( isSavingPost || isAutosavingPost ) && ! isPostSavingInProgress.current ) {
setIsPostSaved( false );
isPostSavingInProgress.current = true;
}
if ( ! ( isSavingPost || isAutosavingPost ) && isPostSavingInProgress.current ) {
// Código a ejecutar después de que el post se haya guardado.
setIsPostSaved( true );
isPostSavingInProgress.current = false;
}
}, [ isSavingPost, isAutosavingPost ] );
return isPostSaved;
};
/**
* Función de edición de un bloque de ejemplo.
*
* @return {WPElement} Elemento a renderizar.
*/
export default function Edit() {
const isAfterSave = useAfterSave();
useEffect( () => {
if ( isAfterSave ) {
// Agrega aquí tu código que debe ejecutarse después de guardar el post.
console.log( '...guardado completado...' )
}
}, [ isAfterSave ] );
return (
<p { ...useBlockProps() }>
{ __( 'Lista de Tareas – ¡hola desde el editor!', 'todo-list' ) }
</p>
);
}
