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 5a5150d3808..82d3f3e7ec2 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 @@ -240,6 +240,7 @@ export class OdooEditor extends EventTarget { direction: 'ltr', _t: string => string, allowCommandVideo: true, + renderingClasses: [], }, options, ); @@ -902,6 +903,24 @@ export class OdooEditor extends EventTarget { } attributeCache.set(record.target, attributeCache.get(record.target) || {}); + if (record.attributeName === 'class') { + const classBefore = (record.oldValue && record.oldValue.split(' ')) || []; + const classAfter = (record.target.className && record.target.className.split && record.target.className.split(' ')) || []; + const excludedClasses = []; + for (const klass of classBefore) { + if (!classAfter.includes(klass)) { + excludedClasses.push(klass); + } + } + for (const klass of classAfter) { + if (!classBefore.includes(klass)) { + excludedClasses.push(klass); + } + } + if (excludedClasses.every(c => this.options.renderingClasses.includes(c))) { + continue; + } + } if ( typeof attributeCache.get(record.target)[record.attributeName] === 'undefined' ) { @@ -1026,10 +1045,14 @@ export class OdooEditor extends EventTarget { } else if (record.type === 'attributes') { const node = this.idFind(record.id); if (node) { + let value = record.value; + if (typeof value === 'string' && record.attributeName === 'class') { + value = value.split(' ').filter(c => !this.options.renderingClasses.includes(c)).join(' '); + } if (this._collabClientId) { - this._safeSetAttribute(node, record.attributeName, record.value); + this._safeSetAttribute(node, record.attributeName, value); } else { - node.setAttribute(record.attributeName, record.value); + node.setAttribute(record.attributeName, value); } } } else if (record.type === 'remove') { @@ -1166,10 +1189,14 @@ export class OdooEditor extends EventTarget { const node = this.idFind(mutation.id); if (node) { if (mutation.oldValue) { + let value = mutation.oldValue; + if (typeof value === 'string' && mutation.attributeName === 'class') { + value = value.split(' ').filter(c => !this.options.renderingClasses.includes(c)).join(' '); + } if (this._collabClientId) { - this._safeSetAttribute(node, mutation.attributeName, mutation.oldValue); + this._safeSetAttribute(node, mutation.attributeName, value); } else { - node.setAttribute(mutation.attributeName, mutation.oldValue); + node.setAttribute(mutation.attributeName, value); } } else { node.removeAttribute(mutation.attributeName); 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 d133fe3eda7..fbb51015c46 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 @@ -3671,6 +3671,56 @@ X[] }); }); }); + describe('prevent renderingClasses to be set from history', () => { + it('should prevent renderingClasses to be added', async () => { + await testEditor(BasicEditor, { + contentBefore: `
a
`, + stepFunction: async editor => { + const p = editor.editable.querySelector('p'); + p.className = 'x'; + editor.observerFlush(); + editor.historyStep(); + window.chai.expect(editor._historySteps.length).to.eql(1); + }, + }, { + renderingClasses: ['x'] + }); + }); + it('should prevent renderingClasses to be added when adding 2 classes', async () => { + await testEditor(BasicEditor, { + contentBefore: `a
`, + stepFunction: async editor => { + const p = editor.editable.querySelector('p'); + p.className = 'x y'; + editor.observerFlush(); + editor.historyStep(); + editor.historyUndo(); + editor.historyRedo(); + }, + contentAfter: `a
`, + }, { + renderingClasses: ['x'] + }); + }); + it('should prevent renderingClasses to be added in historyApply', async () => { + await testEditor(BasicEditor, { + contentBefore: `a
`, + stepFunction: async editor => { + const p = editor.editable.querySelector('p'); + editor.historyApply([{ + attributeName:"class", + id: p.oid, + oldValue: null, + type:"attributes", + value: "x y", + }]); + }, + contentAfter: `a
`, + }, { + renderingClasses: ['x'] + }); + }); + }); }); describe('columnize', () => { diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index d1df8a32a94..bfce8c259bc 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -204,6 +204,7 @@ const Wysiwyg = Widget.extend({ plugins: options.editorPlugins, direction: options.direction || localization.direction || 'ltr', collaborationClientAvatarUrl: `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${this.getSession().uid}`, + renderingClasses: ['o_dirty', 'o_transform_removal', 'oe_edited_link'], }, editorCollaborationOptions)); this.odooEditor.addEventListener('contentChanged', function () { @@ -1021,6 +1022,7 @@ const Wysiwyg = Widget.extend({ subtree: true, attributes: true, characterData: true, + attributeOldValue: true, }; if (this.odooFieldObservers) { for (let observerData of this.odooFieldObservers) { @@ -1032,7 +1034,11 @@ const Wysiwyg = Widget.extend({ this.odooFieldObservers = []; $odooFields.each((i, field) => { - const observer = new MutationObserver(() => { + const observer = new MutationObserver((mutations) => { + mutations = this.odooEditor.filterMutationRecords(mutations); + if (!mutations.length) { + return; + } let $node = $(field); let $nodes = $odooFields.filter(function () { return this !== field;