[IMP] web_editor: expand deleterange for uneditables
In Chrome, it is possible to partially select the contents of a contenteditable=false element if it has a contenteditable=true child. This commit prevents breaking such uneditables by expanding the range in a deleterange to fully contain such elements. It will be useful with the new blocks introduced with the Knowledge module. Task-id: 2794990 Part-of: odoo/odoo#86489
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
endPos,
|
||||
ensureFocus,
|
||||
getCursorDirection,
|
||||
getFurthestUneditableParent,
|
||||
getListMode,
|
||||
getOuid,
|
||||
insertText,
|
||||
@@ -1323,6 +1324,28 @@ export class OdooEditor extends EventTarget {
|
||||
correctTripleClick: true,
|
||||
});
|
||||
if (!range) return;
|
||||
// Expand the range to fully include all contentEditable=False elements.
|
||||
const commonAncestorContainer = this.editable.contains(range.commonAncestorContainer) ?
|
||||
range.commonAncestorContainer :
|
||||
this.editable;
|
||||
const startUneditable = getFurthestUneditableParent(range.startContainer, commonAncestorContainer);
|
||||
if (startUneditable) {
|
||||
let leaf = previousLeaf(startUneditable);
|
||||
if (leaf) {
|
||||
range.setStart(leaf, nodeSize(leaf));
|
||||
} else {
|
||||
range.setStart(commonAncestorContainer, 0);
|
||||
}
|
||||
}
|
||||
const endUneditable = getFurthestUneditableParent(range.endContainer, commonAncestorContainer);
|
||||
if (endUneditable) {
|
||||
let leaf = nextLeaf(endUneditable);
|
||||
if (leaf) {
|
||||
range.setEnd(leaf, 0);
|
||||
} else {
|
||||
range.setEnd(commonAncestorContainer, nodeSize(commonAncestorContainer));
|
||||
}
|
||||
}
|
||||
let start = range.startContainer;
|
||||
let end = range.endContainer;
|
||||
// Let the DOM split and delete the range.
|
||||
|
||||
@@ -230,6 +230,29 @@ export function findNode(domPath, findCallback = () => true, stopCallback = () =
|
||||
* @param {Node} node
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return the furthest uneditable parent of node contained within parentLimit.
|
||||
* @see deleteRange Used to guarantee that uneditables are fully contained in
|
||||
* the range (so that it is not possible to partially remove them)
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {Node} parentLimit non-inclusive furthest parent allowed
|
||||
* @returns {Node} uneditable parent if it exists
|
||||
*/
|
||||
export function getFurthestUneditableParent(node, parentLimit) {
|
||||
if (node === parentLimit || !parentLimit.contains(node)) {
|
||||
return undefined;
|
||||
}
|
||||
let parent = node && node.parentElement;
|
||||
let nonEditableElement;
|
||||
while (parent && parent !== parentLimit) {
|
||||
if (!parent.isContentEditable) {
|
||||
nonEditableElement = parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return nonEditableElement;
|
||||
}
|
||||
/**
|
||||
* Returns the closest HTMLElement of the provided Node
|
||||
* if a 'selector' is provided, Returns the closest HTMLElement that match the selector
|
||||
|
||||
@@ -2351,6 +2351,36 @@ X[]
|
||||
<p>before[]after</p>`),
|
||||
});
|
||||
});
|
||||
it('should extend the range to fully include contenteditable=false that are partially selected at the end of the range', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: unformat(`
|
||||
<p>before[o</p>
|
||||
<div contenteditable="false">
|
||||
<div contenteditable="true"><p>intruder]</p></div>
|
||||
</div>
|
||||
<p>after</p>`),
|
||||
stepFunction: async editor => {
|
||||
await deleteBackward(editor);
|
||||
},
|
||||
contentAfter: unformat(`
|
||||
<p>before[]</p><p>after</p>`),
|
||||
});
|
||||
});
|
||||
it('should extend the range to fully include contenteditable=false that are partially selected at the start of the range', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: unformat(`
|
||||
<p>before</p>
|
||||
<div contenteditable="false">
|
||||
<div contenteditable="true"><p>[intruder</p></div>
|
||||
</div>
|
||||
<p>o]after</p>`),
|
||||
stepFunction: async editor => {
|
||||
await deleteBackward(editor);
|
||||
},
|
||||
contentAfter: unformat(`
|
||||
<p>before[]after</p>`),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CTRL+Backspace', () => {
|
||||
|
||||
Reference in New Issue
Block a user