[FIX] web_editor: ensure proper live adjacent nested lists merging

Sometimes, two similar adjacent elements must be merged together (eg,
two `<span>` elements with the same style, or two lists of the same
type).  This is done by the sanitizer, which is called on the common
ancestor of the mutated nodes after each change, and on the whole
editable when the document is saved. In the case of nested lists, this
can cause a visible discrepancy between what is saved and what is
edited:

1. In a list, say the first item contains a nested bullet list, and the
   second item contains a nested numbered list:
   `<li><ul>...</ul><li><ol>...</ol></li>`
2. Now we transform the numbered list into a bullet list:
   `<li><ul>...</ul><li><ul>...</ul></li>`
3. The mutation happened on the second `<li>` element so we call the
   sanitizer on it, and it doesn't have enough context information to
   merge the two `<li>` elements, so it leaves them as is.
4. The user saves the document, and the sanitizer is called on the
    whole editable, which has enough context information to merge the
    two `<li>` elements, so it does so, and the saved document is
    different from what the user sees. It is now `<li><ul>...</ul></li>`
    as it should have been immediately after the mutation.

We solve this in a straight-forward, ad-hoc way since we currently know
of no other case where this is needed, by calling the sanitizer on the
parent of the first ancestor nested list of the mutated nodes, if any.

closes odoo/odoo#111871

X-original-commit: 50b7b41a4573fdce8891fac5d6aa1d08c557d455
Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
Antoine Guenet
2023-02-03 16:32:40 +01:00
parent 4a57f9c54f
commit 08645e8e2e
2 changed files with 24 additions and 0 deletions
@@ -711,6 +711,19 @@ export class OdooEditor extends EventTarget {
return false;
}
// If the common ancestor is in a nested list, make sure to sanitize
// that list's parent <li> instead, so there is enough context to
// potentially merge sibling nested lists
// (eg, <ol>
// <li class="oe-nested"><ul>...</ul></li>
// <li class="oe-nested"><ul>...</ul></li>
// </ol>: these two lists should be merged together so the common
// ancestor should be the <ol> element).
const nestedListAncestor = closestElement(commonAncestor, '.oe-nested');
if (nestedListAncestor && nestedListAncestor.parentElement) {
commonAncestor = nestedListAncestor.parentElement;
}
// sanitize and mark current position as sanitized
sanitize(commonAncestor);
this._pluginCall('sanitizeElement', [commonAncestor]);
@@ -315,6 +315,17 @@ describe('List', () => {
</li>
</ul>`),
stepFunction: toggleCheckList,
contentAfterEdit: unformat(`
<ul class="o_checklist">
<li class="o_checked" id="checkId-1">abc</li>
<li class="oe-nested">
<ul class="o_checklist">
<li class="o_checked" id="checkId-2">def</li>
<li id="checkId-4">g[]hi</li>
<li class="o_checked" id="checkId-3">jkl</li>
</ul>
</li>
</ul>`),
contentAfter: unformat(`
<ul class="o_checklist">
<li class="o_checked">abc</li>