Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 39 additions & 15 deletions packages/core/src/extensions/Blocks/nodes/BlockContainer.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -222,20 +222,36 @@ export const BlockContainer = Node.create<IBlock>({
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,
Expand All @@ -244,22 +260,30 @@ export const BlockContainer = Node.create<IBlock>({
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 }) => {
Expand Down
36 changes: 36 additions & 0 deletions tests/end-to-end/keyboardhandlers/keyboardhandlers.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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");
});
});
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
]
}
]
}
Loading