[FW][FIX] web_editor: correct delete forward and backward on non editable
Trying to delete forward a non editable was erasing the first character of the non editable. Trying to delete backward one character after the contenteditable false made the cursor jump before the contenteditable. closes odoo/odoo#74623 X-original-commit: 9b7d9ad3771579ccb60a1be526f5d7571f64e638 Signed-off-by: Antoine Guenet <Zinston@users.noreply.github.com>
This commit is contained in:
@@ -30,6 +30,7 @@ import {
|
||||
splitElement,
|
||||
splitTextNode,
|
||||
startPos,
|
||||
nodeSize,
|
||||
} from '../utils/utils.js';
|
||||
|
||||
const TEXT_CLASSES_REGEX = /\btext-[^\s]*\b/g;
|
||||
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
CTYPES,
|
||||
leftPos,
|
||||
isFontAwesome,
|
||||
rightLeafOnlyNotBlockPath,
|
||||
rightLeafOnlyPath,
|
||||
rightLeafOnlyNotBlockNotEditablePath,
|
||||
rightLeafOnlyPathNotBlockNotEditablePath,
|
||||
isNotEditableNode,
|
||||
} from '../utils/utils.js';
|
||||
|
||||
Text.prototype.oDeleteForward = function (offset) {
|
||||
@@ -24,23 +25,27 @@ Text.prototype.oDeleteForward = function (offset) {
|
||||
};
|
||||
|
||||
HTMLElement.prototype.oDeleteForward = function (offset) {
|
||||
const filterFunc = node => isVisibleEmpty(node) || isContentTextNode(node);
|
||||
const filterFunc = node =>
|
||||
isVisibleEmpty(node) || isContentTextNode(node) || isNotEditableNode(node);
|
||||
|
||||
const firstInlineNode = findNode(rightLeafOnlyNotBlockPath(this, offset), filterFunc);
|
||||
if (isFontAwesome(firstInlineNode && firstInlineNode.parentElement)) {
|
||||
firstInlineNode.parentElement.remove();
|
||||
const firstLeafNode = findNode(rightLeafOnlyNotBlockNotEditablePath(this, offset), filterFunc);
|
||||
|
||||
if (firstLeafNode && (isFontAwesome(firstLeafNode) || isNotEditableNode(firstLeafNode))) {
|
||||
firstLeafNode.remove();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
firstInlineNode &&
|
||||
(firstInlineNode.nodeName !== 'BR' ||
|
||||
getState(...rightPos(firstInlineNode), DIRECTIONS.RIGHT).cType !== CTYPES.BLOCK_INSIDE)
|
||||
firstLeafNode &&
|
||||
(firstLeafNode.nodeName !== 'BR' ||
|
||||
getState(...rightPos(firstLeafNode), DIRECTIONS.RIGHT).cType !== CTYPES.BLOCK_INSIDE)
|
||||
) {
|
||||
firstInlineNode.oDeleteBackward(Math.min(1, nodeSize(firstInlineNode)));
|
||||
firstLeafNode.oDeleteBackward(Math.min(1, nodeSize(firstLeafNode)));
|
||||
return;
|
||||
}
|
||||
const firstOutNode = findNode(
|
||||
rightLeafOnlyPath(...(firstInlineNode ? rightPos(firstInlineNode) : [this, offset])),
|
||||
rightLeafOnlyPathNotBlockNotEditablePath(
|
||||
...(firstLeafNode ? rightPos(firstLeafNode) : [this, offset]),
|
||||
),
|
||||
filterFunc,
|
||||
);
|
||||
if (firstOutNode) {
|
||||
|
||||
@@ -133,11 +133,11 @@ const PATH_END_REASONS = {
|
||||
*
|
||||
* @see leftLeafFirstPath
|
||||
* @see leftLeafOnlyNotBlockPath
|
||||
* @see leftLeafOnlyInScopeNotBlockPath
|
||||
* @see rightLeafOnlyNotBlockPath
|
||||
* @see rightLeafOnlyPath
|
||||
* @see rightLeafOnlyNotBlockInScopePath
|
||||
* @see leftLeafOnlyInScopeNotBlockNoEditablePath
|
||||
* @see rightLeafOnlyNotBlockPath
|
||||
* @see rightLeafOnlyPathNotBlockNotEditablePath
|
||||
* @see rightLeafOnlyInScopeNotBlockPath
|
||||
* @see rightLeafOnlyNotBlockNotEditablePath
|
||||
*
|
||||
* @param {number} direction
|
||||
* @param {boolean} [options.leafOnly] if true, do not yield any non-leaf node
|
||||
@@ -456,7 +456,8 @@ export function getNormalizedCursorPosition(node, offset, full = true) {
|
||||
}
|
||||
}
|
||||
if (el) {
|
||||
const leftInlineNode = leftLeafOnlyInScopeNotBlockPath(el, elOffset).next().value;
|
||||
const leftInlineNode = leftLeafOnlyInScopeNotBlockNoEditablePath(el, elOffset).next()
|
||||
.value;
|
||||
let leftVisibleEmpty = false;
|
||||
if (leftInlineNode) {
|
||||
leftVisibleEmpty =
|
||||
@@ -467,7 +468,7 @@ export function getNormalizedCursorPosition(node, offset, full = true) {
|
||||
: endPos(leftInlineNode);
|
||||
}
|
||||
if (!leftInlineNode || leftVisibleEmpty) {
|
||||
const rightInlineNode = rightLeafOnlyNotBlockInScopePath(el, elOffset).next().value;
|
||||
const rightInlineNode = rightLeafOnlyInScopeNotBlockPath(el, elOffset).next().value;
|
||||
if (rightInlineNode) {
|
||||
const rightVisibleEmpty =
|
||||
isVisibleEmpty(rightInlineNode) ||
|
||||
@@ -1889,17 +1890,23 @@ export function getRangePosition(el, document, options = {}) {
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
export const isNotEditableNode = node =>
|
||||
node.getAttribute &&
|
||||
node.getAttribute('contenteditable') &&
|
||||
node.getAttribute('contenteditable').toLowerCase() === 'false';
|
||||
|
||||
export const leftLeafFirstPath = createDOMPathGenerator(DIRECTIONS.LEFT);
|
||||
export const leftLeafOnlyNotBlockPath = createDOMPathGenerator(DIRECTIONS.LEFT, {
|
||||
leafOnly: true,
|
||||
stopTraverseFunction: isBlock,
|
||||
stopFunction: isBlock,
|
||||
});
|
||||
export const leftLeafOnlyInScopeNotBlockPath = createDOMPathGenerator(DIRECTIONS.LEFT, {
|
||||
export const leftLeafOnlyInScopeNotBlockNoEditablePath = createDOMPathGenerator(DIRECTIONS.LEFT, {
|
||||
leafOnly: true,
|
||||
inScope: true,
|
||||
stopTraverseFunction: isBlock,
|
||||
stopFunction: isBlock,
|
||||
stopTraverseFunction: node => isNotEditableNode(node) || isBlock(node),
|
||||
stopFunction: node => isNotEditableNode(node) || isBlock(node),
|
||||
});
|
||||
|
||||
export const rightLeafOnlyNotBlockPath = createDOMPathGenerator(DIRECTIONS.RIGHT, {
|
||||
@@ -1908,12 +1915,17 @@ export const rightLeafOnlyNotBlockPath = createDOMPathGenerator(DIRECTIONS.RIGHT
|
||||
stopFunction: isBlock,
|
||||
});
|
||||
|
||||
export const rightLeafOnlyPath = createDOMPathGenerator(DIRECTIONS.RIGHT, {
|
||||
export const rightLeafOnlyPathNotBlockNotEditablePath = createDOMPathGenerator(DIRECTIONS.RIGHT, {
|
||||
leafOnly: true,
|
||||
});
|
||||
export const rightLeafOnlyNotBlockInScopePath = createDOMPathGenerator(DIRECTIONS.RIGHT, {
|
||||
export const rightLeafOnlyInScopeNotBlockPath = createDOMPathGenerator(DIRECTIONS.RIGHT, {
|
||||
leafOnly: true,
|
||||
inScope: true,
|
||||
stopTraverseFunction: isBlock,
|
||||
stopFunction: isBlock,
|
||||
});
|
||||
export const rightLeafOnlyNotBlockNotEditablePath = createDOMPathGenerator(DIRECTIONS.RIGHT, {
|
||||
leafOnly: true,
|
||||
stopTraverseFunction: node => isNotEditableNode(node) || isBlock(node),
|
||||
stopFunction: node => isBlock(node) && !isNotEditableNode(node),
|
||||
});
|
||||
|
||||
@@ -157,18 +157,18 @@ X[]
|
||||
});
|
||||
it('should remove contenteditable="false"', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: `<div><span contenteditable="false">abc</span>[]def</div>`,
|
||||
contentBefore: `<div>[]<span contenteditable="false">abc</span>def</div>`,
|
||||
stepFunction: async editor => {
|
||||
await deleteBackward(editor);
|
||||
await deleteForward(editor);
|
||||
},
|
||||
contentAfter: `<div>[]def</div>`,
|
||||
});
|
||||
});
|
||||
it('should remove contenteditable="False"', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: `<div><span contenteditable="False">abc</span>[]def</div>`,
|
||||
contentBefore: `<div>[]<span contenteditable="False">abc</span>def</div>`,
|
||||
stepFunction: async editor => {
|
||||
await deleteBackward(editor);
|
||||
await deleteForward(editor);
|
||||
},
|
||||
contentAfter: `<div>[]def</div>`,
|
||||
});
|
||||
@@ -1043,6 +1043,24 @@ X[]
|
||||
contentAfter: `<p>mollis.[]<i>Pelentesque</i></p>`,
|
||||
});
|
||||
});
|
||||
it('should remove contenteditable="false"', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: `<div><span contenteditable="false">abc</span>[]def</div>`,
|
||||
stepFunction: async editor => {
|
||||
await deleteBackward(editor);
|
||||
},
|
||||
contentAfter: `<div>[]def</div>`,
|
||||
});
|
||||
});
|
||||
it('should remove contenteditable="False"', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: `<div><span contenteditable="False">abc</span>[]def</div>`,
|
||||
stepFunction: async editor => {
|
||||
await deleteBackward(editor);
|
||||
},
|
||||
contentAfter: `<div>[]def</div>`,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Line breaks', () => {
|
||||
describe('Single', () => {
|
||||
|
||||
Reference in New Issue
Block a user