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 bd6fd5c6849..b71c1d2a406 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 @@ -66,6 +66,7 @@ import { hasTableSelection, pxToFloat, parseHTML, + splitTextNode, } from './utils/utils.js'; import { editorCommands } from './commands/commands.js'; import { Powerbox } from './powerbox/Powerbox.js'; @@ -3011,6 +3012,54 @@ export class OdooEditor extends EventTarget { ); } } + if (ev.data === '`' && !closestElement(selection.anchorNode, 'code')) { + // We just inserted a backtick, check if there was another + // one before. + const range = getDeepRange(this.editable); + let textNode = range.startContainer; + let offset = range.startOffset; + let sibling = textNode.previousSibling; + while (sibling && sibling.nodeType === Node.TEXT_NODE) { + offset += sibling.textContent.length; + sibling.textContent += textNode.textContent; + textNode.remove(); + textNode = sibling; + sibling = textNode.previousSibling; + } + sibling = textNode.nextSibling; + while (sibling && sibling.nodeType === Node.TEXT_NODE) { + sibling.textContent =+ textNode.textContent; + textNode.remove(); + textNode = sibling; + sibling = textNode.nextSibling; + } + setSelection(textNode, offset); + const textHasOpeningTick = /`.*`/.test(textNode.textContent); + if (textHasOpeningTick) { + this.historyStep(); + if (offset !== textNode.textContent.length) { + splitTextNode(textNode, offset); + textNode = textNode.previousSibling; + } + const openingTickOffset = textNode.textContent.substring(0, textNode.textContent.length - 1).lastIndexOf('`'); + if (openingTickOffset) { + splitTextNode(textNode, openingTickOffset); + } + // Remove ticks. + textNode.textContent = textNode.textContent.substring(1, textNode.textContent.length - 1); + // Insert code element. + const codeElement = this.document.createElement('code'); + codeElement.classList.add('o_inline_code'); + textNode.before(codeElement); + codeElement.append(textNode); + 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); + } + } this.historyStep(); } else if (ev.inputType === 'insertLineBreak') { this._compositionStep(); diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/style.scss b/addons/web_editor/static/src/js/editor/odoo-editor/src/style.scss index fa5e9ffec7a..ba00266b9da 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/style.scss +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/style.scss @@ -429,3 +429,10 @@ i.oe-powerbox-commandImg { z-index: 1; } } +code.o_inline_code { + background-color: #c5c5c5; + padding: 2px; + margin: 2px; + color: black; + font-size: inherit; +} 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 b77b4e0d6d7..c0e56630bfa 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 @@ -5122,4 +5122,58 @@ X[] }); }); }); + + describe('markdown', () => { + describe('inline code', () => { + it('should convert text into inline code (start)', async () => { + await testEditor(BasicEditor, { + contentBefore: '
`ab[]cd
', + stepFunction: async editor => insertText(editor, '`'), + contentAfter: '\u200Bab\u200B[]cd
ab`cd[]ef
', + stepFunction: async editor => insertText(editor, '`'), + contentAfter: 'abcd\u200B[]ef
ab`cd[]
', + stepFunction: async editor => insertText(editor, '`'), + contentAfter: 'abcd\u200B[]
a`b`cd[]e`f
', + stepFunction: async editor => insertText(editor, '`'), + contentAfter: 'a`bcd\u200B[]e`f
ab`cde[]fg
', + stepFunction: async editor => insertText(editor, '`'), + contentAfter: 'ab`cde`[]fg
', + }); + }); + it('should not convert text into inline code when interrupted by linebreak', async () => { + await testEditor(BasicEditor, { + contentBefore: 'ab`c
d[]ef
ab`c
d`[]ef
ab`cd[]ef
ab`cd`[]ef