From 112aad4863ba44304265de6db7bbba14beac8c2f Mon Sep 17 00:00:00 2001 From: Nicolas Bayet Date: Tue, 29 Jun 2021 11:56:25 +0000 Subject: [PATCH] [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 --- .../lib/odoo-editor/src/commands/commands.js | 1 + .../odoo-editor/src/commands/deleteForward.js | 27 +++++++++------ .../static/lib/odoo-editor/src/utils/utils.js | 34 +++++++++++++------ .../lib/odoo-editor/test/spec/editor.test.js | 26 +++++++++++--- 4 files changed, 62 insertions(+), 26 deletions(-) diff --git a/addons/web_editor/static/lib/odoo-editor/src/commands/commands.js b/addons/web_editor/static/lib/odoo-editor/src/commands/commands.js index 8934a046a4c..ce447e3110e 100644 --- a/addons/web_editor/static/lib/odoo-editor/src/commands/commands.js +++ b/addons/web_editor/static/lib/odoo-editor/src/commands/commands.js @@ -30,6 +30,7 @@ import { splitElement, splitTextNode, startPos, + nodeSize, } from '../utils/utils.js'; const TEXT_CLASSES_REGEX = /\btext-[^\s]*\b/g; diff --git a/addons/web_editor/static/lib/odoo-editor/src/commands/deleteForward.js b/addons/web_editor/static/lib/odoo-editor/src/commands/deleteForward.js index 50f9206b084..e05d8aebb00 100644 --- a/addons/web_editor/static/lib/odoo-editor/src/commands/deleteForward.js +++ b/addons/web_editor/static/lib/odoo-editor/src/commands/deleteForward.js @@ -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) { diff --git a/addons/web_editor/static/lib/odoo-editor/src/utils/utils.js b/addons/web_editor/static/lib/odoo-editor/src/utils/utils.js index 48ce436d0a8..68ffd2f8535 100644 --- a/addons/web_editor/static/lib/odoo-editor/src/utils/utils.js +++ b/addons/web_editor/static/lib/odoo-editor/src/utils/utils.js @@ -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), +}); diff --git a/addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js b/addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js index 58f3b0dace2..657d98a10f9 100644 --- a/addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js +++ b/addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js @@ -157,18 +157,18 @@ X[] }); it('should remove contenteditable="false"', async () => { await testEditor(BasicEditor, { - contentBefore: `
abc[]def
`, + contentBefore: `
[]abcdef
`, stepFunction: async editor => { - await deleteBackward(editor); + await deleteForward(editor); }, contentAfter: `
[]def
`, }); }); it('should remove contenteditable="False"', async () => { await testEditor(BasicEditor, { - contentBefore: `
abc[]def
`, + contentBefore: `
[]abcdef
`, stepFunction: async editor => { - await deleteBackward(editor); + await deleteForward(editor); }, contentAfter: `
[]def
`, }); @@ -1043,6 +1043,24 @@ X[] contentAfter: `

mollis.[]Pelentesque

`, }); }); + it('should remove contenteditable="false"', async () => { + await testEditor(BasicEditor, { + contentBefore: `
abc[]def
`, + stepFunction: async editor => { + await deleteBackward(editor); + }, + contentAfter: `
[]def
`, + }); + }); + it('should remove contenteditable="False"', async () => { + await testEditor(BasicEditor, { + contentBefore: `
abc[]def
`, + stepFunction: async editor => { + await deleteBackward(editor); + }, + contentAfter: `
[]def
`, + }); + }); }); describe('Line breaks', () => { describe('Single', () => {