[FIX] web_editor: fix triple click on block followed by empty block

Triple click correction failed whenever the triple-click-selected block
was followed by an empty block. The reason is that `getDeepestPosition`
prevents the selection from being placed within a line break (`<br>`)
and therefore returns the block itself (`<p>[]<br></p>` will be
expressed as `(p, 0)`), so the heuristic to determine if triple click
correction in needed in `getDeepRange` would fail as it assumed the
`end` node was a leaf where it wasn't. This fixes the issue by applying
the heurstic to the first leaf of the `end` node instead, and making the
correction for there as well.

task-3180922

closes odoo/odoo#112637

X-original-commit: 81653c320a1e431e4b9ae831bf0b8522588368e6
Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
Antoine Guenet
2023-02-14 13:14:04 +01:00
parent dad8de3863
commit a8581ddca6
2 changed files with 40 additions and 5 deletions
@@ -763,17 +763,18 @@ export function getDeepRange(editable, { range, sel, splitText, select, correctT
}
}
}
// A selection spanning multiple nodes and ending at position 0 of a
// node, like the one resulting from a triple click, are corrected so
// that it ends at the last position of the previous node instead.
const beforeEnd = end.previousSibling;
// A selection spanning multiple nodes and ending at position 0 of a node,
// like the one resulting from a triple click, is corrected so that it ends
// at the last position of the previous node instead.
const endLeaf = firstLeaf(end);
const beforeEnd = endLeaf.previousSibling;
if (
correctTripleClick &&
!endOffset &&
(start !== end || startOffset !== endOffset) &&
(!beforeEnd || (beforeEnd.nodeType === Node.TEXT_NODE && !isVisibleStr(beforeEnd)))
) {
const previous = previousLeaf(end, editable, true);
const previous = previousLeaf(endLeaf, editable, true);
if (previous && closestElement(previous).isContentEditable) {
[end, endOffset] = [previous, nodeSize(previous)];
}
@@ -1190,6 +1190,11 @@ X[]
// JW cAfter: '<p>[]def</p>',
contentAfter: '<h1>[]<br></h1><p>def</p>',
});
await testEditor(BasicEditor, {
contentBefore: '<h1>[abc</h1><p>]<br></p><p>def</p>',
stepFunction: deleteForward,
contentAfter: '<h1>[]<br></h1><p><br></p><p>def</p>',
});
});
it('should delete last character of paragraph, ignoring the selected paragraph break', async () => {
await testEditor(BasicEditor, {
@@ -2668,6 +2673,11 @@ X[]
// JW cAfter: '<p>[]def</p>',
contentAfter: '<h1>[]<br></h1><p>def</p>',
});
await testEditor(BasicEditor, {
contentBefore: '<h1>[abc</h1><p>]<br></p><p>def</p>',
stepFunction: deleteBackward,
contentAfter: '<h1>[]<br></h1><p><br></p><p>def</p>',
});
});
it('should delete last character of paragraph, ignoring the selected paragraph break', async () => {
await testEditor(BasicEditor, {
@@ -2678,6 +2688,14 @@ X[]
stepFunction: deleteBackward,
contentAfter: '<p>ab[]</p><p>def</p>',
});
await testEditor(BasicEditor, {
contentBefore: '<p>ab[c</p><p>]<br></p><p>def</p>',
// This type of selection (typically done with a triple
// click) is "corrected" before remove so triple clicking
// doesn't remove a paragraph break.
stepFunction: deleteBackward,
contentAfter: '<p>ab[]</p><p><br></p><p>def</p>',
});
});
it('should delete first character of paragraph, as well as selected paragraph break', async () => {
await testEditor(BasicEditor, {
@@ -2695,6 +2713,22 @@ X[]
stepFunction: deleteBackward,
contentAfter: '<p>ab[]</p><p t="unbreak">def</p>',
});
await testEditor(BasicEditor, {
contentBefore: '<p>ab[c</p><p t="unbreak">]<br></p><p>def</p>',
// This type of selection (typically done with a triple
// click) is "corrected" before remove so triple clicking
// doesn't remove a paragraph break.
stepFunction: deleteBackward,
contentAfter: '<p>ab[]</p><p t="unbreak"><br></p><p>def</p>',
});
await testEditor(BasicEditor, {
contentBefore: '<p>ab[c</p><p>]<br></p><p t="unbreak">def</p>',
// This type of selection (typically done with a triple
// click) is "corrected" before remove so triple clicking
// doesn't remove a paragraph break.
stepFunction: deleteBackward,
contentAfter: '<p>ab[]</p><p><br></p><p t="unbreak">def</p>',
});
});
it('should delete first character of unbreakable, ignoring selected paragraph break', async () => {
await testEditor(BasicEditor, {