diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index 74dfd68141a..8080389feb5 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -2159,10 +2159,36 @@ const Wysiwyg = Widget.extend({ $('.o_editable') .removeClass('o_editable o_is_inline_editable o_editable_date_field_linked o_editable_date_field_format_changed'); - const defs = _.map(_.groupBy($allBlocks.toArray(), - function($obj) { - return $obj.dataset['oe-model'] + $obj.dataset['oe-field'] + $obj.dataset['oe-id'] - }), (els) => { + const saveElementFuncName = this.options.enableTranslation + ? '_saveTranslationElement' + : '_saveElement'; + + // Group elements to save if possible. + const groupedElements = _.groupBy($allBlocks.toArray(), el => { + const model = el.dataset.oeModel; + const field = el.dataset.oeField; + + // There are elements which have no linked model as something + // special is to be done "to save them" (potential override to + // `_saveElement` which is expected to be called for each unique + // dirty element). In that case, do not group those elements. + if (!model) { + return _.uniqueId('special-element-to-save-'); + } + + // Do not group elements which are parts of views, unless we are + // in translate mode. + if (!this.options.enableTranslation + && (model === 'ir.ui.view' && field === 'arch')) { + return _.uniqueId('view-part-to-save-'); + } + + // Otherwise, group elements which are from the same field of the + // same record (`_saveElement` will only consider the first one and + // `_saveTranslationElement` can handle the set if it makes sense). + return `${model}::${el.dataset.oeId}::${field}`; + }); + const proms = Object.values(groupedElements).map(els => { const $els = $(els); $els.find('[class]').filter(function () { @@ -2173,11 +2199,7 @@ const Wysiwyg = Widget.extend({ // TODO: Add a queue with concurrency limit in webclient return this.saving_mutex.exec(() => { - let saveElement = '_saveElement'; - if (this.options.enableTranslation) { - saveElement = '_saveTranslationElement'; - } - return this[saveElement]($els, context || this.options.context) + return this[saveElementFuncName]($els, context || this.options.context) .then(function () { $els.removeClass('o_dirty'); }).guardedCatch(function (response) { @@ -2198,7 +2220,7 @@ const Wysiwyg = Widget.extend({ }); }); }); - return Promise.all(defs).then(function () { + return Promise.all(proms).then(function () { window.onbeforeunload = null; }).guardedCatch((failed) => { // If there were errors, re-enable edition diff --git a/addons/website/static/tests/tours/multi_edition.js b/addons/website/static/tests/tours/multi_edition.js new file mode 100644 index 00000000000..bfa5472e4a8 --- /dev/null +++ b/addons/website/static/tests/tours/multi_edition.js @@ -0,0 +1,36 @@ +/** @odoo-module **/ + +import wTourUtils from 'website.tour_utils'; + +wTourUtils.registerWebsitePreviewTour('website_multi_edition', { + test: true, + url: '/', + edition: true, +}, [ + { + content: 'Check the current page has not the elements that will be added', + trigger: 'iframe body:not(:has(.s_text_image)):not(:has(.s_hr))', + run: () => null, + }, + // Edit the main element of the page + wTourUtils.dragNDrop({ + id: 's_text_image', + name: 'Text - Image', + }), + // Edit another part in the page, like the footer + wTourUtils.dragNDrop({ + id: 's_hr', + name: 'Separator', + }), + ...wTourUtils.clickOnSave(), + { + content: 'Check that the main element of the page was properly saved', + trigger: 'iframe main .s_text_image', + run: () => null, + }, + { + content: 'Check that the footer was properly saved', + trigger: 'iframe footer .s_hr', + run: () => null, + }, +]); diff --git a/addons/website/tests/test_ui.py b/addons/website/tests/test_ui.py index 3e2d7c918e1..d30ed22e38a 100644 --- a/addons/website/tests/test_ui.py +++ b/addons/website/tests/test_ui.py @@ -314,3 +314,6 @@ class TestUi(odoo.tests.HttpCase): def test_22_website_gray_color_palette(self): self.start_tour('/web', 'website_gray_color_palette', login='admin') + + def test_23_website_multi_edition(self): + self.start_tour('/@?enable_editor=1', 'website_multi_edition', login='admin')