mirror of
https://github.com/facebook/lexical.git
synced 2025-08-06 00:21:47 +08:00
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:
@ -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,
|
||||
|
@ -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': {
|
||||
|
@ -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)) {
|
||||
|
@ -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."
|
||||
}
|
||||
|
Reference in New Issue
Block a user