mercury_editor-2.0.x-dev/source/js/autosave.js
source/js/autosave.js
((Drupal, drupalSettings, $) => {
let ajaxing = false;
function setAjaxing(value) {
document.body.classList.toggle('me-ajaxing', value);
ajaxing = value;
}
$(document).on('ajaxStart', () => setAjaxing(true));
$(document).on('ajaxComplete', () => setAjaxing(false));
let interacting = false;
window.addEventListener('message', (event) => {
if (event.data.type === 'layoutParagraphsEvent') {
if (event.data.eventName === 'lpb-component:drag') {
interacting = true;
}
if (event.data.eventName === 'lpb-component:drop') {
interacting = false;
}
}
});
/**
* Serializes a form.
* @param {HTMLElement} form The form element.
* @return {string} The serialized form data.
*/
function serializeForm(form) {
// JavaScript editors like CKEditor and CodeMirror save changes to text areas
// when behaviors are detached. So we need to detach behaviors first, then
// serialize the form.
Drupal.detachBehaviors(form, drupalSettings, 'serialize');
// Drupal.attachBehaviors(form, drupalSettings);
let formData = Object.fromEntries(new FormData(form).entries());
formData = Object.fromEntries(
Object.entries(formData).filter(
([, value]) => value !== '' && value !== null && value !== undefined,
),
);
formData = Object.keys(formData)
.sort()
.reduce((obj, key) => {
obj[key] = formData[key];
return obj;
}, {});
// Remove non-essential keys to avoid false positives.
['form_build_id', 'form_id', 'form_token', 'tabs'].forEach((key) => {
delete formData[key];
});
return JSON.stringify(formData);
}
/**
* Debounced function to submit form when it changes.
* @param {Element} form The form.
* @return {boolean} True if the form has changed.
*/
function checkFormForChanges(form) {
if (
interacting ||
ajaxing ||
!drupalSettings.ajaxPreviewPageState ||
!form.serializedData ||
form.hasAttribute('data-me-saving') ||
form.querySelector('input[type="file"][disabled]')
) {
return false;
}
const serializedData = serializeForm(form);
// No changes detected.
if (form.serializedData === serializedData) {
return false;
}
form.serializedData = serializedData;
return true;
}
// Only one form is autosaved at a time, so we need to keep track of it.
let autosaveForm = null;
/**
* @todo Pause the interval after period of inactivity.
* @todo Evaluate for performance.
*/
let autosaveInterval = null;
/**
* Start polling for changes in the autosave form.
* @todo Use a more efficient way to check for changes.
*/
function startPolling() {
if (autosaveInterval) {
return;
}
autosaveInterval = setInterval(() => {
if (!ajaxing && autosaveForm) {
if (checkFormForChanges(autosaveForm) === true) {
const saveButton = autosaveForm.querySelector('.me-autosave-btn');
if (saveButton) {
saveButton.dispatchEvent(new Event('mousedown'));
}
}
}
}, 500);
}
/**
* Stop polling for changes in the autosave form.
*/
function stopPolling() {
if (autosaveInterval) {
clearInterval(autosaveInterval);
autosaveInterval = null;
}
}
/**
* Set the autosave form to the last autosave button's parent form in the DOM.
*/
function setAutosaveForm() {
const autosaveBtns = document.querySelectorAll('form .me-autosave-btn');
if (autosaveBtns.length > 0) {
autosaveForm = autosaveBtns[autosaveBtns.length - 1].closest('form');
if (!autosaveForm.serializedData) {
autosaveForm.serializedData = serializeForm(autosaveForm);
}
autosaveForm.classList.add('me-autosave');
startPolling();
} else {
autosaveForm = null;
stopPolling();
}
}
/**
* Debounced function to check validity of inputs on keyup.
* @todo Re-evaluate necessity, approach, and UX impact.
*/
// let checkValidityTimeout = null;
// document.addEventListener('keyup', (event) => {
// if (!event.target.validity) return;
// clearTimeout(checkValidityTimeout);
// checkValidityTimeout = setTimeout(
// () => {
// if (!event.target.validity.valid) {
// event.target.reportValidity();
// }
// },
// event.target.type === 'date' || event.target.type === 'time' ? 1000 : 500,
// );
// });
/**
* Re-evaluate the autosave form when a dialog is closed.
*/
document.addEventListener('dialog:afterclose', () => {
setTimeout(setAutosaveForm, 100);
});
/**
* Attaches the autosave behavior to forms with the .me-autosave-form class.
*/
Drupal.behaviors.mercuryEditorAutosave = {
attach() {
if (document.querySelectorAll('.me-autosave-form')) {
setAutosaveForm();
}
},
};
})(Drupal, drupalSettings, jQuery, once, Drupal.debounce);
