From c32dfae1753cba18fef6e5ffb5dcc008acdef485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Souk=C3=A9ina=20Bojabza?= Date: Mon, 22 Jan 2024 16:31:18 +0100 Subject: [PATCH] [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 --- .../wysiwyg_adapter/wysiwyg_adapter.js | 87 +++++++++++++++++-- .../static/src/js/content/auto_hide_menu.js | 2 + addons/website/static/src/js/content/menu.js | 4 + 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js b/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js index 20a86f16a35..8c7c0ee12ea 100644 --- a/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js +++ b/addons/website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js @@ -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)); } /** diff --git a/addons/website/static/src/js/content/auto_hide_menu.js b/addons/website/static/src/js/content/auto_hide_menu.js index cfffa333ca3..9419e80e58f 100644 --- a/addons/website/static/src/js/content/auto_hide_menu.js +++ b/addons/website/static/src/js/content/auto_hide_menu.js @@ -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(); diff --git a/addons/website/static/src/js/content/menu.js b/addons/website/static/src/js/content/menu.js index 3fc09d685a8..d6959ca9766 100644 --- a/addons/website/static/src/js/content/menu.js +++ b/addons/website/static/src/js/content/menu.js @@ -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