diff --git a/packages/core/src/extensions/Blocks/nodes/BlockContainer.ts b/packages/core/src/extensions/Blocks/nodes/BlockContainer.ts index 7577d774ad..5e248468c4 100644 --- a/packages/core/src/extensions/Blocks/nodes/BlockContainer.ts +++ b/packages/core/src/extensions/Blocks/nodes/BlockContainer.ts @@ -1,5 +1,5 @@ import { mergeAttributes, Node } from "@tiptap/core"; -import { Slice } from "prosemirror-model"; +import { Fragment, Slice } from "prosemirror-model"; import { TextSelection } from "prosemirror-state"; import { BlockUpdate } from "../apiTypes"; import { getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos"; @@ -222,20 +222,36 @@ export const BlockContainer = Node.create({ const { contentNode, contentType, startPos, endPos, depth } = blockInfo; - const newBlockInsertionPos = endPos + 1; - - // Creates new block first, otherwise positions get changed due to the original block's content changing. - // Only text content is transferred to the new block. - const secondBlockContent = state.doc.textBetween(posInBlock, endPos); + const originalBlockContent = state.doc.cut(startPos + 1, posInBlock); + const newBlockContent = state.doc.cut(posInBlock, endPos - 1); const newBlock = state.schema.nodes["blockContainer"].createAndFill()!; + + const newBlockInsertionPos = endPos + 1; const newBlockContentPos = newBlockInsertionPos + 2; if (dispatch) { + // Creates a new block. Since the schema requires it to have a content node, a paragraph node is created + // automatically, spanning newBlockContentPos to newBlockContentPos + 1. state.tr.insert(newBlockInsertionPos, newBlock); - state.tr.insertText(secondBlockContent, newBlockContentPos); + // Replaces the content of the newly created block's content node. Doesn't replace the whole content node so + // its type doesn't change. + state.tr.replace( + newBlockContentPos, + newBlockContentPos + 1, + newBlockContent.content.size > 0 + ? new Slice( + Fragment.from(newBlockContent), + depth + 2, + depth + 2 + ) + : undefined + ); + + // Changes the type of the content node. The range doesn't matter as long as both from and to positions are + // within the content node. if (keepType) { state.tr.setBlockType( newBlockContentPos, @@ -244,22 +260,30 @@ export const BlockContainer = Node.create({ contentNode.attrs ); } - } - // Updates content of original block. - const firstBlockContent = state.doc.content.cut(startPos, posInBlock); + // Sets the selection to the start of the new block's content node. + state.tr.setSelection( + new TextSelection(state.doc.resolve(newBlockContentPos)) + ); - if (dispatch) { + // Replaces the content of the original block's content node. Doesn't replace the whole content node so its + // type doesn't change. state.tr.replace( - startPos, - endPos, - new Slice(firstBlockContent, depth, depth) + startPos + 1, + endPos - 1, + originalBlockContent.content.size > 0 + ? new Slice( + Fragment.from(originalBlockContent), + depth + 2, + depth + 2 + ) + : undefined ); } return true; }, - // Changes the content of a block at a given position to a given type. + // Updates the type and attributes of a block at a given position. BNUpdateBlock: (posInBlock, blockUpdate) => ({ state, dispatch }) => { diff --git a/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts index 36c24edc4f..39be3d5f62 100644 --- a/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts +++ b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts @@ -1,6 +1,7 @@ import { test } from "../../setup/setupScript"; import { BASE_URL, + ITALIC_BUTTON_SELECTOR, H_ONE_BLOCK_SELECTOR, H_TWO_BLOCK_SELECTOR, } from "../../utils/const"; @@ -34,4 +35,39 @@ test.describe("Check Keyboard Handlers' Behaviour", () => { await compareDocToSnapshot(page, "enterSelectionNotEmpty.json"); }); + test("Check Enter preserves marks", async ({ page }) => { + await focusOnEditor(page); + await insertHeading(page, 1); + + const element = await page.locator(H_ONE_BLOCK_SELECTOR); + let boundingBox = await element.boundingBox(); + let { x, y, height } = boundingBox; + + await page.mouse.click(x + 35, y + height / 2, { clickCount: 2 }); + await page.locator(ITALIC_BUTTON_SELECTOR).click(); + await page.waitForTimeout(600); + await page.mouse.click(x + 35, y + height / 2); + await page.keyboard.press("Enter"); + + await page.pause(); + + await compareDocToSnapshot(page, "enterPreservesMarks.json"); + }); + test("Check Enter preserves nested blocks", async ({ page }) => { + await focusOnEditor(page); + await insertHeading(page, 1); + await page.keyboard.press("Tab"); + await insertHeading(page, 2); + await page.keyboard.press("Tab"); + await insertHeading(page, 3); + + const element = await page.locator(H_ONE_BLOCK_SELECTOR); + let boundingBox = await element.boundingBox(); + let { x, y, height } = boundingBox; + + await page.mouse.click(x + 35, y + height / 2); + await page.keyboard.press("Enter"); + + await compareDocToSnapshot(page, "enterPreservesNestedBlocks.json"); + }); }); diff --git a/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-chromium-linux.json b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-chromium-linux.json new file mode 100644 index 0000000000..fa64e4b10e --- /dev/null +++ b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-chromium-linux.json @@ -0,0 +1,81 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 0, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "1" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "H" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 2, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "eading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 1, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-firefox-linux.json b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-firefox-linux.json new file mode 100644 index 0000000000..fa64e4b10e --- /dev/null +++ b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-firefox-linux.json @@ -0,0 +1,81 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 0, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "1" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "H" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 2, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "eading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 1, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-webkit-linux.json b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-webkit-linux.json new file mode 100644 index 0000000000..fa64e4b10e --- /dev/null +++ b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesMarks-json-webkit-linux.json @@ -0,0 +1,81 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 0, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "1" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "H" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 2, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "eading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 1, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-chromium-linux.json b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-chromium-linux.json new file mode 100644 index 0000000000..3b74616256 --- /dev/null +++ b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-chromium-linux.json @@ -0,0 +1,122 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 0, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "1" + }, + "content": [ + { + "type": "text", + "text": "H" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 4, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "eading" + } + ] + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 1, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "2" + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 2, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "3" + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 3, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-firefox-linux.json b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-firefox-linux.json new file mode 100644 index 0000000000..3b74616256 --- /dev/null +++ b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-firefox-linux.json @@ -0,0 +1,122 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 0, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "1" + }, + "content": [ + { + "type": "text", + "text": "H" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 4, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "eading" + } + ] + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 1, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "2" + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 2, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "3" + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 3, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-webkit-linux.json b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-webkit-linux.json new file mode 100644 index 0000000000..3b74616256 --- /dev/null +++ b/tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts-snapshots/enterPreservesNestedBlocks-json-webkit-linux.json @@ -0,0 +1,122 @@ +{ + "type": "doc", + "content": [ + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 0, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "1" + }, + "content": [ + { + "type": "text", + "text": "H" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 4, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "eading" + } + ] + }, + { + "type": "blockGroup", + "content": [ + { + "type": "blockContainer", + "attrs": { + "id": 1, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "2" + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 2, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "heading", + "attrs": { + "textAlignment": "left", + "level": "3" + }, + "content": [ + { + "type": "text", + "text": "Heading" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "blockContainer", + "attrs": { + "id": 3, + "textColor": "default", + "backgroundColor": "default" + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlignment": "left" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/utils/const.ts b/tests/utils/const.ts index 804868ab25..3f8e7c09e7 100644 --- a/tests/utils/const.ts +++ b/tests/utils/const.ts @@ -20,6 +20,7 @@ export const DRAG_HANDLE_ADD_SELECTOR = `[data-test="dragHandleAdd"]`; export const DRAG_HANDLE_MENU_SELECTOR = `.mantine-DragHandleMenu-root`; export const SLASH_MENU_SELECTOR = `.mantine-SlashMenu-root`; +export const ITALIC_BUTTON_SELECTOR = `[data-test="italic"]`; export const COLORS_BUTTON_SELECTOR = `[data-test="colors"]`; export const TEXT_COLOR_SELECTOR = (color: string) => `[data-test="text-color-${color}"]`;