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 2c699d2423f..d452fff29be 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
@@ -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,
[...]
will preserve the styles of the
+ // 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(' '))
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 e17d8074a7e..8041867dfa7 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
@@ -1003,11 +1003,16 @@ X[]
contentAfter: '',
});
});
- it('should delete empty nodes ', async () => {
+ it('should not delete single remaining empty inline', async () => {
// Forward selection
await testEditor(BasicEditor, {
contentBefore: '[abcdef]
',
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: '[]\u200B
',
+ // The flagged 200B is removed by the sanitizer if its
+ // parent remains empty.
contentAfter: '[]
',
});
});
diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/insert.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/insert.test.js
index f0d0cb7a4a5..d5e2e9552e3 100644
--- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/insert.test.js
+++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/insert.test.js
@@ -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: '[abc
]def
',
+ stepFunction: async editor => insertText(editor, 'g'),
+ contentAfter: 'g[]
def
',
+ });
+ await testEditor(BasicEditor, {
+ contentBefore: '[abc
]def
',
+ stepFunction: async editor => {
+ await deleteBackward(editor);
+ await insertText(editor, 'g');
+ },
+ contentAfter: 'g[]
def
',
+ });
+ });
+ });
+});