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 d608fa88546..4b416d96c0c 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -36,6 +36,10 @@ var SnippetEditor = Widget.extend({ 'snippet_option_update': '_onSnippetOptionUpdate', 'snippet_option_visibility_update': '_onSnippetOptionVisibilityUpdate', }, + layoutElementsSelector: [ + '.o_we_shape', + '.o_we_bg_filter', + ].join(','), /** * @constructor @@ -326,6 +330,17 @@ var SnippetEditor = Widget.extend({ } if ($parent.closest(':data("snippet-editor")').length) { + const isEmptyAndRemovable = ($el, editor) => { + editor = editor || $el.data('snippet-editor'); + const isEmpty = $el.text().trim() === '' + && $el.children().toArray().every(el => { + // Consider layout-only elements (like bg-shapes) as empty + return el.matches(this.layoutElementsSelector); + }); + return isEmpty && !$el.hasClass('oe_structure') + && (!editor || editor.isTargetParentEditable); + }; + var editor = $parent.data('snippet-editor'); while (!editor) { var $nextParent = $parent.parent(); @@ -349,17 +364,10 @@ var SnippetEditor = Widget.extend({ this.trigger_up('snippet_removed'); this.destroy(); $parent.trigger('content_changed'); - // TODO Page content changed, some elements may need to be adapted // according to it. While waiting for a better way to handle that this // window trigger will handle most cases. $(window).trigger('resize'); - - function isEmptyAndRemovable($el, editor) { - editor = editor || $el.data('snippet-editor'); - return $el.children().length === 0 && $el.text().trim() === '' - && !$el.hasClass('oe_structure') && (!editor || editor.isTargetParentEditable); - } }, /** * Displays/Hides the editor overlay. diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js index 40f999f836c..c5d64e11d54 100644 --- a/addons/website/static/src/js/editor/snippets.editor.js +++ b/addons/website/static/src/js/editor/snippets.editor.js @@ -187,4 +187,12 @@ weSnippetEditor.Class.include({ }); }, }); + +weSnippetEditor.Editor.include({ + layoutElementsSelector: [ + weSnippetEditor.Editor.prototype.layoutElementsSelector, + '.s_parallax_bg', + '.o_bg_video_container', + ].join(','), +}); }); diff --git a/addons/website/static/tests/tours/snippet_empty_parent_autoremove.js b/addons/website/static/tests/tours/snippet_empty_parent_autoremove.js new file mode 100644 index 00000000000..719d112f81a --- /dev/null +++ b/addons/website/static/tests/tours/snippet_empty_parent_autoremove.js @@ -0,0 +1,75 @@ +odoo.define("website.tour.snippet_empty_parent_autoremove", function (require) { +"use strict"; + +const tour = require('web_tour.tour'); +const wTourUtils = require('website.tour_utils'); + +function removeSelectedBlock() { + return { + content: "Remove selected block", + trigger: '#oe_snippets we-customizeblock-options:last-child .oe_snippet_remove', + }; +} + +tour.register('snippet_empty_parent_autoremove', { + test: true, + url: '/?enable_editor=1', +}, [ + // Base case: remove both columns from text - image + wTourUtils.dragNDrop({ + id: 's_text_image', + name: 'Text - Image', + }), + { + content: "Click on second column", + trigger: '#wrap .s_text_image .row > :nth-child(2)', + }, + removeSelectedBlock(), + { + content: "Click on first column", + trigger: '#wrap .s_text_image .row > :first-child', + }, + removeSelectedBlock(), + { + content: "Check that #wrap is empty", + trigger: '#wrap:empty', + }, + + // Banner: test that parallax, bg-filter and shape are not treated as content + wTourUtils.dragNDrop({ + id: 's_banner', + name: 'Banner', + }), + wTourUtils.clickOnSnippet({ + id: 's_banner', + name: 'Banner', + }), + { + content: "Check that parallax is present", + trigger: '#wrap .s_banner .s_parallax_bg', + run: () => null, + }, + wTourUtils.changeOption('ColoredLevelBackground', 'Shape'), + { + content: "Check that shape is present", + trigger: '#wrap .s_banner .o_we_shape', + run: () => null, + }, + wTourUtils.changeOption('ColoredLevelBackground', 'Filter'), + { + content: "Check that background-filter is present", + trigger: '#wrap .s_banner .o_we_bg_filter', + run: () => null, + }, + { + content: "Click on first column", + trigger: '#wrap .s_banner .row > :first-child', + }, + removeSelectedBlock(), + { + content: "Check that #wrap is empty", + trigger: '#wrap:empty', + run: () => null, + }, +]); +}); diff --git a/addons/website/tests/__init__.py b/addons/website/tests/__init__.py index a9fac1e745b..da782faf547 100644 --- a/addons/website/tests/__init__.py +++ b/addons/website/tests/__init__.py @@ -18,3 +18,4 @@ from . import test_website_favicon from . import test_website_reset_password from . import test_website_visitor from . import test_controllers +from . import test_snippets diff --git a/addons/website/tests/test_snippets.py b/addons/website/tests/test_snippets.py new file mode 100644 index 00000000000..83f906f57e4 --- /dev/null +++ b/addons/website/tests/test_snippets.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo +import odoo.tests + + +@odoo.tests.common.tagged('post_install', '-at_install', 'website_snippets') +class TestSnippets(odoo.tests.HttpCase): + + def test_01_empty_parents_autoremove(self): + self.start_tour("/?enable_editor=1", "snippet_empty_parent_autoremove", login='admin') diff --git a/addons/website/views/assets.xml b/addons/website/views/assets.xml index f40970f5fec..b0d66b6d909 100644 --- a/addons/website/views/assets.xml +++ b/addons/website/views/assets.xml @@ -29,6 +29,7 @@