[REF] web_editor: remove sanitization of zws in favor of cleanForSave
Superfluous zero-width spaces were removed by the sanitizer and then upon saving (`cleanForSave`). This commit removes the code dedicated to that from the sanitizer in favor of the code in `cleanForSave` (adapted to cover all cases). Note: this means that with this commit, superfluous zero-width spaces are not removed from the editor during edition anymore, but only when saving. task-3081457 Part-of: odoo/odoo#111119
This commit is contained in:
@@ -3878,23 +3878,21 @@ export class OdooEditor extends EventTarget {
|
||||
hint.removeAttribute('placeholder');
|
||||
}
|
||||
this._pluginCall('cleanForSave', [element]);
|
||||
// Clean the remaining ZeroWidthspaces added by the `fillEmpty` function
|
||||
// ( contain "data-oe-zws-empty-inline" attr)
|
||||
// If the element contain more than just a ZWS,
|
||||
// we remove it and clean the attribute.
|
||||
// If the element have a class,
|
||||
// we only remove the attribute to ensure we don't break some style.
|
||||
// Otherwise we remove the entire inline element.
|
||||
for (const emptyElement of element.querySelectorAll('[data-oe-zws-empty-inline]')) {
|
||||
if (isZWS(emptyElement)) {
|
||||
if (emptyElement.classList.length > 0) {
|
||||
emptyElement.removeAttribute('data-oe-zws-empty-inline');
|
||||
} else {
|
||||
emptyElement.remove();
|
||||
}
|
||||
} else {
|
||||
|
||||
// Clean the zero-width spaces added by the `fillEmpty` function
|
||||
// (flagged with the "data-oe-zws-empty-inline" attributes). Reverse the
|
||||
// list to start from the deepest elements (for emptiness checks).
|
||||
const allWhitespaceRegex = /^[\s\u200b]*$/;
|
||||
for (const emptyElement of [...element.querySelectorAll('[data-oe-zws-empty-inline]')].reverse()) {
|
||||
emptyElement.removeAttribute('data-oe-zws-empty-inline');
|
||||
if (!allWhitespaceRegex.test(emptyElement.textContent)) {
|
||||
// The element has some meaningful text. Remove the ZWS in it.
|
||||
cleanZWS(emptyElement);
|
||||
emptyElement.removeAttribute('data-oe-zws-empty-inline');
|
||||
} else if (!emptyElement.classList.length) {
|
||||
// We only remove the empty element if it has no class, to
|
||||
// ensure we don't break visual styles (in that case, its
|
||||
// ZWS was kept to ensure the cursor can be placed in it).
|
||||
emptyElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,11 @@ function sanitizeNode(node, root) {
|
||||
if (
|
||||
areSimilarElements(node, node.previousSibling) &&
|
||||
!isUnbreakable(node) &&
|
||||
!isEditorTab(node)
|
||||
!isEditorTab(node) &&
|
||||
!(
|
||||
node.attributes?.length === 1 &&
|
||||
node.hasAttribute('data-oe-zws-empty-inline') &&
|
||||
(node.textContent === '\u200B' || node.previousSibling.textContent === '\u200B'))
|
||||
) {
|
||||
// Merge identical elements together.
|
||||
getDeepRange(root, { select: true });
|
||||
@@ -121,31 +125,6 @@ function sanitizeNode(node, root) {
|
||||
paragraph.replaceChildren(...node.childNodes);
|
||||
node.replaceWith(paragraph);
|
||||
node = paragraph; // The node has been removed, update the reference.
|
||||
} else if (
|
||||
node.nodeType === Node.TEXT_NODE &&
|
||||
node.textContent.includes('\u200B') &&
|
||||
node.parentElement.hasAttribute('data-oe-zws-empty-inline') &&
|
||||
node !== root.ownerDocument.getSelection()?.anchorNode &&
|
||||
!isBlock(node.parentElement) &&
|
||||
(
|
||||
node.textContent.length > 1 ||
|
||||
// There can be multiple ajacent text nodes, in which case
|
||||
// the zero-width space is not needed either, despite being
|
||||
// alone (length === 1) in its own text node.
|
||||
Array.from(node.parentNode.childNodes).find(
|
||||
sibling =>
|
||||
sibling !== node &&
|
||||
sibling.nodeType === Node.TEXT_NODE &&
|
||||
sibling.length > 0
|
||||
)
|
||||
)
|
||||
) {
|
||||
// Remove zero-width spaces added by `fillEmpty` when there is
|
||||
// content and the selection is not next to it.
|
||||
const restoreCursor = node.isConnected && preserveCursor(root.ownerDocument);
|
||||
node.textContent = node.textContent.replace('\u200B', '');
|
||||
node.parentElement.removeAttribute("data-oe-zws-empty-inline");
|
||||
restoreCursor?.();
|
||||
} else if (isFontAwesome(node) && node.textContent !== '\u200B') {
|
||||
// Ensure a zero width space is present inside the FA element.
|
||||
node.textContent = '\u200B';
|
||||
|
||||
@@ -1540,7 +1540,7 @@ X[]
|
||||
await deleteBackward(editor);
|
||||
await insertText(editor, 'x');
|
||||
},
|
||||
contentAfterEdit: '<div><p>cd</p><br><span>x[]</span></div>',
|
||||
contentAfterEdit: '<div><p>cd</p><br><span data-oe-zws-empty-inline="">x[]\u200B</span></div>',
|
||||
contentAfter: '<div><p>cd</p><br><span>x[]</span></div>',
|
||||
});
|
||||
});
|
||||
@@ -1562,7 +1562,7 @@ X[]
|
||||
await deleteBackward(editor);
|
||||
await insertText(editor, 'i');
|
||||
},
|
||||
contentAfterEdit: '<p>uv<i style="color:red">i[]</i>xy</p>',
|
||||
contentAfterEdit: '<p>uv<i style="color:red" data-oe-zws-empty-inline="">i[]\u200B</i>xy</p>',
|
||||
contentAfter: '<p>uv<i style="color:red">i[]</i>xy</p>',
|
||||
});
|
||||
await testEditor(BasicEditor, {
|
||||
@@ -1581,7 +1581,7 @@ X[]
|
||||
await deleteBackward(editor);
|
||||
await insertText(editor, 'x');
|
||||
},
|
||||
contentAfterEdit: '<p>ab<span class="style">x[]</span>ef</p>',
|
||||
contentAfterEdit: '<p>ab<span class="style" data-oe-zws-empty-inline="">x[]\u200B</span>ef</p>',
|
||||
contentAfter: '<p>ab<span class="style">x[]</span>ef</p>',
|
||||
});
|
||||
});
|
||||
@@ -2504,7 +2504,7 @@ X[]
|
||||
await deleteBackward(editor);
|
||||
await insertText(editor, 'x');
|
||||
},
|
||||
contentAfterEdit: '<div><p>ab <span class="style">x[]</span> d</p></div>',
|
||||
contentAfterEdit: '<div><p>ab <span class="style" data-oe-zws-empty-inline="">x[]\u200B</span> d</p></div>',
|
||||
contentAfter: '<div><p>ab <span class="style">x[]</span> d</p></div>',
|
||||
});
|
||||
await testEditor(BasicEditor, {
|
||||
@@ -2513,7 +2513,7 @@ X[]
|
||||
await deleteBackward(editor);
|
||||
await insertText(editor, 'x');
|
||||
},
|
||||
contentAfterEdit: '<div><p>ab<span class="style">x[]</span>d</p></div>',
|
||||
contentAfterEdit: '<div><p>ab<span class="style" data-oe-zws-empty-inline="">x[]\u200B</span>d</p></div>',
|
||||
contentAfter: '<div><p>ab<span class="style">x[]</span>d</p></div>',
|
||||
});
|
||||
await testEditor(BasicEditor, {
|
||||
@@ -2522,7 +2522,7 @@ X[]
|
||||
await deleteBackward(editor);
|
||||
await insertText(editor, 'x');
|
||||
},
|
||||
contentAfterEdit: '<div><p>ab <span class="style">x[]</span> f</p></div>',
|
||||
contentAfterEdit: '<div><p>ab <span class="style" data-oe-zws-empty-inline="">x[]\u200B</span> f</p></div>',
|
||||
contentAfter: '<div><p>ab <span class="style">x[]</span> f</p></div>',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -578,7 +578,7 @@ describe('Format', () => {
|
||||
});
|
||||
});
|
||||
it('should remove underline, write, restore underline, write, remove underline again, write (collapsed, strikeThrough)', async () => {
|
||||
const uselessU = u(''); // TODO: clean
|
||||
const uselessS = s(u('\u200B', 'first'), 'first'); // TODO: clean
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: `<p>ab${u(s(`cd[]ef`))}</p>`,
|
||||
stepFunction: async editor => {
|
||||
@@ -589,7 +589,7 @@ describe('Format', () => {
|
||||
await editor.execCommand('underline');
|
||||
await editor.execCommand('insert', 'C');
|
||||
},
|
||||
contentAfterEdit: `<p>ab${u(s(`cd`))}${s(`A${u(`B`)}C[]${uselessU}`)}${u(s(`ef`))}</p>`,
|
||||
contentAfterEdit: `<p>ab${u(s(`cd`))}${s(`A${u(`B`, 'first')}C[]\u200B`, 'first')}${uselessS}${u(s(`ef`))}</p>`,
|
||||
});
|
||||
});
|
||||
it('should remove only underline decoration on a span', async () => {
|
||||
@@ -682,7 +682,6 @@ describe('Format', () => {
|
||||
});
|
||||
});
|
||||
it('should remove underline, write, restore underline, write, remove underline again, write (collapsed, italic)', async () => {
|
||||
const uselessU = u(''); // TODO: clean
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: `<p>ab${u(em(`cd[]ef`))}</p>`,
|
||||
stepFunction: async editor => {
|
||||
@@ -693,7 +692,7 @@ describe('Format', () => {
|
||||
await editor.execCommand('underline');
|
||||
await editor.execCommand('insert', 'C');
|
||||
},
|
||||
contentAfter: `<p>ab${u(em(`cd`))}${em(`A${u(`B`)}C[]${uselessU}`)}${u(em(`ef`))}</p>`,
|
||||
contentAfter: `<p>ab${u(em(`cd`))}${em(`A${u(`B`)}C[]`)}${u(em(`ef`))}</p>`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user