[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) <dmo@odoo.com>
Signed-off-by: Antoine Guenet (age) <age@odoo.com>
This commit is contained in:
Antoine Guenet
2023-01-24 10:20:45 +01:00
parent f261efa1d7
commit 91c647a368
2 changed files with 66 additions and 13 deletions
@@ -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();
@@ -5684,6 +5684,12 @@ X[]
stepFunction: async editor => insertText(editor, '`'),
contentAfter: '<p>\u200B<code class="o_inline_code">ab</code>\u200B[]cd</p>',
});
// BACKWARDS
await testEditor(BasicEditor, {
contentBefore: '<p>[]ab`cd</p>',
stepFunction: async editor => insertText(editor, '`'),
contentAfter: '<p>\u200B<code class="o_inline_code">[]ab</code>cd</p>',
});
});
it('should convert text into inline code (middle)', async () => {
await testEditor(BasicEditor, {
@@ -5691,6 +5697,12 @@ X[]
stepFunction: async editor => insertText(editor, '`'),
contentAfter: '<p>ab<code class="o_inline_code">cd</code>\u200B[]ef</p>',
});
// BACKWARDS
await testEditor(BasicEditor, {
contentBefore: '<p>ab[]cd`ef</p>',
stepFunction: async editor => insertText(editor, '`'),
contentAfter: '<p>ab<code class="o_inline_code">[]cd</code>ef</p>',
});
});
it('should convert text into inline code (end)', async () => {
await testEditor(BasicEditor, {
@@ -5698,13 +5710,26 @@ X[]
stepFunction: async editor => insertText(editor, '`'),
contentAfter: '<p>ab<code class="o_inline_code">cd</code>\u200B[]</p>',
});
// BACKWARDS
await testEditor(BasicEditor, {
contentBefore: '<p>ab[]cd`</p>',
stepFunction: async editor => insertText(editor, '`'),
contentAfter: '<p>ab<code class="o_inline_code">[]cd</code></p>',
});
});
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: '<p>a`b`cd[]e`f</p>',
stepFunction: async editor => insertText(editor, '`'),
// The closest PREVIOUS backtick is prioritary
contentAfter: '<p>a`b<code class="o_inline_code">cd</code>\u200B[]e`f</p>',
});
await testEditor(BasicEditor, {
contentBefore: '<p>ab[]cd`e`f</p>',
stepFunction: async editor => insertText(editor, '`'),
// If there is no previous backtick, use the closest NEXT backtick.
contentAfter: '<p>ab<code class="o_inline_code">[]cd</code>e`f</p>',
});
});
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: '<p>ab[]c</p>',
stepFunction: async editor => {
@@ -5766,6 +5792,15 @@ X[]
},
contentAfter: '<p>\u200B<code class="o_inline_code">ab</code>\u200B[]c</p>',
});
// BACKTICK IS NEXT SIBLING
await testEditor(BasicEditor, {
contentBefore: '<p>ab[]c</p>',
stepFunction: async editor => {
editor.document.getSelection().anchorNode.after(document.createTextNode('`'));
insertText(editor, '`');
},
contentAfter: '<p>ab<code class="o_inline_code">[]c</code></p>',
});
});
});
});