Запуск JavaScript при сохранении в Gutenberg (Блочный редактор)
У меня есть метабокс, и мне нужно запустить JavaScript при сохранении поста (в данном случае - для обновления страницы).
В классическом редакторе это можно сделать через простой редирект, подключенный к хуку save_post
(с высоким приоритетом).
Но так как Gutenberg преобразует процесс сохранения для существующих метабоксов в отдельные AJAX-запросы, теперь это должно быть реализовано через JavaScript. Итак, как мне:
Отследить событие, когда все процессы сохранения завершены, и затем запустить JavaScript? Если да, то как называется это событие? Где можно найти документацию по этим событиям? ИЛИ
Запустить JavaScript внутри процесса сохранения метабокса через AJAX, который затем может проверить состояние процесса сохранения родительской страницы перед продолжением?

Не уверен, есть ли лучший способ, но я использую subscribe
вместо добавления обработчика событий к кнопке:
wp.data.subscribe(function () {
var isSavingPost = wp.data.select('core/editor').isSavingPost();
var isAutosavingPost = wp.data.select('core/editor').isAutosavingPost();
if (isSavingPost && !isAutosavingPost) {
// Здесь ваш AJAX код ......
}
})
Официальная документация данных редактора записей: https://wordpress.org/gutenberg/handbook/designers-developers/developers/data/data-core-editor/

это выглядит намного чище, просто интересно, откуда вообще берётся метод subscribe
? это часть функции wp.data
? В документации я не нашёл упоминания об этом.

Да, subscribe
— это метод модуля wp.data. Откройте консоль при редактировании поста в Gutenberg и выполните wp.data
. Это выведет список всех доступных методов модуля данных.

отличная находка! жаль, что документация Gutenberg так запутанно организована и содержит мало примеров. плюс ожидание, что разработчики будут знать и/или захотят изучать методы React, — это действительно перебор... конечно, это может экономить время, если вы уже знакомы с ними, но если нет — это настоящая трата времени: у меня ушли часы, чтобы просто разобраться, как получить доступ к чему-то полезному в модели wp.data
. я возвращаюсь к PHP (и классическому редактору).

Спасибо за публикацию! Как я могу перехватить и остановить обновление/публикацию записи на основе условия.

Похоже, этот метод также запускает код, когда пользователь нажимает кнопку "Переместить в корзину" (статус записи меняется на "trash", а значение isSavingPost равно "true" независимо от этого). Также в моем случае один клик по "Обновить" вызвал срабатывание кода подписки 3 раза. В итоге я стал отслеживать клики по .editor-post-publish-button, .editor-post-save-draft и .editor-post-preview.

Этот сниппет отличный, но он будет срабатывать несколько раз, в следующей ветке объясняется, как это предотвратить: https://github.com/WordPress/gutenberg/issues/5975#issuecomment-483488988

Хорошо, решение получилось гораздо более хакерским, чем я хотел, но оно работает...
Вот немного упрощенный и абстрагированный способ из моего кода, на случай, если кому-то понадобится сделать то же самое (я уверен, что в ближайшем будущем так поступят и другие плагины).
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('Сохранение: '+postsaving+' - Автосохранение: '+autosaving+' - Успешно: '+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('Требуется перезагрузка страницы. Обновить страницу сейчас?')) {
window.location.href = window.location.href+'&refreshed=1';
}
}
}, 1000);
});
}
}, 500);
});
...просто нужно заменить metabox_input_id
и trigger_value
на нужные значения. :-)

Это было полезно, единственный пример, который я смог найти по доступу к иерархии модулей Gutenberg в JavaScript: https://github.com/front/gutenberg-js

Если кому-то еще интересно, я придумал простой способ выполнить какой-то код сразу ПОСЛЕ того, как редактор блоков (Gutenberg) завершит публикацию/обновление записи:
const editor = window.wp.data.dispatch('core/editor')
const savePost = editor.savePost
editor.savePost = function (options) {
options = options || {}
return savePost(options)
.then(() => {
// Выполнить что-то после фактического асинхронного сохранения записи.
console.log('Запись была сохранена.')
if (!options.isAutosave) {
// Это не автосохранение.
}
})
}
По сути, приведенный выше сниппет переопределяет нативную функцию savePost()
.
Таким образом, вы переопределяете её своей собственной функцией, вызываете savePost()
внутри неё и используете преимущество возвращаемого промиса через then
.

Чтобы выполнить действие (в данном случае Ajax-запрос) ПОСЛЕ того, как сохранение записи будет ЗАВЕРШЕНО, можно использовать интервал для ожидания, пока isSavingPost снова вернет false.
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);
}
}
});

Вам необходимо собрать функцию отписки от подписки и вызвать её, чтобы избежать многократных вызовов.
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();
// ваш AJAX ЗДЕСЬ();
}
});

Это работает нормально, за исключением того, что срабатывает ДО полного сохранения записи. Мой код, размещенный под "unsubscribe()", запускается до того, как WordPress инициирует сохранение записи через AJAX. Есть ли решение для выполнения кода после того, как WordPress на 100% завершит сохранение/обновление записи?

Я подтверждаю, что это срабатывает до завершения сохранения записи. Я добавил новый ответ, который, как мне кажется, может сработать для запуска только после полного сохранения записи: https://wordpress.stackexchange.com/a/390543/171971

Я написал статью в блоге на эту тему — https://thewpvoyage.com/how-to-detect-when-a-post-is-done-saving-in-wordpress-gutenberg/
Вы можете использовать следующий хук:
import { useBlockProps } from '@wordpress/block-editor';
import { useRef, useState, useEffect } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
/**
* Возвращает `true`, если запись завершила сохранение, иначе `false`.
*
* @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 ) {
// Код, выполняемый после завершения сохранения записи.
setIsPostSaved( true );
isPostSavingInProgress.current = false;
}
}, [ isSavingPost, isAutosavingPost ] );
return isPostSaved;
};
/**
* Функция редактирования примера блока.
*
* @return {WPElement} Элемент для рендеринга.
*/
export default function Edit() {
const isAfterSave = useAfterSave();
useEffect( () => {
if ( isAfterSave ) {
// Добавьте здесь ваш код, который должен выполниться после сохранения записи.
console.log( '...сохранение завершено...' )
}
}, [ isAfterSave ] );
return (
<p { ...useBlockProps() }>
{ __( 'Список дел — привет из редактора!', 'todo-list' ) }
</p>
);
}
