[FIX] website: fix the history of some changes in the header

The changes happening in the header often break the history, because a
lot of them add steps in it while they should not be observed at all.
This results in losing the redo history, having to click multiple times
to undo one specific change or even seeing some intermediate states that
should not be appearing, every time we interact with the header (e.g. by
opening/closing a dropdown or a burger menu, resizing or scrolling the
window).

This commit fixes these history issues by not observing some problematic
changes. Note that this commit only fixes the most "annoying" ones (i.e.
the ones that break things) that prevented the other commits of this PR
to work correctly. A second pass will be needed to remove other useless
mutations (i.e. that do not break things but should not be observed
either). This commit will also need to be backported, as these issues
are also present in earlier versions.

Here are the changes that are not observed anymore:
- When we open/close a "burger" menu (i.e. the "Hamburger menu" or in
mobile view). It was adding a step in the history, so if we opened and
closed it in the middle of a redo, we would lose the remaining "redo".

- When the extra menu is added/adapted. When resizing the window, which
also means toggling the mobile view, if there is not enough space for
all the desktop menus to be visible, they are moved in an extra ("+")
menu (they are moved out when there is enough space). This resulted in
the intermediate state where the menus are not in the extra menu yet to
be visible when undoing at some point, which did not look good.

- When hiding a dropdown by scrolling the page and hiding a hoverable
dropdown by clicking somewhere on the page. When they are hidden this
way, undoing the change that will be done after that would reopen the
dropdown, which could be annoying if the dropdown was a mega menu for
example.

- When showing a hoverable dropdown. This added a step in the history,
and since opening a "clickable" dropdown does not do it, this also
should not be the case for a hoverable one.

task-3609531
opw-3730165

