From fc089f32b0175b266c65aca7c94f78de5aa1d778 Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Thu, 22 Oct 2020 10:42:54 +0000 Subject: [PATCH] [FIX] web_editor: DOM is not cleaned before saving custom snippets Previously, when saving a custom snippet, the outerHTML of the snippet was naively read directly and saved as a new custom snippet. Unfortunately this completely bypasses all cleanup mechanisms that we have before saving, such as stopping the public widgets, or creating attachments for cropped or optimized images. This would result in weird bugs, where public widgets would appear twice when dropping a custom snippet containing one, as well as the creating a new attachment for any image that was optimized or cropped before saving the custom snippet every time it was dropped in the page again. Unfortunately there is no good mechanism for an option to ask the editor to clean the DOM without actually saving it, as a temporary measure, the snippet-save option will instead save the whole page (which will clean up the DOM) and save the custom snippet after the save, before refreshing the page and reopening the editor, not unlike some theme options. task-2341612 closes odoo/odoo#64166 X-original-commit: 38569777935a7939182f49ca978d98f4079e8f2f Signed-off-by: Quentin Smetz (qsm) --- .../static/tests/tours/custom_snippets.js | 4 ++ .../static/src/js/editor/snippets.editor.js | 6 +++ .../static/src/js/editor/snippets.options.js | 54 ++++++++++++++----- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/addons/test_website/static/tests/tours/custom_snippets.js b/addons/test_website/static/tests/tours/custom_snippets.js index 1b6b4a593ba..7fc066a1648 100644 --- a/addons/test_website/static/tests/tours/custom_snippets.js +++ b/addons/test_website/static/tests/tours/custom_snippets.js @@ -50,6 +50,10 @@ tour.register('test_custom_snippet', { content: "confirm save name", trigger: ".modal-dialog button span:contains('Save')", }, + { + content: "confirm reload", + trigger: ".modal-dialog button span:contains('Save and Reload')", + }, { content: "go to blocks", trigger: ".o_we_add_snippet_btn", 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 517f4e62d93..8b2f3b66794 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -2419,6 +2419,12 @@ var SnippetsMenu = Widget.extend({ }); }, /** + * UNUSED: used to be called when saving a custom snippet. We now save and + * reload the page when saving a custom snippet so that all the DOM cleanup + * mechanisms are run before saving. Kept for compatibility. + * + * TODO: remove in master / find a way to clean the DOM without save+reload + * * @private */ _onReloadSnippetTemplate: async function (ev) { diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index fe3f9118597..d36df85d1c8 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -4,6 +4,7 @@ odoo.define('web_editor.snippets.options', function (require) { var core = require('web.core'); const {ColorpickerWidget} = require('web.Colorpicker'); const Dialog = require('web.Dialog'); +const rpc = require('web.rpc'); const time = require('web.time'); var Widget = require('web.Widget'); var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget; @@ -4695,27 +4696,54 @@ registry.SnippetSave = SnippetOptionWidget.extend({ classes: 'btn-primary', close: true, click: async () => { - const snippetName = dialog.el.querySelector('.o_we_snippet_name_input').value; - const targetCopyEl = this.$target[0].cloneNode(true); - delete targetCopyEl.dataset.name; + const save = await new Promise(resolve => { + Dialog.confirm(this, _t("To save a snippet, we need to save all your previous modifications and reload the page."), { + buttons: [ + { + text: _t("Save and Reload"), + classes: 'btn-primary', + close: true, + click: () => resolve(true), + }, { + text: _t("Cancel"), + close: true, + click: () => resolve(false), + } + ] + }); + }); + if (!save) { + return; + } const snippetKey = this.$target[0].dataset.snippet; let thumbnailURL; this.trigger_up('snippet_thumbnail_url_request', { key: snippetKey, onSuccess: url => thumbnailURL = url, }); - await this._rpc({ - model: 'ir.ui.view', - method: 'save_snippet', - kwargs: { - 'name': snippetName, - 'arch': targetCopyEl.outerHTML, - 'template_key': this.options.snippets, - 'snippet_key': snippetKey, - 'thumbnail_url': thumbnailURL, + this.trigger_up('request_save', { + reloadEditor: true, + onSuccess: async () => { + const snippetName = dialog.el.querySelector('.o_we_snippet_name_input').value; + const targetCopyEl = this.$target[0].cloneNode(true); + delete targetCopyEl.dataset.name; + // By the time onSuccess is called after request_save, the + // current widget has been destroyed and is orphaned, so this._rpc + // will not work as it can't trigger_up. For this reason, we need + // to bypass the service provider and use the global RPC directly + await rpc.query({ + model: 'ir.ui.view', + method: 'save_snippet', + kwargs: { + 'name': snippetName, + 'arch': targetCopyEl.outerHTML, + 'template_key': this.options.snippets, + 'snippet_key': snippetKey, + 'thumbnail_url': thumbnailURL, + }, + }); }, }); - this.trigger_up('reload_snippet_template'); }, }, { text: _t("Discard"),