[FIX] web_editor: preserve style on empty element

Make sure not to remove inline styles when emptying an element.

task-3102841

X-original-commit: 8d0397c99798f05a41276d62a66f47c41f255ef8
Part-of: odoo/odoo#116558
This commit is contained in:
Antoine Guenet
2023-03-25 02:46:58 +01:00
parent 1beb99b73d
commit 3d89a2ff91
3 changed files with 47 additions and 2 deletions
@@ -1853,6 +1853,19 @@ export class OdooEditor extends EventTarget {
range.setEnd(commonAncestorContainer, nodeSize(commonAncestorContainer));
}
}
let insertedZws;
if (sel && !sel.isCollapsed && !range.startOffset && !range.startContainer.previousSibling) {
// Insert a zero-width space before the selection if the selection
// is non-collapsed and at the beginning of its parent, so said
// parent will have content after extraction. This ensures that the
// parent will not be removed by "tricking" `range.extractContents`.
// Eg, <h1><font>[...]</font></h1> will preserve the styles of the
// <font> node. If it remains empty, it will be cleaned up later by
// the sanitizer.
const zws = document.createTextNode('\u200B');
range.startContainer.before(zws);
insertedZws = zws;
}
let start = range.startContainer;
let end = range.endContainer;
// Let the DOM split and delete the range.
@@ -1925,6 +1938,14 @@ export class OdooEditor extends EventTarget {
break;
}
}
if (insertedZws) {
// Remove the zero-width space (zws) that was added to preserve the
// parent styles, then call `fillEmpty` to properly add a flagged
// zws if still needed.
const el = closestElement(insertedZws);
insertedZws.remove();
el && fillEmpty(el);
}
next = range.endContainer && rightLeafOnlyNotBlockPath(range.endContainer).next().value;
if (
shouldPreserveSpace && next && !(next && next.nodeType === Node.TEXT_NODE && next.textContent.startsWith(' '))
@@ -1003,11 +1003,16 @@ X[]
contentAfter: '<div>a<p>b[]e</p>f</div>',
});
});
it('should delete empty nodes ', async () => {
it('should not delete single remaining empty inline', async () => {
// Forward selection
await testEditor(BasicEditor, {
contentBefore: '<h1><font>[abcdef]</font></h1>',
stepFunction: deleteForward,
// The flagged 200B is there to preserve the font so if we
// write now, we still write in the font element's style.
contentAfterEdit: '<h1><font data-oe-zws-empty-inline="">[]\u200B</font><br></h1>',
// The flagged 200B is removed by the sanitizer if its
// parent remains empty.
contentAfter: '<h1>[]<br></h1>',
});
});
@@ -1,5 +1,5 @@
import { parseHTML } from '../../src/utils/utils.js';
import { BasicEditor, testEditor, unformat } from '../utils.js';
import { BasicEditor, testEditor, unformat, insertText, deleteBackward } from '../utils.js';
const span = text => {
const span = document.createElement('span');
@@ -264,3 +264,22 @@ describe('insert HTML', () => {
});
});
});
describe('insert text', () => {
describe('not collapsed selection', () => {
it('should insert a character in a fully selected font in a heading, preserving its style', async () => {
await testEditor(BasicEditor, {
contentBefore: '<h1><font style="background-color: red;">[abc</font><br></h1><p>]def</p>',
stepFunction: async editor => insertText(editor, 'g'),
contentAfter: '<h1><font style="background-color: red;">g[]</font><br></h1><p>def</p>',
});
await testEditor(BasicEditor, {
contentBefore: '<h1><font style="background-color: red;">[abc</font><br></h1><p>]def</p>',
stepFunction: async editor => {
await deleteBackward(editor);
await insertText(editor, 'g');
},
contentAfter: '<h1><font style="background-color: red;">g[]</font><br></h1><p>def</p>',
});
});
});
});