[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:
@@ -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>`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user