[ADD] web_editor: multiline tab and shift+tab

This adds the possibility to remove leading tabulations with shift+tab,
as well as supporting tabulation and untabulation over multiple blocks.

task-2792099

closes odoo/odoo#100386

Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
Antoine Guenet
2022-12-09 13:03:33 +01:00
parent 55339cd6a7
commit 7c19e7389e
2 changed files with 445 additions and 55 deletions
@@ -66,7 +66,8 @@ import {
hasTableSelection,
pxToFloat,
parseHTML,
splitTextNode
splitTextNode,
isEditorTab,
} from './utils/utils.js';
import { editorCommands } from './commands/commands.js';
import { Powerbox } from './powerbox/Powerbox.js';
@@ -3350,15 +3351,83 @@ export class OdooEditor extends EventTarget {
}
} else if (ev.key === 'Tab') {
// Tab
const tabHtml = '<span class="oe-tabs" contenteditable="false">\u0009</span>\u200B';
const sel = this.document.getSelection();
const closestTag = (closestElement(sel.anchorNode, 'li, table') || {}).tagName;
if (closestTag === 'LI') {
this._applyCommand('indentList', ev.shiftKey ? 'outdent' : 'indent');
} else if (closestTag === 'TABLE') {
if (closestElement(sel.anchorNode, 'table')) {
this._onTabulationInTable(ev);
} else if (!ev.shiftKey) {
this.execCommand('insert', parseHTML('<span class="oe-tabs" contenteditable="false">\u0009</span>\u200B'));
} else if (!ev.shiftKey && sel.isCollapsed && !closestElement(sel.anchorNode, 'li')) {
// Indent text (collapsed selection).
this.execCommand('insert', parseHTML(tabHtml));
} else {
// Indent/outdent selection.
// Split traversed nodes into list items and the rest.
const listItems = new Set();
const nonListItems = new Set();
for (const node of getTraversedNodes(this.editable)) {
const closestLi = closestElement(node, 'li');
const target = closestLi || node;
if (!(target.querySelector && target.querySelector('li'))) {
if (closestLi) {
listItems.add(closestLi);
} else {
nonListItems.add(node);
}
}
}
const restore = preserveCursor(this.document);
// Indent/outdent list items.
for (const listItem of listItems) {
if (ev.shiftKey) {
listItem.oShiftTab(0);
} else {
listItem.oTab(0);
}
}
// Indent/outdent the rest.
if (ev.shiftKey) {
const editorTabs = new Set(
[...nonListItems].map(node => {
const block = closestBlock(node);
if (block && isEditorTab(block.firstElementChild)) {
return block.firstElementChild;
}
}).filter(node => (
// Filter out tabs preceded by visible text.
node && !getAdjacentPreviousSiblings(node).some(sibling => (
sibling.nodeType === Node.TEXT_NODE && !/^[\u200B\s]*$/.test(sibling.textContent)
))
)));
for (const tab of editorTabs) {
let { anchorNode, anchorOffset, focusNode, focusOffset } = sel;
const updateAnchor = anchorNode === tab.nextSibling;
const updateFocus = focusNode === tab.nextSibling;
let zwsRemoved = 0;
while (tab.nextSibling && tab.nextSibling.nodeType === Node.TEXT_NODE && tab.nextSibling.textContent.startsWith('\u200B')) {
splitTextNode(tab.nextSibling, 1, DIRECTIONS.LEFT);
tab.nextSibling.remove();
zwsRemoved++;
}
if (updateAnchor || updateFocus) {
setSelection(
updateAnchor ? tab.nextSibling : anchorNode,
updateAnchor ? Math.max(0, anchorOffset - zwsRemoved) : anchorOffset,
updateFocus ? tab.nextSibling : focusNode,
updateFocus ? Math.max(0, focusOffset - zwsRemoved) : focusOffset
);
}
tab.remove();
};
} else {
const tab = parseHTML(tabHtml);
for (const block of new Set([...nonListItems].map(node => closestBlock(node)).filter(node => node))) {
block.prepend(tab.cloneNode(true));
}
restore();
}
this.historyStep();
}
ev.preventDefault();
ev.stopPropagation();
@@ -3577,8 +3646,7 @@ export class OdooEditor extends EventTarget {
}
/**
* initialise the provided element to be ready for edition
*
* Initialize the provided element to be ready for edition.
*/
initElementForEdition(element = this.editable) {
// Detect if the editable base element contain orphan inline nodes. If
@@ -7,43 +7,48 @@ import {
testEditor,
} from '../utils.js';
const TAB_WIDTH = 40;
describe('Tabs', () => {
const oeTab = (size = '40px', contenteditable = true) => {
return `<span class="oe-tabs"${contenteditable ? '' : ' contenteditable="false"'} style="width: ${size};">\u0009</span>\u200B`
};
describe('insert Tabulation', () => {
const oeTab = (size, contenteditable = true) => (
`<span class="oe-tabs"` +
(contenteditable ? '' : ' contenteditable="false"') +
(size ?` style="width: ${size}px;"` : '') +
`>\u0009</span>\u200B`
);
describe('insert tabulation', () => {
it('should insert a tab character', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a[]b</p>`,
stepFunction: async editor => {
await triggerEvent(editor.editable, 'keydown', { key: 'Tab'});
},
contentAfterEdit: `<p>a${oeTab('32.8906px', false)}[]b</p>`,
contentAfter: `<p>a${oeTab('32.8906px')}[]b</p>`,
contentAfterEdit: `<p>a${oeTab(32.8906, false)}[]b</p>`,
contentAfter: `<p>a${oeTab(32.8906)}[]b</p>`,
});
});
it('should clear selection and insert a tab character', async () => {
it('should keep selection and insert a tab character at the beginning of the paragraph', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a[xxx]b</p>`,
stepFunction: async editor => {
await triggerEvent(editor.editable, 'keydown', { key: 'Tab'});
},
contentAfterEdit: `<p>a${oeTab('32.8906px', false)}[]b</p>`,
contentAfter: `<p>a${oeTab('32.8906px')}[]b</p>`,
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}a[xxx]b</p>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}a[xxx]b</p>`,
});
});
it('should insert two tab character', async () => {
it('should insert two tab characters', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a[]b</p>`,
stepFunction: async editor => {
await triggerEvent(editor.editable, 'keydown', { key: 'Tab'});
await triggerEvent(editor.editable, 'keydown', { key: 'Tab'});
},
contentAfterEdit: `<p>a${oeTab('32.8906px', false)}${oeTab('40px', false)}[]b</p>`,
contentAfter: `<p>a${oeTab('32.8906px')}${oeTab()}[]b</p>`,
contentAfterEdit: `<p>a${oeTab(32.8906, false)}${oeTab(TAB_WIDTH, false)}[]b</p>`,
contentAfter: `<p>a${oeTab(32.8906)}${oeTab(TAB_WIDTH)}[]b</p>`,
});
});
it('should insert two tab character with one char between', async () => {
it('should insert two tab characters with one char between them', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a[]b</p>`,
stepFunction: async editor => {
@@ -51,31 +56,187 @@ describe('Tabs', () => {
await insertText(editor,'a');
await triggerEvent(editor.editable, 'keydown', { key: 'Tab'});
},
contentAfterEdit: `<p>a${oeTab('32.8906px', false)}a${oeTab('32.8906px', false)}[]b</p>`,
contentAfter: `<p>a${oeTab('32.8906px')}a${oeTab('32.8906px')}[]b</p>`,
contentAfterEdit: `<p>a${oeTab(32.8906, false)}a${oeTab(32.8906, false)}[]b</p>`,
contentAfter: `<p>a${oeTab(32.8906)}a${oeTab(32.8906)}[]b</p>`,
});
});
it('should insert tab characters at the beginning of two separate paragraphs', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a[b</p>` +
`<p>c]d</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH, false)}c]d</p>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH)}c]d</p>`,
});
});
it('should insert tab characters at the beginning of two separate indented paragraphs', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}a[b</p>` +
`<p>${oeTab()}c]d</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}c]d</p>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}c]d</p>`,
});
});
it('should insert tab characters at the beginning of two separate paragraphs (one indented, the other not)', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}a[b</p>` +
`<p>c]d</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH, false)}c]d</p>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH)}c]d</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a[b</p>` +
`<p>${oeTab()}c]d</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}c]d</p>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}c]d</p>`,
});
});
it('should insert tab characters at the beginning of two separate paragraphs with tabs in them', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}a[${oeTab()}b${oeTab()}</p>` +
`<p>c${oeTab()}]d${oeTab()}</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[${oeTab(32.8906, false)}b${oeTab(32, false)}</p>` +
`<p>${oeTab(TAB_WIDTH, false)}c${oeTab(32.8906, false)}]d${oeTab(32, false)}</p>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[${oeTab(32.8906)}b${oeTab(32)}</p>` +
`<p>${oeTab(TAB_WIDTH)}c${oeTab(32.8906)}]d${oeTab(32)}</p>`,
});
});
it('should insert tab characters at the beginning of three separate blocks', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>xxx</p>` +
`<p>a[b</p>` +
`<h1>cd</h1>` +
`<blockquote>e]f</blockquote>` +
`<h4>zzz</h4>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>xxx</p>` +
`<p>${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<h1>${oeTab(TAB_WIDTH, false)}cd</h1>` +
`<blockquote>${oeTab(TAB_WIDTH, false)}e]f</blockquote>` +
`<h4>zzz</h4>`,
contentAfter: `<p>xxx</p>` +
`<p>${oeTab(TAB_WIDTH)}a[b</p>` +
`<h1>${oeTab(TAB_WIDTH)}cd</h1>` +
`<blockquote>${oeTab(TAB_WIDTH)}e]f</blockquote>` +
`<h4>zzz</h4>`,
});
});
it('should insert tab characters at the beginning of three separate indented blocks', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}xxx</p>` +
`<p>${oeTab()}a[b</p>` +
`<h1>${oeTab()}cd</h1>` +
`<blockquote>${oeTab()}e]f</blockquote>` +
`<h4>${oeTab()}zzz</h4>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}xxx</p>` +
`<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<h1>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}cd</h1>` +
`<blockquote>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}e]f</blockquote>` +
`<h4>${oeTab(TAB_WIDTH, false)}zzz</h4>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}xxx</p>` +
`<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[b</p>` +
`<h1>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}cd</h1>` +
`<blockquote>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}e]f</blockquote>` +
`<h4>${oeTab(TAB_WIDTH)}zzz</h4>`,
});
});
it('should insert tab characters at the beginning of three separate blocks of mixed indentation', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>xxx</p>` +
`<p>${oeTab()}${oeTab()}a[b</p>` +
`<h1>${oeTab()}cd</h1>` +
`<blockquote>e]f</blockquote>` +
`<h4>zzz</h4>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>xxx</p>` +
`<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<h1>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}cd</h1>` +
`<blockquote>${oeTab(TAB_WIDTH, false)}e]f</blockquote>` +
`<h4>zzz</h4>`,
contentAfter: `<p>xxx</p>` +
`<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[b</p>` +
`<h1>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}cd</h1>` +
`<blockquote>${oeTab(TAB_WIDTH)}e]f</blockquote>` +
`<h4>zzz</h4>`,
});
});
it('should insert tab characters at the beginning of three separate blocks with tabs in them', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>xxx</p>` +
`<p>${oeTab()}a[${oeTab()}b${oeTab()}</p>` +
`<h1>c${oeTab()}d${oeTab()}</h1>` +
`<blockquote>e${oeTab()}]f</blockquote>` +
`<h4>zzz</h4>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>xxx</p>` +
`<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[${oeTab(32.8906, false)}b${oeTab(32, false)}</p>` +
`<h1>${oeTab(TAB_WIDTH, false)}c${oeTab(25.7969, false)}d${oeTab(22.2031, false)}</h1>` +
`<blockquote>${oeTab(TAB_WIDTH, false)}e${oeTab(32.8906, false)}]f</blockquote>` +
`<h4>zzz</h4>`,
contentAfter: `<p>xxx</p>` +
`<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[${oeTab(32.8906)}b${oeTab(32)}</p>` +
`<h1>${oeTab(TAB_WIDTH)}c${oeTab(25.7969)}d${oeTab(22.2031)}</h1>` +
`<blockquote>${oeTab(TAB_WIDTH)}e${oeTab(32.8906)}]f</blockquote>` +
`<h4>zzz</h4>`,
});
});
it('should insert tab characters in blocks and indent lists', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}a[${oeTab()}b${oeTab()}</p>` +
`<ul>` +
`<li>c${oeTab()}d${oeTab()}</li>` +
`<li class="oe-nested"><ul><li>${oeTab()}e${oeTab()}</li></ul></li>` +
`</ul>` +
`<blockquote>f${oeTab()}]g</blockquote>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab'}),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}${oeTab(TAB_WIDTH, false)}a[${oeTab(32.8906, false)}b${oeTab(32, false)}</p>` +
`<ul>` +
`<li class="oe-nested"><ul><li>c${oeTab(32.8906, false)}d${oeTab(32, false)}</li>` +
`<li class="oe-nested"><ul><li>${oeTab(TAB_WIDTH, false)}e${oeTab(32.8906, false)}</li></ul></li></ul></li>` +
`</ul>` +
`<blockquote>${oeTab(TAB_WIDTH, false)}f${oeTab(34.6719, false)}]g</blockquote>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}${oeTab(TAB_WIDTH)}a[${oeTab(32.8906)}b${oeTab(32)}</p>` +
`<ul>` +
`<li class="oe-nested"><ul><li>c${oeTab(32.8906)}d${oeTab(32)}</li>` +
`<li class="oe-nested"><ul><li>${oeTab(TAB_WIDTH)}e${oeTab(32.8906)}</li></ul></li></ul></li>` +
`</ul>` +
`<blockquote>${oeTab(TAB_WIDTH)}f${oeTab(34.6719)}]g</blockquote>`,
});
});
});
describe('delete backward Tabulation', () => {
describe('delete backward tabulation', () => {
it('should remove one tab character', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}[]b</p>`,
contentBefore: `<p>a${oeTab(32.8906)}[]b</p>`,
stepFunction: async editor => {
await deleteBackward(editor);
},
contentAfter: `<p>a[]b</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}[]${oeTab()}b</p>`,
contentBefore: `<p>a${oeTab(32.8906)}[]${oeTab()}b</p>`,
stepFunction: async editor => {
await deleteBackward(editor);
},
contentAfter: `<p>a[]${oeTab('32.8906px')}b</p>`,
contentAfter: `<p>a[]${oeTab(32.8906)}b</p>`,
});
});
it('should remove two tab character', async () => {
it('should remove two tab characters', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}${oeTab()}[]b</p>`,
contentBefore: `<p>a${oeTab(32.8906)}${oeTab()}[]b</p>`,
stepFunction: async editor => {
await deleteBackward(editor);
await deleteBackward(editor);
@@ -83,17 +244,17 @@ describe('Tabs', () => {
contentAfter: `<p>a[]b</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}${oeTab()}[]${oeTab()}b</p>`,
contentBefore: `<p>a${oeTab(32.8906)}${oeTab()}[]${oeTab()}b</p>`,
stepFunction: async editor => {
await deleteBackward(editor);
await deleteBackward(editor);
},
contentAfter: `<p>a[]${oeTab('32.8906px')}b</p>`,
contentAfter: `<p>a[]${oeTab(32.8906)}b</p>`,
});
});
it('should remove three tab character', async () => {
it('should remove three tab characters', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}${oeTab()}${oeTab()}[]b</p>`,
contentBefore: `<p>a${oeTab(32.8906)}${oeTab()}${oeTab()}[]b</p>`,
stepFunction: async editor => {
await deleteBackward(editor);
await deleteBackward(editor);
@@ -103,33 +264,33 @@ describe('Tabs', () => {
});
});
});
describe('delete forward Tabulation', () => {
describe('delete forward tabulation', () => {
it('should remove one tab character', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a[]${oeTab('32.8906px')}b1</p>`,
contentBefore: `<p>a[]${oeTab(32.8906)}b1</p>`,
stepFunction: async editor => {
await deleteForward(editor);
},
contentAfter: `<p>a[]b1</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}[]${oeTab()}b2</p>`,
contentBefore: `<p>a${oeTab(32.8906)}[]${oeTab()}b2</p>`,
stepFunction: async editor => {
await deleteForward(editor);
},
contentAfter: `<p>a${oeTab('32.8906px')}[]b2</p>`,
contentAfter: `<p>a${oeTab(32.8906)}[]b2</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a[]${oeTab('32.8906px')}${oeTab()}b3</p>`,
contentBefore: `<p>a[]${oeTab(32.8906)}${oeTab()}b3</p>`,
stepFunction: async editor => {
await deleteForward(editor);
},
contentAfter: `<p>a[]${oeTab('32.8906px')}b3</p>`,
contentAfter: `<p>a[]${oeTab(32.8906)}b3</p>`,
});
});
it('should remove two tab character', async () => {
it('should remove two tab characters', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a[]${oeTab('32.8906px')}${oeTab()}b1</p>`,
contentBefore: `<p>a[]${oeTab(32.8906)}${oeTab()}b1</p>`,
stepFunction: async editor => {
await deleteForward(editor);
await deleteForward(editor);
@@ -137,25 +298,25 @@ describe('Tabs', () => {
contentAfter: `<p>a[]b1</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a[]${oeTab('32.8906px')}${oeTab()}${oeTab()}b2</p>`,
contentBefore: `<p>a[]${oeTab(32.8906)}${oeTab()}${oeTab()}b2</p>`,
stepFunction: async editor => {
await deleteForward(editor);
await deleteForward(editor);
},
contentAfter: `<p>a[]${oeTab('32.8906px')}b2</p>`,
contentAfter: `<p>a[]${oeTab(32.8906)}b2</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}[]${oeTab()}${oeTab()}b3</p>`,
contentBefore: `<p>a${oeTab(32.8906)}[]${oeTab()}${oeTab()}b3</p>`,
stepFunction: async editor => {
await deleteForward(editor);
await deleteForward(editor);
},
contentAfter: `<p>a${oeTab('32.8906px')}[]b3</p>`,
contentAfter: `<p>a${oeTab(32.8906)}[]b3</p>`,
});
});
it('should remove three tab character', async () => {
it('should remove three tab characters', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a[]${oeTab('32.8906px')}${oeTab()}${oeTab()}b</p>`,
contentBefore: `<p>a[]${oeTab(32.8906)}${oeTab()}${oeTab()}b</p>`,
stepFunction: async editor => {
await deleteForward(editor);
await deleteForward(editor);
@@ -165,10 +326,10 @@ describe('Tabs', () => {
});
});
});
describe('delete mixxed Tabulation', () => {
it('should remove all tab character', async () => {
describe('delete mixed tabulation', () => {
it('should remove all tab characters', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}[]${oeTab()}b1</p>`,
contentBefore: `<p>a${oeTab(32.8906)}[]${oeTab()}b1</p>`,
stepFunction: async editor => {
await deleteForward(editor);
await deleteBackward(editor);
@@ -176,7 +337,7 @@ describe('Tabs', () => {
contentAfter: `<p>a[]b1</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}[]${oeTab()}b2</p>`,
contentBefore: `<p>a${oeTab(32.8906)}[]${oeTab()}b2</p>`,
stepFunction: async editor => {
await deleteBackward(editor);
await deleteForward(editor);
@@ -184,7 +345,7 @@ describe('Tabs', () => {
contentAfter: `<p>a[]b2</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}${oeTab()}[]${oeTab()}b3</p>`,
contentBefore: `<p>a${oeTab(32.8906)}${oeTab()}[]${oeTab()}b3</p>`,
stepFunction: async editor => {
await deleteBackward(editor);
await deleteForward(editor);
@@ -193,7 +354,7 @@ describe('Tabs', () => {
contentAfter: `<p>a[]b3</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab('32.8906px')}[]${oeTab()}${oeTab()}b4</p>`,
contentBefore: `<p>a${oeTab(32.8906)}[]${oeTab()}${oeTab()}b4</p>`,
stepFunction: async editor => {
await deleteForward(editor);
await deleteBackward(editor);
@@ -203,4 +364,165 @@ describe('Tabs', () => {
});
});
});
describe('remove tabulation with shift+tab', () => {
it('should not remove a non-leading tab character', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>a${oeTab()}[]b</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfterEdit: `<p>a${oeTab(32.8906, false)}[]b</p>`,
contentAfter: `<p>a${oeTab(32.8906)}[]b</p>`,
});
});
it('should remove a tab character', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}a[]b</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfter: `<p>a[]b</p>`,
});
});
it('should keep selection and remove a tab character from the beginning of the paragraph', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}a[xxx]b</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfter: `<p>a[xxx]b</p>`,
});
});
it('should remove two tab characters', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}${oeTab()}a[]b</p>`,
stepFunction: async editor => {
await triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true });
await triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true });
},
contentAfter: `<p>a[]b</p>`,
});
});
it('should remove tab characters from the beginning of two separate paragraphs', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}a[b</p>` +
`<p>${oeTab()}c]d</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfter: `<p>a[b</p>` +
`<p>c]d</p>`,
});
});
it('should remove tab characters from the beginning of two separate double-indented paragraphs', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}${oeTab()}a[b</p>` +
`<p>${oeTab()}${oeTab()}c]d</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH, false)}c]d</p>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}a[b</p>` +
`<p>${oeTab(TAB_WIDTH)}c]d</p>`,
});
});
it('should remove tab characters from the beginning of two separate paragraphs of mixed indentations', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}${oeTab()}a[b</p>` +
`<p>${oeTab()}c]d</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<p>c]d</p>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}a[b</p>` +
`<p>c]d</p>`,
});
await testEditor(BasicEditor, {
contentBefore: `<p>a[b</p>` +
`<p>${oeTab()}c]d</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfter: `<p>a[b</p>` +
`<p>c]d</p>`,
});
});
it('should remove tab characters from the beginning of two separate paragraphs with tabs in them', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}a[${oeTab()}b${oeTab()}</p>` +
`<p>c${oeTab()}]d${oeTab()}</p>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfterEdit: `<p>a[${oeTab(32.8906, false)}b${oeTab(32, false)}</p>` +
`<p>c${oeTab(32.8906, false)}]d${oeTab(32, false)}</p>`,
contentAfter: `<p>a[${oeTab(32.8906)}b${oeTab(32)}</p>` +
`<p>c${oeTab(32.8906)}]d${oeTab(32)}</p>`,
});
});
it('should remove tab characters from the beginning of three separate blocks', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>xxx</p>` +
`<p>${oeTab()}a[b</p>` +
`<h1>${oeTab()}cd</h1>` +
`<blockquote>${oeTab()}e]f</blockquote>` +
`<h4>zzz</h4>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfter: `<p>xxx</p>` +
`<p>a[b</p>` +
`<h1>cd</h1>` +
`<blockquote>e]f</blockquote>` +
`<h4>zzz</h4>`,
});
});
it('should remove tab characters from the beginning of three separate blocks of mixed indentation', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>xxx</p>` +
`<p>${oeTab()}${oeTab()}a[b</p>` +
`<h1>${oeTab()}cd</h1>` +
`<blockquote>e]f</blockquote>` +
`<h4>zzz</h4>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfterEdit: `<p>xxx</p>` +
`<p>${oeTab(TAB_WIDTH, false)}a[b</p>` +
`<h1>cd</h1>` +
`<blockquote>e]f</blockquote>` +
`<h4>zzz</h4>`,
contentAfter: `<p>xxx</p>` +
`<p>${oeTab(TAB_WIDTH)}a[b</p>` +
`<h1>cd</h1>` +
`<blockquote>e]f</blockquote>` +
`<h4>zzz</h4>`,
});
});
it('should remove tab characters from the beginning of three separate blocks with tabs in them', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>xxx</p>` +
`<p>${oeTab()}a[${oeTab()}b${oeTab()}</p>` +
`<h1>${oeTab()}c${oeTab()}d${oeTab()}</h1>` +
`<blockquote>${oeTab()}e${oeTab()}]f</blockquote>` +
`<h4>zzz</h4>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfterEdit: `<p>xxx</p>` +
`<p>a[${oeTab(32.8906, false)}b${oeTab(32, false)}</p>` +
`<h1>c${oeTab(25.7969, false)}d${oeTab(22.2031, false)}</h1>` +
`<blockquote>e${oeTab(32.8906, false)}]f</blockquote>` +
`<h4>zzz</h4>`,
contentAfter: `<p>xxx</p>` +
`<p>a[${oeTab(32.8906)}b${oeTab(32)}</p>` +
`<h1>c${oeTab(25.7969)}d${oeTab(22.2031)}</h1>` +
`<blockquote>e${oeTab(32.8906)}]f</blockquote>` +
`<h4>zzz</h4>`,
});
});
it('should remove tab characters from the beginning of blocks and outdent lists', async () => {
await testEditor(BasicEditor, {
contentBefore: `<p>${oeTab()}${oeTab()}a[${oeTab()}b${oeTab()}</p>` +
`<ul>` +
`<li class="oe-nested"><ul><li>c${oeTab()}d${oeTab()}</li>` +
`<li class="oe-nested"><ul><li>${oeTab()}e${oeTab()}</li></ul></li></ul></li>` +
`</ul>` +
`<blockquote>${oeTab()}f${oeTab()}]g</blockquote>`,
stepFunction: editor => triggerEvent(editor.editable, 'keydown', { key: 'Tab', shiftKey: true }),
contentAfterEdit: `<p>${oeTab(TAB_WIDTH, false)}a[${oeTab(32.8906, false)}b${oeTab(32, false)}</p>` +
`<ul>` +
`<li>c${oeTab(32.8906, false)}d${oeTab(32, false)}</li>` +
`<li class="oe-nested"><ul><li>${oeTab(TAB_WIDTH, false)}e${oeTab(32.8906, false)}</li></ul></li>` +
`</ul>` +
`<blockquote>f${oeTab(34.6719, false)}]g</blockquote>`,
contentAfter: `<p>${oeTab(TAB_WIDTH)}a[${oeTab(32.8906)}b${oeTab(32)}</p>` +
`<ul>` +
`<li>c${oeTab(32.8906)}d${oeTab(32)}</li>` +
`<li class="oe-nested"><ul><li>${oeTab(TAB_WIDTH)}e${oeTab(32.8906)}</li></ul></li>` +
`</ul>` +
`<blockquote>f${oeTab(34.6719)}]g</blockquote>`,
});
});
});
});