diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index 9162e90617b5303e7b6fe619131aa7d2e0423fe7..82b5e1cb7c3abbcd80a0a6070454afcb23d4f8ef 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -2475,16 +2475,70 @@ var SnippetsMenu = Widget.extend({ $invisibleDOMPanelEl.toggleClass('d-none', !$invisibleSnippets.length); - const proms = _.map($invisibleSnippets, async el => { - const editor = await this._createSnippetEditor($(el)); - const $invisEntry = $('<div/>', { - class: 'o_we_invisible_entry d-flex align-items-center justify-content-between', - text: editor.getName(), - }).append($('<i/>', {class: `fa ${editor.isTargetVisible() ? 'fa-eye' : 'fa-eye-slash'} ms-2`})); - $invisibleDOMPanelEl.append($invisEntry); - this.invisibleDOMMap.set($invisEntry[0], el); + // descendantPerSnippet: a map with its keys set to invisible + // snippets that have invisible descendants. The value corresponding + // to an invisible snippet element is a list filled with all its + // descendant invisible snippets except those that have a closer + // invisible snippet ancestor. + const descendantPerSnippet = new Map(); + // Filter the "$invisibleSnippets" to only keep the root snippets + // and create the map ("descendantPerSnippet") of the snippets and + // their descendant snippets. + const rootInvisibleSnippetEls = [...$invisibleSnippets].filter(invisibleSnippetEl => { + const ancestorInvisibleEl = invisibleSnippetEl + .parentElement.closest(".o_snippet_invisible"); + if (!ancestorInvisibleEl) { + return true; + } + const descendantSnippets = descendantPerSnippet.get(ancestorInvisibleEl) || []; + descendantPerSnippet.set(ancestorInvisibleEl, + [...descendantSnippets, invisibleSnippetEl]); + return false; }); - return Promise.all(proms); + // Insert an invisible snippet in its "parentEl" element. + const createInvisibleElement = async (invisibleSnippetEl, isRootParent, isDescendant, + parentEl) => { + const editor = await this._createSnippetEditor($(invisibleSnippetEl)); + const invisibleEntryEl = document.createElement("div"); + invisibleEntryEl.className = `${isRootParent ? "o_we_invisible_root_parent" : ""}`; + invisibleEntryEl.classList.add("o_we_invisible_entry", "d-flex", + "align-items-center", "justify-content-between"); + invisibleEntryEl.classList.toggle("o_we_sublevel_1", isDescendant); + const titleEl = document.createElement("we-title"); + titleEl.textContent = editor.getName(); + invisibleEntryEl.appendChild(titleEl); + const iconEl = document.createElement("i"); + const eyeIconClass = editor.isTargetVisible() ? "fa-eye" : "fa-eye-slash"; + iconEl.classList.add("fa", "ms-2", eyeIconClass); + invisibleEntryEl.appendChild(iconEl); + parentEl.appendChild(invisibleEntryEl); + this.invisibleDOMMap.set(invisibleEntryEl, invisibleSnippetEl); + }; + // Insert all the invisible snippets contained in "snippetEls" as + // well as their descendants in the "parentEl" element. If + // "snippetEls" is set to "rootInvisibleSnippetEls" and "parentEl" + // is set to "$invisibleDOMPanelEl[0]", then fills the right + // invisible panel like this: + // rootInvisibleSnippet + // â”” descendantInvisibleSnippet + // â”” descendantOfDescendantInvisibleSnippet + // â”” etc... + const createInvisibleElements = async (snippetEls, isDescendant, parentEl) => { + for (const snippetEl of snippetEls) { + const descendantSnippetEls = descendantPerSnippet.get(snippetEl); + // An element is considered as "RootParent" if it has one or + // more invisible descendants but is not a descendant. + await createInvisibleElement(snippetEl, + !isDescendant && !!descendantSnippetEls, isDescendant, parentEl); + if (descendantSnippetEls) { + // Insert all the descendant snippets in a list. + const listEntryEl = document.createElement("ul"); + await createInvisibleElements(descendantSnippetEls, true, listEntryEl); + parentEl.appendChild(listEntryEl); + } + } + }; + return createInvisibleElements(rootInvisibleSnippetEls, false, $invisibleDOMPanelEl[0]); }, false); }, /** diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss index d8073d51e7e826da2484d6d63d2e428eb52b0649..52d39fa3818ba6b21aa1c523c91439bb06a4cf48 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss @@ -268,6 +268,24 @@ } } + %o_we_sublevel > we-title::before { + content: "â””"; // TODO The size and look of this depends on the + // browser default font, we should use a SVG instead. + display: inline-block; + margin-right: 0.4em; + } + @for $level from 1 through 3 { + .o_we_sublevel_#{$level} { + @extend %o_we_sublevel; + + @if $level > 1 { + > we-title { + padding-left: ($level - 1) * 0.6em; + } + } + } + } + #snippets_menu { flex: 0 0 auto; display: flex; @@ -1658,24 +1676,6 @@ } } - %o_we_sublevel > we-title::before { - content: "â””"; // TODO The size and look of this depends on the - // browser default font, we should use a SVG instead. - display: inline-block; - margin-right: 0.4em; - } - @for $level from 1 through 3 { - .o_we_sublevel_#{$level} { - @extend %o_we_sublevel; - - @if $level > 1 { - > we-title { - padding-left: ($level - 1) * 0.6em; - } - } - } - } - .o_we_image_weight { margin-left: $o-we-sidebar-content-field-label-spacing * 2; } @@ -1710,6 +1710,21 @@ background-color: $o-we-sidebar-bg; } } + + div.o_we_invisible_root_parent { + padding-bottom: 3px; + } + + ul { + list-style: none; + padding-inline-start: 15px; + margin-bottom: $o-we-sidebar-content-field-spacing - 3px; + + div.o_we_invisible_entry { + padding-top: 3px; + padding-bottom: 3px; + } + } } &.o_we_backdrop {