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 @@
+