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 0fb5e4d1048..05b2822d1ef 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 @@ -929,6 +929,13 @@ export class OdooEditor extends EventTarget { continue; } } + if (record.target && [Node.TEXT_NODE, Node.ELEMENT_NODE].includes(record.target.nodeType)) { + const closestProtected = closestElement(record.target, '[data-oe-protected="true"]'); + if (closestProtected && closestProtected.nodeType === Node.ELEMENT_NODE && + record.target !== closestProtected) { + continue; + } + } filteredRecords.push(record); } return this.options.filterMutationRecords(filteredRecords); @@ -2164,12 +2171,16 @@ export class OdooEditor extends EventTarget { * @returns {boolean} */ _handleSelectionInTable(ev=undefined) { + const selection = this.document.getSelection(); + const anchorNode = selection.anchorNode; + if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) { + return false; + } this.deselectTable(); const traversedNodes = getTraversedNodes(this.editable); if (this._isResizingTable || !traversedNodes.some(node => !!closestElement(node, 'td'))) { return false; } - const selection = this.document.getSelection(); let range; if (selection.rangeCount > 1) { // Firefox selection in table works with multiple ranges. @@ -3362,7 +3373,7 @@ export class OdooEditor extends EventTarget { _onSelectionChange() { const selection = this.document.getSelection(); const anchorNode = selection.anchorNode; - if (anchorNode && closestElement(anchorNode, '.oe-blackbox')) { + if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) { return; } @@ -3547,6 +3558,13 @@ export class OdooEditor extends EventTarget { emptyElement.removeAttribute('data-oe-zws-empty-inline'); } } + + // Clean all protected nodes because they are not sanitized + const protectedNodes = element.querySelectorAll('[data-oe-protected="true"]'); + for (const node of protectedNodes) { + node.replaceChildren(); + } + sanitize(element); // Remove contenteditable=false on elements @@ -3578,7 +3596,7 @@ export class OdooEditor extends EventTarget { _handleCommandHint() { const selection = this.document.getSelection(); const anchorNode = selection.anchorNode; - if (anchorNode && closestElement(anchorNode, '.oe-blackbox')) { + if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) { return; } @@ -3652,7 +3670,7 @@ export class OdooEditor extends EventTarget { _fixSelectionOnContenteditableFalse() { const selection = this.document.getSelection(); const anchorNode = selection.anchorNode; - if (anchorNode && closestElement(anchorNode, '.oe-blackbox')) { + if (anchorNode && closestElement(anchorNode, '[data-oe-protected="true"]')) { return; } // When the browser set the selection inside a node that is diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/sanitize.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/sanitize.js index 77307dec78d..c9c468f9807 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/sanitize.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/sanitize.js @@ -109,6 +109,10 @@ class Sanitize { _parse(node) { while (node) { + const closestProtected = closestElement(node, '[data-oe-protected="true"]'); + if (closestProtected && node !== closestProtected) { + return; + } // Merge identical elements together. while ( areSimilarElements(node, node.previousSibling) && diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js index 30de9bc5135..deee3fd67c5 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js @@ -1,5 +1,8 @@ import { OdooEditor } from '../../src/OdooEditor.js'; -import { getTraversedNodes } from '../../src/utils/utils.js'; +import { + getTraversedNodes, + setSelection, +} from '../../src/utils/utils.js'; import { BasicEditor, deleteBackward, @@ -5688,4 +5691,57 @@ X[] }); }); }); + + describe('oe-protected', () => { + it('should ignore protected elements children mutations', async () => { + await testEditor(BasicEditor, { + contentBefore: unformat(` +
a[]
a
ab[]
ab
+
+
\u200B
++
a[]
a
a[]