diff --git a/addons/web_editor/static/src/js/backend/html_field.js b/addons/web_editor/static/src/js/backend/html_field.js index 563d7a69b03..10080d5c9ef 100644 --- a/addons/web_editor/static/src/js/backend/html_field.js +++ b/addons/web_editor/static/src/js/backend/html_field.js @@ -420,9 +420,29 @@ export class HtmlField extends Component { const strippedPropValue = stripHistoryIds(String(this.props.record.data[this.props.name])); const strippedEditingValue = stripHistoryIds(this.getEditingValue()); const domParser = new DOMParser(); - const parsedPropValue = domParser.parseFromString(strippedPropValue || '


', 'text/html').body; - const parsedEditingValue = domParser.parseFromString(strippedEditingValue, 'text/html').body; - return !this.props.readonly && parsedPropValue.innerHTML !== parsedEditingValue.innerHTML; + const codeViewEl = this._getCodeViewEl(); + let parsedPreviousValue; + // If the wysiwyg is active, we need to clean the content of the + // initialValue as the editingValue will be cleaned. + if (!codeViewEl && this.wysiwyg) { + const editable = domParser.parseFromString(strippedPropValue || '


', 'text/html').body; + // Temporarily append the editable to the DOM because the + // wysiwyg.getValue can indirectly call methods that needs to have + // access the node.ownerDocument.defaultView.getComputedStyle. + // By appending the editable to the dom, the node.ownerDocument will + // have a `defaultView`. + const div = document.createElement('div'); + div.style.display = 'none'; + div.append(editable); + document.body.append(div); + const editableValue = this.wysiwyg.getValue({ $layout: $(editable) }); + div.remove(); + parsedPreviousValue = domParser.parseFromString(editableValue, 'text/html').body; + } else { + parsedPreviousValue = domParser.parseFromString(strippedPropValue || '


', 'text/html').body; + } + const parsedNewValue = domParser.parseFromString(strippedEditingValue, 'text/html').body; + return !this.props.readonly && parsedPreviousValue.innerHTML !== parsedNewValue.innerHTML; } _getCodeViewEl() { return this.state.showCodeView && this.codeViewRef.el; diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js index d96b274477b..f79ec8456fe 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js @@ -4330,7 +4330,7 @@ export class OdooEditor extends EventTarget { node.replaceChildren(); } - sanitize(element, this.editable); + sanitize(element); // Remove contenteditable=false on elements for (const el of element.querySelectorAll('[contenteditable="false"]')) { diff --git a/addons/web_editor/static/tests/html_field_tests.js b/addons/web_editor/static/tests/html_field_tests.js index 3b53e8312c2..941d92d7659 100644 --- a/addons/web_editor/static/tests/html_field_tests.js +++ b/addons/web_editor/static/tests/html_field_tests.js @@ -941,4 +941,39 @@ QUnit.module("WebEditor.HtmlField", ({ beforeEach }) => { assert.strictEqual(p.innerText.replaceAll('\u200B', ''), 'New label', "The link's label should be updated"); }); + + QUnit.module("isDirty"); + + QUnit.test("isDirty should be false when the content is being transformed by the wysiwyg", async (assert) => { + assert.expect(2); + + serverData.models.partner.records.push({ + id: 1, + txt: "

abc

", + }); + let htmlField; + const wysiwygPromise = makeDeferred(); + patchWithCleanup(HtmlField.prototype, { + async startWysiwyg() { + await super.startWysiwyg(...arguments); + htmlField = this; + wysiwygPromise.resolve(); + } + }); + await makeView({ + type: "form", + resId: 1, + resModel: "partner", + serverData, + arch: ` +
+ + `, + }); + await wysiwygPromise; + + assert.strictEqual(htmlField.wysiwyg.getValue(), '

abc

', 'the value should be sanitized by the wysiwyg'); + assert.strictEqual(htmlField._isDirty(), false, 'should not be dirty as the content has not changed'); + + }); });