[FIX] web_editor: fix isDirty check on html fields

The method `getValue` of the wysiwyg could have some cleaning on the
value that will always make it different from the original value.
The _isDirty method of the html field will now make the original value
be processed by the wysiwyg `getValue` to be able to compare it
with the current value.

The sanizite call in `OdooEditor.cleanForSave` had the wrong root. As
we are calling `cleanForSave` for an element that is not inside the
editable, the root cannot be `this.editable`.

task-3679122

closes odoo/odoo#148996

Signed-off-by: Antoine Guenet (age) <age@odoo.com>
This commit is contained in:
Nicolas Bayet
2024-01-22 19:01:00 +00:00
parent 255eb4c99c
commit 119fa9f974
3 changed files with 59 additions and 4 deletions
@@ -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 || '<p><br></p>', '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 || '<p><br></p>', '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 || '<p><br></p>', '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;
@@ -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"]')) {
@@ -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: "<p>a<span>b</span>c</p>",
});
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: `
<form>
<field name="txt" widget="html"/>
</form>`,
});
await wysiwygPromise;
assert.strictEqual(htmlField.wysiwyg.getValue(), '<p>abc</p>', 'the value should be sanitized by the wysiwyg');
assert.strictEqual(htmlField._isDirty(), false, 'should not be dirty as the content has not changed');
});
});