Fix indenting on backspace for lists and paragraphs (#1225)

* fix indenting on backspace for lists and paragraphs

* switch to overriding getIndent
This commit is contained in:
Acy Watson
2022-02-07 08:15:48 -08:00
committed by acywatson
parent a095530067
commit aa2d0777e2
4 changed files with 103 additions and 55 deletions

View File

@ -38,6 +38,20 @@ async function toggleNumberedList(page) {
await click(page, '.dropdown .icon.numbered-list');
}
async function clickIndentButton(page, times = 1) {
await waitForSelector(page, 'button .indent');
for (let i = 0; i < times; i++) {
await click(page, 'button .indent');
}
}
async function clickOutdentButton(page, times = 1) {
await waitForSelector(page, 'button .outdent');
for (let i = 0; i < times; i++) {
await click(page, 'button .outdent');
}
}
describe('Nested List', () => {
initializeE2E((e2e) => {
it(`Can toggle an empty list on/off`, async () => {
@ -76,24 +90,14 @@ describe('Nested List', () => {
}
await focusEditor(page);
await waitForSelector(page, '.block-controls');
await click(page, '.block-controls');
await waitForSelector(page, '.dropdown .icon.bullet-list');
await click(page, '.dropdown .icon.bullet-list');
await toggleBulletList(page);
await assertHTML(
page,
'<ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k" value="1"><br></li></ul>',
);
// Should allow indenting an empty list item
await waitForSelector(page, 'button .indent');
await click(page, 'button .indent');
await click(page, 'button .indent');
await clickIndentButton(page, 2);
await assertHTML(
page,
@ -126,20 +130,14 @@ describe('Nested List', () => {
await selectAll(page);
await waitForSelector(page, 'button .indent');
await click(page, 'button .indent');
await click(page, 'button .indent');
await click(page, 'button .indent');
await clickIndentButton(page, 3);
await assertHTML(
page,
'<ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp" value="1"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp" value="1"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp" value="1"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr" value="1"><span data-lexical-text="true">Hello</span></li><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr" value="2"><span data-lexical-text="true">from</span></li><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr" value="3"><span data-lexical-text="true">the</span></li><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr" value="4"><span data-lexical-text="true">other</span></li><li class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr" value="5"><span data-lexical-text="true">side</span></li></ul></li></ul></li></ul></li></ul',
);
await waitForSelector(page, 'button .outdent');
await click(page, 'button .outdent');
await click(page, 'button .outdent');
await click(page, 'button .outdent');
await clickOutdentButton(page, 3);
await assertHTML(
page,
@ -147,6 +145,41 @@ describe('Nested List', () => {
);
});
it('Should outdent if indented when the backspace key is pressed', async () => {
const {isRichText, page} = e2e;
if (!isRichText) {
return;
}
await focusEditor(page);
await toggleBulletList(page);
await page.keyboard.type('Hello');
await page.keyboard.press('Enter');
await clickIndentButton(page, 3);
await assertHTML(
page,
'<ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">Hello</span></li><li value="2" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k"><br></li></ul></li></ul></li></ul></li></ul>',
);
await page.keyboard.press('Backspace');
await assertHTML(
page,
'<ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">Hello</span></li><li value="2" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k"><br></li></ul></li></ul></li></ul>',
);
await page.keyboard.press('Backspace');
await assertHTML(
page,
'<ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">Hello</span></li><li value="2" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k"><br></li></ul></li></ul>',
);
});
it(`Can indent/outdent mutliple list nodes in a list with multiple levels of indentation`, async () => {
const {isRichText, page} = e2e;
@ -156,13 +189,7 @@ describe('Nested List', () => {
await focusEditor(page);
await waitForSelector(page, '.block-controls');
await click(page, '.block-controls');
await waitForSelector(page, '.dropdown .icon.bullet-list');
await click(page, '.dropdown .icon.bullet-list');
await toggleBulletList(page);
await assertHTML(
page,
@ -173,8 +200,7 @@ describe('Nested List', () => {
await page.keyboard.press('Enter');
await page.keyboard.type('from');
await waitForSelector(page, 'button .indent');
await click(page, 'button .indent');
await clickIndentButton(page);
// - Hello
// - from
@ -185,20 +211,14 @@ describe('Nested List', () => {
await selectAll(page);
await waitForSelector(page, 'button .indent');
await click(page, 'button .indent');
await click(page, 'button .indent');
await click(page, 'button .indent');
await clickIndentButton(page, 3);
await assertHTML(
page,
'<ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">Hello</span></li><li value="2" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">from</span></li></ul></li></ul></li></ul></li></ul></li></ul>',
);
await waitForSelector(page, 'button .outdent');
await click(page, 'button .outdent');
await click(page, 'button .outdent');
await click(page, 'button .outdent');
await clickOutdentButton(page, 3);
await assertHTML(
page,
@ -214,7 +234,7 @@ describe('Nested List', () => {
await page.keyboard.press('Enter');
await page.keyboard.type('side');
await click(page, 'button .outdent');
await clickOutdentButton(page);
// - Hello
// - from
@ -228,14 +248,14 @@ describe('Nested List', () => {
await selectAll(page);
await click(page, 'button .indent');
await clickIndentButton(page);
await assertHTML(
page,
'<ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">Hello</span></li><li value="2" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__nestedListItem a75w6hnp"><ul class="PlaygroundEditorTheme__ul srn514ro oxkhqvkx rl78xhln nch0832m m8h3af8h l7ghb35v kjdc1dyq kmwttqpk i2mu9gw5"><li value="1" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">from</span></li><li value="2" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">the</span></li><li value="3" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">other</span></li></ul></li><li value="2" class="PlaygroundEditorTheme__listItem th51lws0 r26s8xbz mfn553m3 gug11x0k PlaygroundEditorTheme__ltr gkum2dnh" dir="ltr"><span data-lexical-text="true">side</span></li></ul></li></ul>',
);
await click(page, 'button .outdent');
await clickOutdentButton(page);
await assertHTML(
page,
@ -307,10 +327,7 @@ describe('Nested List', () => {
await toggleBulletList(page);
await waitForSelector(page, 'button .indent');
await click(page, 'button .indent');
await click(page, 'button .indent');
await click(page, 'button .indent');
await clickIndentButton(page, 3);
await assertHTML(
page,
@ -591,16 +608,16 @@ describe('Nested List', () => {
await page.keyboard.type('Hello');
await page.keyboard.press('Enter');
await page.keyboard.type('from');
await click(page, 'button .indent');
await clickIndentButton(page);
await page.keyboard.press('Enter');
await page.keyboard.type('the');
await click(page, 'button .indent');
await clickIndentButton(page);
await page.keyboard.press('Enter');
await page.keyboard.type('other');
await click(page, 'button .outdent');
await clickOutdentButton(page);
await page.keyboard.press('Enter');
await page.keyboard.type('side');
await click(page, 'button .outdent');
await clickOutdentButton(page);
await assertHTML(
page,
@ -638,16 +655,17 @@ describe('Nested List', () => {
await page.keyboard.type('Hello');
await page.keyboard.press('Enter');
await page.keyboard.type('from');
await click(page, 'button .indent');
await clickIndentButton(page);
await page.keyboard.press('Enter');
await page.keyboard.type('the');
await click(page, 'button .indent');
await clickIndentButton(page);
await page.keyboard.press('Enter');
await page.keyboard.type('other');
await click(page, 'button .outdent');
await clickOutdentButton(page);
await page.keyboard.press('Enter');
await page.keyboard.type('side');
await click(page, 'button .outdent');
await clickOutdentButton(page);
await assertHTML(
page,

View File

@ -220,6 +220,16 @@ export function useRichTextSetup(editor: LexicalEditor, init: boolean): void {
case 'keyBackspace': {
const event: KeyboardEvent = payload;
event.preventDefault();
const {anchor} = selection;
if (selection.isCollapsed() && anchor.offset === 0) {
const element =
anchor.type === 'element'
? anchor.getNode()
: anchor.getNode().getParentOrThrow();
if (element.getIndent() > 0) {
return editor.execCommand('outdentContent');
}
}
return editor.execCommand('deleteCharacter', true);
}
case 'keyDelete': {

View File

@ -202,9 +202,9 @@ export class ListItemNode extends ElementNode {
children.forEach((child) => paragraph.append(child));
const listNode = this.getParentOrThrow();
const listNodeParent = listNode.getParentOrThrow();
const isNested = $isListItemNode(listNodeParent);
const isIndented = $isListItemNode(listNodeParent);
if (listNode.getChildrenSize() === 1) {
if (isNested) {
if (isIndented) {
// if the list node is nested, we just want to remove it,
// effectively unindenting it.
listNode.remove();
@ -230,6 +230,23 @@ export class ListItemNode extends ElementNode {
return true;
}
getIndent(): number {
// ListItemNode should always have a ListNode for a parent.
let listNodeParent = this.getParentOrThrow().getParentOrThrow();
let indentLevel = 0;
while ($isListItemNode(listNodeParent)) {
listNodeParent = listNodeParent.getParentOrThrow().getParentOrThrow();
indentLevel++;
}
return indentLevel;
}
setIndent(): this {
// TODO move indent/outdent logic to this file and use here.
invariant(true, 'setIndent is not implemented on ListItemNode');
return this;
}
insertBefore(nodeToInsert: LexicalNode): LexicalNode {
const siblings = this.getNextSiblings();
if ($isListItemNode(nodeToInsert)) {

View File

@ -48,5 +48,8 @@
"46": "rootNode.append: Only element or decorator nodes can be appended to the root node",
"47": "getListItemValue: list node is not parent of list item node",
"48": "Should never happen",
"49": "append: attemtping to append self"
"49": "append: attemtping to append self",
"50": "Node %s and selection point do not match.",
"51": "setIndent is not implemented on ListItemNode",
"52": "Expected node %s to to be a ElementNode."
}