Part-of: odoo/odoo#146492
This commit is contained in:
Soukéina Bojabza
2024-02-26 21:22:46 +00:00
committed by qsm-odoo
parent 2d33dc64e9
commit c32dfae175
3 changed files with 88 additions and 5 deletions
@@ -46,6 +46,25 @@ function toggleDropdown($toggles, show) {
})).then(() => $toggles);
}
/**
* Checks if the classes that changed during the mutation are all to be ignored.
* (The mutation can be discarded if it is the case, when filtering the mutation
* records).
*
* @param {Object} record the current mutation
* @param {Array} excludedClasses the classes to ignore
* @returns {Boolean}
*/
function checkForExcludedClasses(record, excludedClasses) {
const classBefore = (record.oldValue && record.oldValue.split(" ")) || [];
const classAfter = [...record.target.classList];
const changedClasses = [
...classBefore.filter(c => c && !classAfter.includes(c)),
...classAfter.filter(c => c && !classBefore.includes(c)),
];
return changedClasses.every(c => excludedClasses.includes(c));
}
/**
* This component adapts the Wysiwyg widget from @web_editor/wysiwyg.js.
* It encapsulate it so that this legacy widget can work in an OWL framework.
@@ -172,7 +191,6 @@ export class WysiwygAdapterComponent extends Wysiwyg {
// Since bootstrap 5.1.3, removing bsToggle is not sufficient anymore.
$dropdownMenuToggles.siblings(".dropdown-menu").addClass("o_wysiwyg_submenu");
$dropdownMenuToggles.on('click.wysiwyg_megamenu', ev => {
this.odooEditor.observerUnactive();
var $toggle = $(ev.currentTarget);
// Each time we toggle a dropdown, we will destroy the dropdown
@@ -189,10 +207,7 @@ export class WysiwygAdapterComponent extends Wysiwyg {
if (!this.options.enableTranslation) {
this._toggleMegaMenu($toggle[0]);
}
})
// FIXME this is not right, the observer should not be inactive
// for async periods of time.
.then(() => this.odooEditor.observerActive());
});
});
// Ensure :blank oe_structure elements are in fact empty as ':blank'
@@ -204,6 +219,66 @@ export class WysiwygAdapterComponent extends Wysiwyg {
}
await super.startEdition();
// Overriding the `filterMutationRecords` function so it can be used to
// filter website-specific mutations.
const webEditorFilterMutationRecords = this.odooEditor.options.filterMutationRecords;
Object.assign(this.odooEditor.options, {
/**
* @override
*/
filterMutationRecords(records) {
const filteredRecords = webEditorFilterMutationRecords(records);
// Dropdown attributes to ignore.
const dropdownClasses = ["show"];
const dropdownToggleAttributes = ["aria-expanded"];
const dropdownMenuAttributes = ["data-popper-placement", "style", "data-bs-popper"];
// Offcanvas attributes to ignore.
const offcanvasClasses = ["show"];
const offcanvasAttributes = ["aria-modal", "aria-hidden", "role", "style"];
return filteredRecords.filter(record => {
if (record.type === "attributes") {
if (record.target.closest("header#top")) {
// Do not record when showing/hiding a dropdown.
if (record.target.matches(".dropdown-toggle, .dropdown-menu")
&& record.attributeName === "class") {
if (checkForExcludedClasses(record, dropdownClasses)) {
return false;
}
} else if (record.target.matches(".dropdown-menu")
&& dropdownMenuAttributes.includes(record.attributeName)) {
return false;
} else if (record.target.matches(".dropdown-toggle")
&& dropdownToggleAttributes.includes(record.attributeName)) {
return false;
}
// Do not record when showing/hiding an offcanvas.
if (record.target.matches(".offcanvas, .offcanvas-backdrop")
&& record.attributeName === "class") {
if (checkForExcludedClasses(record, offcanvasClasses)) {
return false;
}
} else if (record.target.matches(".offcanvas")
&& offcanvasAttributes.includes(record.attributeName)) {
return false;
}
}
} else if (record.type === "childList") {
const addedOrRemovedNode = record.addedNodes[0] || record.removedNodes[0];
// Do not record the addition/removal of the offcanvas
// backdrop.
if (addedOrRemovedNode.nodeType === Node.ELEMENT_NODE
&& addedOrRemovedNode.matches(".offcanvas-backdrop")) {
return false;
}
}
return true;
});
}
});
// Disable OdooEditor observer's while setting up classes
this.odooEditor.observerUnactive();
this._addEditorMessages();
@@ -979,7 +1054,9 @@ export class WysiwygAdapterComponent extends Wysiwyg {
if (!megaMenuEl || !megaMenuEl.classList.contains('show')) {
return this.snippetsMenu.activateSnippet(false);
}
this.odooEditor.observerUnactive("toggleMegaMenu");
megaMenuEl.classList.add('o_no_parent_editor');
this.odooEditor.observerActive("toggleMegaMenu");
return this.snippetsMenu.activateSnippet($(megaMenuEl));
}
/**
@@ -115,7 +115,9 @@ async function autoHideMenu(el, options) {
const wysiwyg = window.$ && $('#wrapwrap').data('wysiwyg');
const odooEditor = wysiwyg && wysiwyg.odooEditor;
if (odooEditor) {
odooEditor.observerUnactive("adapt");
odooEditor.withoutRollback(__adapt);
odooEditor.observerActive("adapt");
return;
}
__adapt();
@@ -704,13 +704,17 @@ publicWidget.registry.HeaderGeneral = publicWidget.Widget.extend({
* @private
*/
_onCollapseShow() {
this.options.wysiwyg?.odooEditor.observerUnactive("addCollapseClass");
this.el.classList.add('o_top_menu_collapse_shown');
this.options.wysiwyg?.odooEditor.observerActive("addCollapseClass");
},
/**
* @private
*/
_onCollapseHidden() {
this.options.wysiwyg?.odooEditor.observerUnactive("removeCollapseClass");
this.el.classList.remove('o_top_menu_collapse_shown');
this.options.wysiwyg?.odooEditor.observerActive("removeCollapseClass");
},
/**
* @private