From 91c647a3688102896e441cee0c48327cf04a916a Mon Sep 17 00:00:00 2001 From: Antoine Guenet Date: Thu, 12 Jan 2023 16:39:43 +0000 Subject: [PATCH] [FIX] web_editor: handle backwards backtick conversion to inline code When inserting a backtick in text that has another backtick before (case 1) or after (case 2) it, we convert the text contained between said backticks into inline code. Case 2 was not tested, and didn't work properly. closes odoo/odoo#110784 X-original-commit: cc2a4a960d6d83362e35abc8a253e95105fd07f9 Signed-off-by: David Monjoie (dmo) Signed-off-by: Antoine Guenet (age) --- .../js/editor/odoo-editor/src/OdooEditor.js | 42 +++++++++++++------ .../odoo-editor/test/spec/editor.test.js | 37 +++++++++++++++- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js index 5b5270399bd..8026441f299 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js @@ -3186,7 +3186,7 @@ export class OdooEditor extends EventTarget { } if (ev.data === '`' && !closestElement(selection.anchorNode, 'code')) { // We just inserted a backtick, check if there was another - // one before. + // one in the text. const range = getDeepRange(this.editable); let textNode = range.startContainer; let offset = range.startOffset; @@ -3205,16 +3205,30 @@ export class OdooEditor extends EventTarget { sibling = textNode.nextSibling; } setSelection(textNode, offset); - const textHasOpeningTick = /`.*`/.test(textNode.textContent); - if (textHasOpeningTick) { + const textHasTwoTicks = /`.*`/.test(textNode.textContent); + if (textHasTwoTicks) { this.historyStep(); - if (offset !== textNode.textContent.length) { - splitTextNode(textNode, offset); - textNode = textNode.previousSibling; + const insertedBacktickIndex = offset - 1; + const textBeforeInsertedBacktick = textNode.textContent.substring(0, insertedBacktickIndex - 1); + let startOffset, endOffset; + const isClosingForward = textBeforeInsertedBacktick.includes('`'); + if (isClosingForward) { + // There is a backtick before the new backtick. + startOffset = textBeforeInsertedBacktick.lastIndexOf('`'); + endOffset = insertedBacktickIndex; + } else { + // There is a backtick after the new backtick. + const textAfterInsertedBacktick = textNode.textContent.substring(offset); + startOffset = insertedBacktickIndex; + endOffset = offset + textAfterInsertedBacktick.indexOf('`'); } - const openingTickOffset = textNode.textContent.substring(0, textNode.textContent.length - 1).lastIndexOf('`'); - if (openingTickOffset) { - splitTextNode(textNode, openingTickOffset); + // Split around the backticks if needed so text starts + // and ends with a backtick. + if (endOffset && endOffset < textNode.textContent.length) { + splitTextNode(textNode, endOffset + 1, DIRECTIONS.LEFT); + } + if (startOffset) { + splitTextNode(textNode, startOffset); } // Remove ticks. textNode.textContent = textNode.textContent.substring(1, textNode.textContent.length - 1); @@ -3226,9 +3240,13 @@ export class OdooEditor extends EventTarget { if (!codeElement.previousSibling || codeElement.previousSibling.nodeType !== Node.TEXT_NODE) { codeElement.before(document.createTextNode('\u200B')); } - // Move selection out of code element. - codeElement.after(document.createTextNode('\u200B')); - setSelection(codeElement.nextSibling, 1); + if (isClosingForward) { + // Move selection out of code element. + codeElement.after(document.createTextNode('\u200B')); + setSelection(codeElement.nextSibling, 1); + } else { + setSelection(codeElement.firstChild, 0); + } } } this.historyStep(); diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js index dc0c171485b..1db4573a8b1 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js @@ -5684,6 +5684,12 @@ X[] stepFunction: async editor => insertText(editor, '`'), contentAfter: '

\u200Bab\u200B[]cd

', }); + // BACKWARDS + await testEditor(BasicEditor, { + contentBefore: '

[]ab`cd

', + stepFunction: async editor => insertText(editor, '`'), + contentAfter: '

\u200B[]abcd

', + }); }); it('should convert text into inline code (middle)', async () => { await testEditor(BasicEditor, { @@ -5691,6 +5697,12 @@ X[] stepFunction: async editor => insertText(editor, '`'), contentAfter: '

abcd\u200B[]ef

', }); + // BACKWARDS + await testEditor(BasicEditor, { + contentBefore: '

ab[]cd`ef

', + stepFunction: async editor => insertText(editor, '`'), + contentAfter: '

ab[]cdef

', + }); }); it('should convert text into inline code (end)', async () => { await testEditor(BasicEditor, { @@ -5698,13 +5710,26 @@ X[] stepFunction: async editor => insertText(editor, '`'), contentAfter: '

abcd\u200B[]

', }); + // BACKWARDS + await testEditor(BasicEditor, { + contentBefore: '

ab[]cd`

', + stepFunction: async editor => insertText(editor, '`'), + contentAfter: '

ab[]cd

', + }); }); - it('should convert text into inline code and leave an earlier and a later backtick alone', async () => { + it('should convert text into inline code, with parasite backticks', async () => { await testEditor(BasicEditor, { contentBefore: '

a`b`cd[]e`f

', stepFunction: async editor => insertText(editor, '`'), + // The closest PREVIOUS backtick is prioritary contentAfter: '

a`bcd\u200B[]e`f

', }); + await testEditor(BasicEditor, { + contentBefore: '

ab[]cd`e`f

', + stepFunction: async editor => insertText(editor, '`'), + // If there is no previous backtick, use the closest NEXT backtick. + contentAfter: '

ab[]cde`f

', + }); }); it('should not convert text into inline code when traversing HTMLElements', async () => { await testEditor(BasicEditor, { @@ -5758,6 +5783,7 @@ X[] }); }); it('should convert text into inline code even when the other backtick is in a separate text node', async () => { + // BACKTICK IS PREVIOUS SIBLING await testEditor(BasicEditor, { contentBefore: '

ab[]c

', stepFunction: async editor => { @@ -5766,6 +5792,15 @@ X[] }, contentAfter: '

\u200Bab\u200B[]c

', }); + // BACKTICK IS NEXT SIBLING + await testEditor(BasicEditor, { + contentBefore: '

ab[]c

', + stepFunction: async editor => { + editor.document.getSelection().anchorNode.after(document.createTextNode('`')); + insertText(editor, '`'); + }, + contentAfter: '

ab[]c

', + }); }); }); });