[ADD] web_editor: insert inline code by wrapping it in backticks
This transforms text between two backticks into a code element, when typing the closing backtick. task-2985108 closes odoo/odoo#100218 Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -5122,4 +5122,58 @@ X[]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdown', () => {
|
||||
describe('inline code', () => {
|
||||
it('should convert text into inline code (start)', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<p>`ab[]cd</p>',
|
||||
stepFunction: async editor => insertText(editor, '`'),
|
||||
contentAfter: '<p>\u200B<code class="o_inline_code">ab</code>\u200B[]cd</p>',
|
||||
});
|
||||
});
|
||||
it('should convert text into inline code (middle)', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<p>ab`cd[]ef</p>',
|
||||
stepFunction: async editor => insertText(editor, '`'),
|
||||
contentAfter: '<p>ab<code class="o_inline_code">cd</code>\u200B[]ef</p>',
|
||||
});
|
||||
});
|
||||
it('should convert text into inline code (end)', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<p>ab`cd[]</p>',
|
||||
stepFunction: async editor => insertText(editor, '`'),
|
||||
contentAfter: '<p>ab<code class="o_inline_code">cd</code>\u200B[]</p>',
|
||||
});
|
||||
});
|
||||
it('should convert text into inline code and leave an earlier and a later backtick alone', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<p>a`b`cd[]e`f</p>',
|
||||
stepFunction: async editor => insertText(editor, '`'),
|
||||
contentAfter: '<p>a`b<code class="o_inline_code">cd</code>\u200B[]e`f</p>',
|
||||
});
|
||||
});
|
||||
it('should not convert text into inline code when traversing HTMLElements', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<p>ab`c<strong>d</strong>e[]fg</p>',
|
||||
stepFunction: async editor => insertText(editor, '`'),
|
||||
contentAfter: '<p>ab`c<strong>d</strong>e`[]fg</p>',
|
||||
});
|
||||
});
|
||||
it('should not convert text into inline code when interrupted by linebreak', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<p>ab`c<br>d[]ef</p>',
|
||||
stepFunction: async editor => insertText(editor, '`'),
|
||||
contentAfter: '<p>ab`c<br>d`[]ef</p>',
|
||||
});
|
||||
});
|
||||
it('should not convert text into inline code when inside inline code', async () => {
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<p>a<code class="o_inline_code">b`cd[]e</code>f</p>',
|
||||
stepFunction: async editor => insertText(editor, '`'),
|
||||
contentAfter: '<p>a<code class="o_inline_code">b`cd`[]e</code>f</p>',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user