Skip to content

Commit aa159a6

Browse files
fix: Backspace/enter behaviour in empty block with children (#2451)
* Fixed child blocks when hitting backspace/enter in empty block * Added comments
1 parent f728f6a commit aa159a6

8 files changed

Lines changed: 710 additions & 12 deletions

packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,14 @@ export const KeyboardShortcutsExtension = Extension.create<{
184184

185185
let chainedCommands = chain();
186186

187+
// Moves the children of the current block to the previous one.
188+
if (blockInfo.childContainer) {
189+
chainedCommands.insertContentAt(
190+
blockInfo.bnBlock.afterPos,
191+
blockInfo.childContainer?.node.content,
192+
);
193+
}
194+
187195
if (
188196
prevBlockInfo.blockContent.node.type.spec.content ===
189197
"tableRow+"
@@ -209,8 +217,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
209217
);
210218
} else {
211219
const blockContentStartPos =
212-
prevBlockInfo.blockContent.afterPos -
213-
prevBlockInfo.blockContent.node.nodeSize;
220+
prevBlockInfo.blockContent.afterPos - 1;
214221

215222
chainedCommands =
216223
chainedCommands.setTextSelection(blockContentStartPos);
@@ -413,7 +420,7 @@ export const KeyboardShortcutsExtension = Extension.create<{
413420
// Creates a new block and moves the selection to it if the current one is empty, while the selection is also
414421
// empty & at the start of the block.
415422
() =>
416-
commands.command(({ state, dispatch }) => {
423+
commands.command(({ state, dispatch, tr }) => {
417424
const blockInfo = getBlockInfoFromSelection(state);
418425
if (!blockInfo.isBlockContainer) {
419426
return false;
@@ -431,15 +438,34 @@ export const KeyboardShortcutsExtension = Extension.create<{
431438
const newBlockContentPos = newBlockInsertionPos + 2;
432439

433440
if (dispatch) {
434-
const newBlock =
435-
state.schema.nodes["blockContainer"].createAndFill()!;
436-
437-
state.tr
438-
.insert(newBlockInsertionPos, newBlock)
441+
// Creates a new block with the children of the current block,
442+
// if it has any.
443+
const newBlock = state.schema.nodes[
444+
"blockContainer"
445+
].createAndFill(
446+
undefined,
447+
[
448+
state.schema.nodes["paragraph"].createAndFill() ||
449+
undefined,
450+
blockInfo.childContainer?.node,
451+
].filter((node) => node !== undefined),
452+
)!;
453+
454+
// Inserts the new block and moves the selection to it.
455+
tr.insert(newBlockInsertionPos, newBlock)
456+
.setSelection(
457+
new TextSelection(tr.doc.resolve(newBlockContentPos)),
458+
)
439459
.scrollIntoView();
440-
state.tr.setSelection(
441-
new TextSelection(state.doc.resolve(newBlockContentPos)),
442-
);
460+
461+
// Deletes old block's children, as they have been moved to
462+
// the new one.
463+
if (blockInfo.childContainer) {
464+
tr.delete(
465+
blockInfo.childContainer.beforePos,
466+
blockInfo.childContainer.afterPos,
467+
);
468+
}
443469
}
444470

445471
return true;

tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,32 @@ test.describe("Check Keyboard Handlers' Behaviour", () => {
6767
await page.keyboard.press("ArrowUp");
6868
await page.keyboard.press("ArrowUp");
6969
await page.keyboard.press("ArrowUp");
70-
await page.keyboard.press("ArrowUp");
7170
await page.keyboard.press("Control+ArrowLeft");
7271
await page.keyboard.press("ArrowRight");
7372
await page.keyboard.press("Enter");
7473

7574
await compareDocToSnapshot(page, "enterPreservesNestedBlocks.json");
7675
});
76+
test("Check Enter preserves nested blocks for empty block", async ({
77+
page,
78+
}) => {
79+
await focusOnEditor(page);
80+
await page.keyboard.press("#");
81+
await page.keyboard.press(" ");
82+
await page.keyboard.press("ArrowDown", { delay: 10 });
83+
await page.keyboard.press("Tab");
84+
await insertHeading(page, 2);
85+
await page.keyboard.press("Tab");
86+
await insertHeading(page, 3);
87+
88+
await page.waitForTimeout(500);
89+
await page.keyboard.press("ArrowUp");
90+
await page.keyboard.press("ArrowUp");
91+
await page.keyboard.press("ArrowUp");
92+
await page.keyboard.press("Enter");
93+
94+
await compareDocToSnapshot(page, "enterPreservesNestedBlocksEmpty.json");
95+
});
7796
test("Check Backspace at the start of a block", async ({ page }) => {
7897
await focusOnEditor(page);
7998
await insertHeading(page, 1);
@@ -129,6 +148,29 @@ test.describe("Check Keyboard Handlers' Behaviour", () => {
129148

130149
await compareDocToSnapshot(page, "backspacePreservesNestedBlocks.json");
131150
});
151+
test("Check Backspace preserves nested blocks for empty block", async ({
152+
page,
153+
}) => {
154+
await focusOnEditor(page);
155+
await insertParagraph(page);
156+
await page.keyboard.press("Enter", { delay: 10 });
157+
await page.keyboard.press("Tab");
158+
await insertParagraph(page);
159+
await page.keyboard.press("Tab");
160+
await page.keyboard.press("Tab");
161+
await insertParagraph(page);
162+
163+
for (let i = 0; i < 3; i++) {
164+
await page.keyboard.press("ArrowUp");
165+
}
166+
167+
await page.keyboard.press("Backspace");
168+
169+
await compareDocToSnapshot(
170+
page,
171+
"backspacePreservesNestedBlocksEmpty.json",
172+
);
173+
});
132174
test("Check heading 1 shortcut", async ({ page }) => {
133175
await focusOnEditor(page);
134176
await page.keyboard.type("Paragraph");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{
2+
"type": "doc",
3+
"content": [
4+
{
5+
"type": "blockGroup",
6+
"content": [
7+
{
8+
"type": "blockContainer",
9+
"attrs": {
10+
"id": "0"
11+
},
12+
"content": [
13+
{
14+
"type": "paragraph",
15+
"attrs": {
16+
"backgroundColor": "default",
17+
"textColor": "default",
18+
"textAlignment": "left"
19+
},
20+
"content": [
21+
{
22+
"type": "text",
23+
"text": "Paragraph"
24+
}
25+
]
26+
}
27+
]
28+
},
29+
{
30+
"type": "blockContainer",
31+
"attrs": {
32+
"id": "2"
33+
},
34+
"content": [
35+
{
36+
"type": "paragraph",
37+
"attrs": {
38+
"backgroundColor": "default",
39+
"textColor": "default",
40+
"textAlignment": "left"
41+
},
42+
"content": [
43+
{
44+
"type": "text",
45+
"text": "Paragraph"
46+
}
47+
]
48+
},
49+
{
50+
"type": "blockGroup",
51+
"content": [
52+
{
53+
"type": "blockContainer",
54+
"attrs": {
55+
"id": "3"
56+
},
57+
"content": [
58+
{
59+
"type": "paragraph",
60+
"attrs": {
61+
"backgroundColor": "default",
62+
"textColor": "default",
63+
"textAlignment": "left"
64+
},
65+
"content": [
66+
{
67+
"type": "text",
68+
"text": "Paragraph"
69+
}
70+
]
71+
}
72+
]
73+
}
74+
]
75+
}
76+
]
77+
},
78+
{
79+
"type": "blockContainer",
80+
"attrs": {
81+
"id": "4"
82+
},
83+
"content": [
84+
{
85+
"type": "paragraph",
86+
"attrs": {
87+
"backgroundColor": "default",
88+
"textColor": "default",
89+
"textAlignment": "left"
90+
}
91+
}
92+
]
93+
}
94+
]
95+
}
96+
]
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{
2+
"type": "doc",
3+
"content": [
4+
{
5+
"type": "blockGroup",
6+
"content": [
7+
{
8+
"type": "blockContainer",
9+
"attrs": {
10+
"id": "0"
11+
},
12+
"content": [
13+
{
14+
"type": "paragraph",
15+
"attrs": {
16+
"backgroundColor": "default",
17+
"textColor": "default",
18+
"textAlignment": "left"
19+
},
20+
"content": [
21+
{
22+
"type": "text",
23+
"text": "Paragraph"
24+
}
25+
]
26+
}
27+
]
28+
},
29+
{
30+
"type": "blockContainer",
31+
"attrs": {
32+
"id": "2"
33+
},
34+
"content": [
35+
{
36+
"type": "paragraph",
37+
"attrs": {
38+
"backgroundColor": "default",
39+
"textColor": "default",
40+
"textAlignment": "left"
41+
},
42+
"content": [
43+
{
44+
"type": "text",
45+
"text": "Paragraph"
46+
}
47+
]
48+
},
49+
{
50+
"type": "blockGroup",
51+
"content": [
52+
{
53+
"type": "blockContainer",
54+
"attrs": {
55+
"id": "3"
56+
},
57+
"content": [
58+
{
59+
"type": "paragraph",
60+
"attrs": {
61+
"backgroundColor": "default",
62+
"textColor": "default",
63+
"textAlignment": "left"
64+
},
65+
"content": [
66+
{
67+
"type": "text",
68+
"text": "Paragraph"
69+
}
70+
]
71+
}
72+
]
73+
}
74+
]
75+
}
76+
]
77+
},
78+
{
79+
"type": "blockContainer",
80+
"attrs": {
81+
"id": "4"
82+
},
83+
"content": [
84+
{
85+
"type": "paragraph",
86+
"attrs": {
87+
"backgroundColor": "default",
88+
"textColor": "default",
89+
"textAlignment": "left"
90+
}
91+
}
92+
]
93+
}
94+
]
95+
}
96+
]
97+
}

0 commit comments

Comments
 (0)