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 b56b16bb044..8d6a3857218 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -693,6 +693,8 @@ var SnippetEditor = Widget.extend({ this.styles = {}; this.selectorSiblings = []; this.selectorChildren = []; + this.selectorLockWithin = new Set(); + const selectorExcludeAncestor = new Set(); var $element = this.$target.parent(); while ($element.length) { @@ -740,6 +742,12 @@ var SnippetEditor = Widget.extend({ if (val['drop-in']) { this.selectorChildren.push(val['drop-in']); } + if (val['drop-lock-within']) { + this.selectorLockWithin.add(val['drop-lock-within']); + } + if (val['drop-exclude-ancestor']) { + selectorExcludeAncestor.add(val['drop-exclude-ancestor']); + } var optionName = val.option; var option = new (options.registry[optionName] || options.Class)( @@ -774,6 +782,13 @@ var SnippetEditor = Widget.extend({ return option.appendTo(document.createDocumentFragment()); }); + if (selectorExcludeAncestor.size) { + // Prevents dropping an element into another one. + // (E.g. ToC inside another ToC) + const excludedAncestorSelector = [...selectorExcludeAncestor].join(", "); + this.excludeAncestors = (i, el) => !el.closest(excludedAncestorSelector); + } + this.isTargetMovable = (this.selectorSiblings.length > 0 || this.selectorChildren.length > 0); this.$el.find('[data-bs-toggle="dropdown"]').dropdown(); @@ -1006,40 +1021,34 @@ var SnippetEditor = Widget.extend({ width: self.$target.width(), height: self.$target.height() }; - const closestFormEl = this.$target[0].closest('form'); - self.$target.after('
'); + const dropCloneEl = document.createElement("div"); + dropCloneEl.classList.add("oe_drop_clone"); + dropCloneEl.style.setProperty("display", "none"); + self.$target[0].after(dropCloneEl); self.$target.detach(); self.$el.addClass('d-none'); var $selectorSiblings; for (var i = 0; i < self.selectorSiblings.length; i++) { - if (!$selectorSiblings) { - $selectorSiblings = self.selectorSiblings[i].all(); - } else { - $selectorSiblings = $selectorSiblings.add(self.selectorSiblings[i].all()); + let $siblings = self.selectorSiblings[i].all(); + if (this.excludeAncestors) { + $siblings = $siblings.filter(this.excludeAncestors); } + $selectorSiblings = $selectorSiblings ? $selectorSiblings.add($siblings) : $siblings; } var $selectorChildren; for (i = 0; i < self.selectorChildren.length; i++) { - if (!$selectorChildren) { - $selectorChildren = self.selectorChildren[i].all(); - } else { - $selectorChildren = $selectorChildren.add(self.selectorChildren[i].all()); + let $children = self.selectorChildren[i].all(); + if (this.excludeAncestors) { + $children = $children.filter(this.excludeAncestors); } + $selectorChildren = $selectorChildren ? $selectorChildren.add($children) : $children; } - // TODO In master, do not reference other module class + find a better - // system to define such cases + avoid duplicated code (drag & drop from - // editor panel + drag & drop from move button of existing block). - // Prevent dropping ToC inside another ToC. grep: NO_DOUBLE_TOC - if (this.$target[0].classList.contains('s_table_of_content')) { - $selectorChildren = $selectorChildren.filter((i, el) => !el.closest('.s_table_of_content')); - } - // Disallow dropping form fields outside of their form. - // TODO this can probably be implemented by reviewing data-drop-near - // definitions in master but we should find a better to define those and - // such cases. - if (this.$target[0].classList.contains('s_website_form_field')) { - const filterFunc = (i, el) => el.closest('form') === closestFormEl; + // Disallow dropping an element outside a given direct or + // indirect parent. (E.g. form field must remain within its own form) + for (const lockedParentSelector of this.selectorLockWithin) { + const closestLockedParentEl = dropCloneEl.closest(lockedParentSelector); + const filterFunc = (i, el) => el.closest(lockedParentSelector) === closestLockedParentEl; if ($selectorSiblings) { $selectorSiblings = $selectorSiblings.filter(filterFunc); } @@ -2868,6 +2877,8 @@ var SnippetsMenu = Widget.extend({ '$el': $style, 'drop-near': $style.data('drop-near') && self._computeSelectorFunctions($style.data('drop-near'), '', false, noCheck, true, excludeParent), 'drop-in': $style.data('drop-in') && self._computeSelectorFunctions($style.data('drop-in'), '', false, noCheck), + 'drop-exclude-ancestor': this.dataset.dropExcludeAncestor, + 'drop-lock-within': this.dataset.dropLockWithin, 'data': Object.assign({string: $style.attr('string')}, $style.data()), }; self.templateOptions.push(option); @@ -3241,6 +3252,7 @@ var SnippetsMenu = Widget.extend({ var $baseBody = $snippet.find('.oe_snippet_body'); var $selectorSiblings = $(); var $selectorChildren = $(); + const selectorExcludeAncestor = []; var temp = self.templateOptions; for (var k in temp) { if ($baseBody.is(temp[k].base_selector) && !$baseBody.is(temp[k].base_exclude)) { @@ -3250,17 +3262,17 @@ var SnippetsMenu = Widget.extend({ if (temp[k]['drop-in']) { $selectorChildren = $selectorChildren.add(temp[k]['drop-in'].all()); } + if (temp[k]['drop-exclude-ancestor']) { + selectorExcludeAncestor.push(temp[k]['drop-exclude-ancestor']); + } } } - // TODO In master, do not reference other module class + - // find a better system to define such cases + avoid - // duplicated code (drag & drop from editor panel + drag & - // drop from move button of existing block). - // Prevent dropping ToC inside another ToC. - // grep: NO_DOUBLE_TOC - if ($baseBody[0].classList.contains('s_table_of_content')) { - $selectorChildren = $selectorChildren.filter((i, el) => !el.closest('.s_table_of_content')); + // Prevent dropping an element into another one. + // (E.g. ToC inside another ToC) + for (const excludedAncestorSelector of selectorExcludeAncestor) { + $selectorSiblings = $selectorSiblings.filter((i, el) => !el.closest(excludedAncestorSelector)); + $selectorChildren = $selectorChildren.filter((i, el) => !el.closest(excludedAncestorSelector)); } $toInsert = $baseBody.clone(); diff --git a/addons/website/views/snippets/s_table_of_content.xml b/addons/website/views/snippets/s_table_of_content.xml index c233d978d0b..8a3b207329c 100644 --- a/addons/website/views/snippets/s_table_of_content.xml +++ b/addons/website/views/snippets/s_table_of_content.xml @@ -57,7 +57,7 @@