From c37bb5ebbb6f8838c0ce642121b169a06723cf05 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Fri, 13 Feb 2026 19:55:39 +0100 Subject: [PATCH] Fixed child blocks when hitting backspace/enter in empty block --- .../KeyboardShortcutsExtension.ts | 42 +++++-- .../keyboardhandlers/keyboardhandlers.test.ts | 44 ++++++- ...NestedBlocksEmpty-json-chromium-linux.json | 97 +++++++++++++++ ...sNestedBlocksEmpty-json-firefox-linux.json | 97 +++++++++++++++ ...esNestedBlocksEmpty-json-webkit-linux.json | 97 +++++++++++++++ ...NestedBlocksEmpty-json-chromium-linux.json | 113 ++++++++++++++++++ ...sNestedBlocksEmpty-json-firefox-linux.json | 113 ++++++++++++++++++ ...esNestedBlocksEmpty-json-webkit-linux.json | 113 ++++++++++++++++++ 8 files changed, 704 insertions(+), 12 deletions(-) create mode 100644 tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-chromium-linux.json create mode 100644 tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-firefox-linux.json create mode 100644 tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-webkit-linux.json create mode 100644 tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-chromium-linux.json create mode 100644 tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-firefox-linux.json create mode 100644 tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-webkit-linux.json diff --git a/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 07d51f42ea..a31726f4c0 100644 --- a/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -184,6 +184,13 @@ export const KeyboardShortcutsExtension = Extension.create<{ let chainedCommands = chain(); + if (blockInfo.childContainer) { + chainedCommands.insertContentAt( + blockInfo.bnBlock.afterPos, + blockInfo.childContainer?.node.content, + ); + } + if ( prevBlockInfo.blockContent.node.type.spec.content === "tableRow+" @@ -209,8 +216,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ ); } else { const blockContentStartPos = - prevBlockInfo.blockContent.afterPos - - prevBlockInfo.blockContent.node.nodeSize; + prevBlockInfo.blockContent.afterPos - 1; chainedCommands = chainedCommands.setTextSelection(blockContentStartPos); @@ -413,7 +419,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // Creates a new block and moves the selection to it if the current one is empty, while the selection is also // empty & at the start of the block. () => - commands.command(({ state, dispatch }) => { + commands.command(({ state, dispatch, tr }) => { const blockInfo = getBlockInfoFromSelection(state); if (!blockInfo.isBlockContainer) { return false; @@ -431,15 +437,29 @@ export const KeyboardShortcutsExtension = Extension.create<{ const newBlockContentPos = newBlockInsertionPos + 2; if (dispatch) { - const newBlock = - state.schema.nodes["blockContainer"].createAndFill()!; - - state.tr - .insert(newBlockInsertionPos, newBlock) + const newBlock = state.schema.nodes[ + "blockContainer" + ].createAndFill( + undefined, + [ + state.schema.nodes["paragraph"].createAndFill() || + undefined, + blockInfo.childContainer?.node, + ].filter((node) => node !== undefined), + )!; + + tr.insert(newBlockInsertionPos, newBlock) + .setSelection( + new TextSelection(tr.doc.resolve(newBlockContentPos)), + ) .scrollIntoView(); - state.tr.setSelection( - new TextSelection(state.doc.resolve(newBlockContentPos)), - ); + + if (blockInfo.childContainer) { + tr.delete( + blockInfo.childContainer.beforePos, + blockInfo.childContainer.afterPos, + ); + } } return true; diff --git a/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts index 0ea57a0e24..2fb1fa10cf 100644 --- a/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts +++ b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts @@ -67,13 +67,32 @@ test.describe("Check Keyboard Handlers' Behaviour", () => { await page.keyboard.press("ArrowUp"); await page.keyboard.press("ArrowUp"); await page.keyboard.press("ArrowUp"); - await page.keyboard.press("ArrowUp"); await page.keyboard.press("Control+ArrowLeft"); await page.keyboard.press("ArrowRight"); await page.keyboard.press("Enter"); await compareDocToSnapshot(page, "enterPreservesNestedBlocks.json"); }); + test("Check Enter preserves nested blocks for empty block", async ({ + page, + }) => { + await focusOnEditor(page); + await page.keyboard.press("#"); + await page.keyboard.press(" "); + await page.keyboard.press("ArrowDown", { delay: 10 }); + await page.keyboard.press("Tab"); + await insertHeading(page, 2); + await page.keyboard.press("Tab"); + await insertHeading(page, 3); + + await page.waitForTimeout(500); + await page.keyboard.press("ArrowUp"); + await page.keyboard.press("ArrowUp"); + await page.keyboard.press("ArrowUp"); + await page.keyboard.press("Enter"); + + await compareDocToSnapshot(page, "enterPreservesNestedBlocksEmpty.json"); + }); test("Check Backspace at the start of a block", async ({ page }) => { await focusOnEditor(page); await insertHeading(page, 1); @@ -129,6 +148,29 @@ test.describe("Check Keyboard Handlers' Behaviour", () => { await compareDocToSnapshot(page, "backspacePreservesNestedBlocks.json"); }); + test("Check Backspace preserves nested blocks for empty block", async ({ + page, + }) => { + await focusOnEditor(page); + await insertParagraph(page); + await page.keyboard.press("Enter", { delay: 10 }); + await page.keyboard.press("Tab"); + await insertParagraph(page); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await insertParagraph(page); + + for (let i = 0; i < 3; i++) { + await page.keyboard.press("ArrowUp"); + } + + await page.keyboard.press("Backspace"); + + await compareDocToSnapshot( + page, + "backspacePreservesNestedBlocksEmpty.json", + ); + }); test("Check heading 1 shortcut", async ({ page }) => { await focusOnEditor(page); await page.keyboard.type("Paragraph"); diff --git a/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-chromium-linux.json b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-chromium-linux.json new file mode 100644 index 0000000000..b4213527b1 --- /dev/null +++ b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-chromium-linux.json @@ -0,0 +1,97 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "0" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "2" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "3" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "4" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-firefox-linux.json b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-firefox-linux.json new file mode 100644 index 0000000000..b4213527b1 --- /dev/null +++ b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-firefox-linux.json @@ -0,0 +1,97 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "0" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "2" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "3" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "4" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-webkit-linux.json b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-webkit-linux.json new file mode 100644 index 0000000000..b4213527b1 --- /dev/null +++ b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/backspacePreservesNestedBlocksEmpty-json-webkit-linux.json @@ -0,0 +1,97 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "0" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "2" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "3" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "4" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-chromium-linux.json b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-chromium-linux.json new file mode 100644 index 0000000000..6b5910ba90 --- /dev/null +++ b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-chromium-linux.json @@ -0,0 +1,113 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "0" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 1, + "isToggleable": false + } + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "4" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "1" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 2, + "isToggleable": false + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "2" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 3, + "isToggleable": false + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "3" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-firefox-linux.json b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-firefox-linux.json new file mode 100644 index 0000000000..6b5910ba90 --- /dev/null +++ b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-firefox-linux.json @@ -0,0 +1,113 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "0" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 1, + "isToggleable": false + } + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "4" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "1" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 2, + "isToggleable": false + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "2" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 3, + "isToggleable": false + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "3" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-webkit-linux.json b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-webkit-linux.json new file mode 100644 index 0000000000..6b5910ba90 --- /dev/null +++ b/tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocksEmpty-json-webkit-linux.json @@ -0,0 +1,113 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "0" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 1, + "isToggleable": false + } + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "4" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": "1" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 2, + "isToggleable": false + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "2" + }, + "content": [ + { + "type": "heading", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left", + "level": 3, + "isToggleable": false + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": "3" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "backgroundColor": "default", + "textColor": "default", + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file