diff --git a/docs/pages/docs/editor-api/manipulating-blocks.mdx b/docs/pages/docs/editor-api/manipulating-blocks.mdx index 3e90d41a5b..377403bd25 100644 --- a/docs/pages/docs/editor-api/manipulating-blocks.mdx +++ b/docs/pages/docs/editor-api/manipulating-blocks.mdx @@ -115,7 +115,7 @@ Use `insertBlocks` to insert new blocks into the document: insertBlocks( blocksToInsert: PartialBlock[], referenceBlock: BlockIdentifier, - placement: "before" | "after" | "nested" = "before" + placement: "before" | "after" = "before" ): void; // Usage @@ -126,7 +126,7 @@ editor.insertBlocks([{type: "paragraph", content: "Hello World"}], referenceBloc `referenceBlock:` An [identifier](/docs/editor-api/manipulating-blocks#block-identifiers) for an existing block, at which the new blocks should be inserted. -`placement:` Whether the blocks should be inserted just before, just after, or nested inside the `referenceBlock`. Inserts the blocks at the start of the existing block's children if `"nested"` is used. +`placement:` Whether the blocks should be inserted just before or just after the `referenceBlock`. If a block's `id` is undefined, BlockNote generates one automatically. diff --git a/packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap b/packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap deleted file mode 100644 index ecc3057eda..0000000000 --- a/packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +++ /dev/null @@ -1,921 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Insert, Update, & Delete Blocks > Insert, update, & delete multiple blocks 1`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 1", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 1", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 2", - "type": "text", - }, - ], - "id": "4", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 2", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Insert, Update, & Delete Blocks > Insert, update, & delete multiple blocks 2`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 1", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 1", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 2", - "type": "text", - }, - ], - "id": "4", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 2", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Insert, Update, & Delete Blocks > Insert, update, & delete multiple blocks 3`] = ` -[ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Heading 1", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Insert, Update, & Delete Blocks > Insert, update, & delete single block 1`] = ` -[ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Insert, Update, & Delete Blocks > Insert, update, & delete single block 2`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": [ - { - "styles": { - "textColor": "red", - }, - "text": "Heading ", - "type": "text", - }, - { - "styles": { - "backgroundColor": "red", - }, - "text": "3", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "level": 3, - "textAlignment": "right", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Insert, Update, & Delete Blocks > Insert, update, & delete single block 3`] = ` -[ - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Inserting Blocks with Different Placements > Insert after existing block 1`] = ` -[ - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 1", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 1", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 2", - "type": "text", - }, - ], - "id": "4", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 2", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [], - "content": [], - "id": "5", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Inserting Blocks with Different Placements > Insert before existing block 1`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 1", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 1", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 2", - "type": "text", - }, - ], - "id": "4", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 2", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Inserting Blocks with Different Placements > Insert nested inside existing block 1`] = ` -[ - { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 1", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 1", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Heading 2", - "type": "text", - }, - ], - "id": "4", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Heading 2", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "level": 2, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [], - "id": "5", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Update Line Breaks > Update custom block with line break 1`] = ` -[ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Line1 -Line2", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Updated Custom Block with -line -break", - "type": "text", - }, - ], - "id": "2", - "props": {}, - "type": "customBlock", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Update Line Breaks > Update paragraph with line break 1`] = ` -[ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Updated Custom Block with -line -break", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Line1 -Line2", - "type": "text", - }, - ], - "id": "2", - "props": {}, - "type": "customBlock", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Update block cases > Update children only 1`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Heading", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Paragraph", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Update block cases > Update content and children 1`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Heading", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - ], - "content": [ - { - "styles": {}, - "text": "Updated Paragraph", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Update block cases > Update content only 1`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": [ - { - "styles": {}, - "text": "Updated Paragraph", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Update block cases > Update type only 1`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": [ - { - "styles": {}, - "text": "Paragraph", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [], - "content": [], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; diff --git a/packages/core/src/api/blockManipulation/blockManipulation.test.ts b/packages/core/src/api/blockManipulation/blockManipulation.test.ts deleted file mode 100644 index 1a3edcdada..0000000000 --- a/packages/core/src/api/blockManipulation/blockManipulation.test.ts +++ /dev/null @@ -1,370 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { - Block, - DefaultInlineContentSchema, - DefaultStyleSchema, - PartialBlock, - defaultBlockSpecs, -} from "../../blocks/defaultBlocks.js"; -import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import { BlockNoteSchema } from "../../editor/BlockNoteSchema.js"; -import { createBlockSpec } from "../../schema/index.js"; - -const CustomBlock = createBlockSpec( - { - type: "customBlock", - propSchema: {}, - content: "inline", - } as const, - { - render: () => { - const dom = document.createElement("div"); - dom.className = "custom-block"; - - return { - dom: dom, - contentDOM: dom, - }; - }, - } -); - -const schema = BlockNoteSchema.create({ - blockSpecs: { - ...defaultBlockSpecs, - customBlock: CustomBlock, - }, -}); - -let editor: BlockNoteEditor; -const div = document.createElement("div"); - -let singleBlock: PartialBlock< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->; - -let singleBlockWithChildren: PartialBlock< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->; - -let multipleBlocks: PartialBlock< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->[]; - -let blocksWithLineBreaks: PartialBlock< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->[]; - -let insert: ( - placement: "before" | "nested" | "after" -) => Block< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->[]; - -beforeEach(() => { - editor = BlockNoteEditor.create({ - schema: schema, - }); - - editor.mount(div); - - singleBlock = { - type: "paragraph", - content: "Paragraph", - }; - - singleBlockWithChildren = { - type: "paragraph", - content: "Paragraph", - children: [ - { - type: "paragraph", - content: "Nested Paragraph", - }, - ], - }; - - multipleBlocks = [ - { - type: "heading", - props: { - level: 1, - }, - content: "Heading 1", - children: [ - { - type: "heading", - props: { - level: 1, - }, - content: "Nested Heading 1", - }, - ], - }, - { - type: "heading", - props: { - level: 2, - }, - content: "Heading 2", - children: [ - { - type: "heading", - props: { - level: 2, - }, - content: "Nested Heading 2", - }, - ], - }, - ]; - - blocksWithLineBreaks = [ - { - type: "paragraph", - content: "Line1\nLine2", - }, - { - type: "customBlock", - content: "Line1\nLine2", - }, - ]; - - insert = (placement) => { - const existingBlock = editor.document[0]; - editor.insertBlocks(multipleBlocks, existingBlock, placement); - - return editor.document; - }; -}); - -afterEach(() => { - editor.mount(undefined); - editor._tiptapEditor.destroy(); - editor = undefined as any; -}); - -describe("Test strong typing", () => { - it("checks that block types are inferred correctly", () => { - try { - editor.updateBlock( - { id: "sdf" }, - { - // @ts-expect-error invalid type - type: "non-existing", - } - ); - } catch (e) { - // id doesn't exists, which is fine, this is a compile-time check - } - }); - - it("checks that block props are inferred correctly", () => { - try { - editor.updateBlock( - { id: "sdf" }, - { - type: "paragraph", - props: { - // @ts-expect-error invalid type - level: 1, - }, - } - ); - } catch (e) { - // id doesn't exists, which is fine, this is a compile-time check - } - try { - editor.updateBlock( - { id: "sdf" }, - { - type: "heading", - props: { - level: 1, - }, - } - ); - } catch (e) { - // id doesn't exists, which is fine, this is a compile-time check - } - }); -}); - -describe("Inserting Blocks with Different Placements", () => { - it("Insert before existing block", () => { - const output = insert("before"); - - expect(output).toMatchSnapshot(); - }); - - it("Insert nested inside existing block", () => { - const output = insert("nested"); - - expect(output).toMatchSnapshot(); - }); - - it("Insert after existing block", () => { - const output = insert("after"); - - expect(output).toMatchSnapshot(); - }); -}); - -describe("Insert, Update, & Delete Blocks", () => { - it("Insert, update, & delete single block", () => { - const existingBlock = editor.document[0]; - editor.insertBlocks([singleBlock], existingBlock); - - expect(editor.document).toMatchSnapshot(); - - const newBlock = editor.document[0]; - editor.updateBlock(newBlock, { - type: "heading", - props: { - textAlignment: "right", - level: 3, - }, - content: [ - { - type: "text", - text: "Heading ", - styles: { - textColor: "red", - }, - }, - { - type: "text", - text: "3", - styles: { - backgroundColor: "red", - }, - }, - ], - children: [singleBlock], - }); - - expect(editor.document).toMatchSnapshot(); - - const updatedBlock = editor.document[0]; - editor.removeBlocks([updatedBlock]); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Insert, update, & delete multiple blocks", () => { - const existingBlock = editor.document[0]; - editor.insertBlocks(multipleBlocks, existingBlock); - - expect(editor.document).toMatchSnapshot(); - - const newBlock = editor.document[0]; - editor.updateBlock(newBlock, { - type: "paragraph", - }); - - expect(editor.document).toMatchSnapshot(); - - const updatedBlocks = editor.document.slice(0, 2); - editor.removeBlocks([updatedBlocks[0].children[0], updatedBlocks[1]]); - - expect(editor.document).toMatchSnapshot(); - }); -}); - -describe("Update block cases", () => { - it("Update type only", async () => { - const existingBlock = editor.document[0]; - editor.insertBlocks([singleBlockWithChildren], existingBlock); - - const newBlock = editor.document[0]; - editor.updateBlock(newBlock, { - type: "heading", - }); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Update content only", async () => { - const existingBlock = editor.document[0]; - editor.insertBlocks([singleBlockWithChildren], existingBlock); - - const newBlock = editor.document[0]; - editor.updateBlock(newBlock, { - content: "Updated Paragraph", - }); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Update children only", async () => { - const existingBlock = editor.document[0]; - editor.insertBlocks([singleBlockWithChildren], existingBlock); - - const newBlock = editor.document[0]; - editor.updateBlock(newBlock, { - children: [ - { - type: "heading", - content: "Heading", - }, - ], - }); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Update content and children", async () => { - const existingBlock = editor.document[0]; - editor.insertBlocks([singleBlockWithChildren], existingBlock); - - const newBlock = editor.document[0]; - editor.updateBlock(newBlock, { - content: "Updated Paragraph", - children: [ - { - type: "heading", - content: "Heading", - }, - ], - }); - - expect(editor.document).toMatchSnapshot(); - }); -}); - -describe("Update Line Breaks", () => { - it("Update paragraph with line break", () => { - const existingBlock = editor.document[0]; - editor.insertBlocks(blocksWithLineBreaks, existingBlock); - - const newBlock = editor.document[0]; - editor.updateBlock(newBlock, { - type: "paragraph", - content: "Updated Custom Block with \nline \nbreak", - }); - - expect(editor.document).toMatchSnapshot(); - }); - it("Update custom block with line break", () => { - const existingBlock = editor.document[0]; - editor.insertBlocks(blocksWithLineBreaks, existingBlock); - - const newBlock = editor.document[1]; - editor.updateBlock(newBlock, { - type: "customBlock", - content: "Updated Custom Block with \nline \nbreak", - }); - - expect(editor.document).toMatchSnapshot(); - }); -}); diff --git a/packages/core/src/api/blockManipulation/blockManipulation.ts b/packages/core/src/api/blockManipulation/blockManipulation.ts deleted file mode 100644 index 1a5dd6dc6a..0000000000 --- a/packages/core/src/api/blockManipulation/blockManipulation.ts +++ /dev/null @@ -1,353 +0,0 @@ -import { Node } from "prosemirror-model"; - -import { selectionToInsertionEnd } from "@tiptap/core"; -import { Transaction } from "prosemirror-state"; -import { Block, PartialBlock } from "../../blocks/defaultBlocks.js"; -import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import { - BlockIdentifier, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../../schema/index.js"; -import { - blockToNode, - nodeToBlock, -} from "../nodeConversions/nodeConversions.js"; -import { getNodeById } from "../nodeUtil.js"; - -export function insertBlocks< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema ->( - blocksToInsert: PartialBlock[], - referenceBlock: BlockIdentifier, - placement: "before" | "after" | "nested" = "before", - editor: BlockNoteEditor -): Block[] { - const id = - typeof referenceBlock === "string" ? referenceBlock : referenceBlock.id; - - const nodesToInsert: Node[] = []; - for (const blockSpec of blocksToInsert) { - nodesToInsert.push( - blockToNode(blockSpec, editor.pmSchema, editor.schema.styleSchema) - ); - } - - const { node, posBeforeNode } = getNodeById( - id, - editor._tiptapEditor.state.doc - ); - - if (placement === "before") { - editor.dispatch( - editor._tiptapEditor.state.tr.insert(posBeforeNode, nodesToInsert) - ); - } - - if (placement === "after") { - editor.dispatch( - editor._tiptapEditor.state.tr.insert( - posBeforeNode + node.nodeSize, - nodesToInsert - ) - ); - } - - if (placement === "nested") { - // Case if block doesn't already have children. - if (node.childCount < 2) { - const blockGroupNode = editor._tiptapEditor.state.schema.nodes[ - "blockGroup" - ].create({}, nodesToInsert); - - editor.dispatch( - editor._tiptapEditor.state.tr.insert( - posBeforeNode + node.firstChild!.nodeSize + 1, - blockGroupNode - ) - ); - } - } - - // Now that the `PartialBlock`s have been converted to nodes, we can - // re-convert them into full `Block`s. - const insertedBlocks: Block[] = []; - for (const node of nodesToInsert) { - insertedBlocks.push( - nodeToBlock( - node, - editor.schema.blockSchema, - editor.schema.inlineContentSchema, - editor.schema.styleSchema, - editor.blockCache - ) - ); - } - - return insertedBlocks; -} - -export function updateBlock< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema ->( - blockToUpdate: BlockIdentifier, - update: PartialBlock, - editor: BlockNoteEditor -): Block { - const ttEditor = editor._tiptapEditor; - - const id = - typeof blockToUpdate === "string" ? blockToUpdate : blockToUpdate.id; - const { posBeforeNode } = getNodeById(id, ttEditor.state.doc); - - ttEditor.commands.BNUpdateBlock(posBeforeNode + 1, update); - - const blockContainerNode = ttEditor.state.doc - .resolve(posBeforeNode + 1) - .node(); - - return nodeToBlock( - blockContainerNode, - editor.schema.blockSchema, - editor.schema.inlineContentSchema, - editor.schema.styleSchema, - editor.blockCache - ); -} - -function removeBlocksWithCallback< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema ->( - blocksToRemove: BlockIdentifier[], - editor: BlockNoteEditor, - // Should return new removedSize. - callback?: ( - node: Node, - pos: number, - tr: Transaction, - removedSize: number - ) => number -): Block[] { - const ttEditor = editor._tiptapEditor; - const tr = ttEditor.state.tr; - - const idsOfBlocksToRemove = new Set( - blocksToRemove.map((block) => - typeof block === "string" ? block : block.id - ) - ); - const removedBlocks: Block[] = []; - let removedSize = 0; - - ttEditor.state.doc.descendants((node, pos) => { - // Skips traversing nodes after all target blocks have been removed. - if (idsOfBlocksToRemove.size === 0) { - return false; - } - - // Keeps traversing nodes if block with target ID has not been found. - if ( - node.type.name !== "blockContainer" || - !idsOfBlocksToRemove.has(node.attrs.id) - ) { - return true; - } - - // Saves the block that is being deleted. - removedBlocks.push( - nodeToBlock( - node, - editor.schema.blockSchema, - editor.schema.inlineContentSchema, - editor.schema.styleSchema, - editor.blockCache - ) - ); - idsOfBlocksToRemove.delete(node.attrs.id); - - // Removes the block and calculates the change in document size. - removedSize = callback?.(node, pos, tr, removedSize) || removedSize; - const oldDocSize = tr.doc.nodeSize; - tr.delete(pos - removedSize - 1, pos - removedSize + node.nodeSize + 1); - const newDocSize = tr.doc.nodeSize; - removedSize += oldDocSize - newDocSize; - - return false; - }); - - // Throws an error if now all blocks could be found. - if (idsOfBlocksToRemove.size > 0) { - const notFoundIds = [...idsOfBlocksToRemove].join("\n"); - - throw Error( - "Blocks with the following IDs could not be found in the editor: " + - notFoundIds - ); - } - - editor.dispatch(tr); - - return removedBlocks; -} - -export function removeBlocks< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema ->( - blocksToRemove: BlockIdentifier[], - editor: BlockNoteEditor -): Block[] { - return removeBlocksWithCallback(blocksToRemove, editor); -} - -export function replaceBlocks< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema ->( - blocksToRemove: BlockIdentifier[], - blocksToInsert: PartialBlock[], - editor: BlockNoteEditor -): { - insertedBlocks: Block[]; - removedBlocks: Block[]; -} { - const nodesToInsert: Node[] = []; - for (const block of blocksToInsert) { - nodesToInsert.push( - blockToNode(block, editor.pmSchema, editor.schema.styleSchema) - ); - } - - const idOfFirstBlock = - typeof blocksToRemove[0] === "string" - ? blocksToRemove[0] - : blocksToRemove[0].id; - const removedBlocks = removeBlocksWithCallback( - blocksToRemove, - editor, - (node, pos, tr, removedSize) => { - if (node.attrs.id === idOfFirstBlock) { - const oldDocSize = tr.doc.nodeSize; - tr.insert(pos, nodesToInsert); - const newDocSize = tr.doc.nodeSize; - - return removedSize + oldDocSize - newDocSize; - } - - return removedSize; - } - ); - - // Now that the `PartialBlock`s have been converted to nodes, we can - // re-convert them into full `Block`s. - const insertedBlocks: Block[] = []; - for (const node of nodesToInsert) { - insertedBlocks.push( - nodeToBlock( - node, - editor.schema.blockSchema, - editor.schema.inlineContentSchema, - editor.schema.styleSchema, - editor.blockCache - ) - ); - } - - return { insertedBlocks, removedBlocks }; -} - -// similar to tiptap insertContentAt -export function insertContentAt< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema ->( - position: any, - nodes: Node[], - editor: BlockNoteEditor, - options: { - updateSelection: boolean; - } = { updateSelection: true } -) { - const tr = editor._tiptapEditor.state.tr; - - // don’t dispatch an empty fragment because this can lead to strange errors - // if (content.toString() === "<>") { - // return true; - // } - - let { from, to } = - typeof position === "number" - ? { from: position, to: position } - : { from: position.from, to: position.to }; - - let isOnlyTextContent = true; - let isOnlyBlockContent = true; - // const nodes = isFragment(content) ? content : [content]; - - let text = ""; - - nodes.forEach((node) => { - // check if added node is valid - node.check(); - - if (isOnlyTextContent && node.isText && node.marks.length === 0) { - text += node.text; - } else { - isOnlyTextContent = false; - } - - isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false; - }); - - // check if we can replace the wrapping node by - // the newly inserted content - // example: - // replace an empty paragraph by an inserted image - // instead of inserting the image below the paragraph - if (from === to && isOnlyBlockContent) { - const { parent } = tr.doc.resolve(from); - const isEmptyTextBlock = - parent.isTextblock && !parent.type.spec.code && !parent.childCount; - - if (isEmptyTextBlock) { - from -= 1; - to += 1; - } - } - - // if there is only plain text we have to use `insertText` - // because this will keep the current marks - if (isOnlyTextContent) { - // if value is string, we can use it directly - // otherwise if it is an array, we have to join it - // if (Array.isArray(value)) { - // tr.insertText(value.map((v) => v.text || "").join(""), from, to); - // } else if (typeof value === "object" && !!value && !!value.text) { - // tr.insertText(value.text, from, to); - // } else { - // tr.insertText(value as string, from, to); - // } - tr.insertText(text, from, to); - } else { - tr.replaceWith(from, to, nodes); - } - - // set cursor at end of inserted content - if (options.updateSelection) { - selectionToInsertionEnd(tr, tr.steps.length - 1, -1); - } - - editor.dispatch(tr); - - return true; -} diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap new file mode 100644 index 0000000000..4bac28e445 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap @@ -0,0 +1,3087 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test insertBlocks > Insert multiple blocks after 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 1", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 3", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert multiple blocks before 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 1", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 3", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert single basic block after 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert single basic block before 1`] = ` +[ + { + "children": [], + "content": [], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert single complex block after 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "inserted-heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert single complex block before 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "inserted-heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts new file mode 100644 index 0000000000..c84a298db6 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts @@ -0,0 +1,132 @@ +import { describe, expect, it } from "vitest"; + +import { setupTestEnv } from "../../setupTestEnv.js"; +import { insertBlocks } from "./insertBlocks.js"; + +const getEditor = setupTestEnv(); + +describe("Test insertBlocks", () => { + it("Insert single basic block before", () => { + insertBlocks(getEditor(), [{ type: "paragraph" }], "paragraph-0", "before"); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert single basic block after", () => { + insertBlocks(getEditor(), [{ type: "paragraph" }], "paragraph-0", "after"); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert multiple blocks before", () => { + insertBlocks( + getEditor(), + [ + { type: "paragraph", content: "Inserted paragraph 1" }, + { type: "paragraph", content: "Inserted paragraph 2" }, + { type: "paragraph", content: "Inserted paragraph 3" }, + ], + "paragraph-0", + "before" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert multiple blocks after", () => { + insertBlocks( + getEditor(), + [ + { type: "paragraph", content: "Inserted paragraph 1" }, + { type: "paragraph", content: "Inserted paragraph 2" }, + { type: "paragraph", content: "Inserted paragraph 3" }, + ], + "paragraph-0", + "after" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert single complex block before", () => { + insertBlocks( + getEditor(), + [ + { + id: "inserted-heading-with-everything", + type: "heading", + props: { + backgroundColor: "red", + level: 2, + textAlignment: "center", + textColor: "red", + }, + content: [ + { type: "text", text: "Heading", styles: { bold: true } }, + { type: "text", text: " with styled ", styles: {} }, + { type: "text", text: "content", styles: { italic: true } }, + ], + children: [ + { + id: "inserted-nested-paragraph-2", + type: "paragraph", + content: "Nested Paragraph 2", + children: [ + { + id: "inserted-double-nested-paragraph-2", + type: "paragraph", + content: "Double Nested Paragraph 2", + }, + ], + }, + ], + }, + ], + "paragraph-0", + "before" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert single complex block after", () => { + insertBlocks( + getEditor(), + [ + { + id: "inserted-heading-with-everything", + type: "heading", + props: { + backgroundColor: "red", + level: 2, + textAlignment: "center", + textColor: "red", + }, + content: [ + { type: "text", text: "Heading", styles: { bold: true } }, + { type: "text", text: " with styled ", styles: {} }, + { type: "text", text: "content", styles: { italic: true } }, + ], + children: [ + { + id: "inserted-nested-paragraph-2", + type: "paragraph", + content: "Nested Paragraph 2", + children: [ + { + id: "inserted-double-nested-paragraph-2", + type: "paragraph", + content: "Double Nested Paragraph 2", + }, + ], + }, + ], + }, + ], + "paragraph-0", + "after" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts new file mode 100644 index 0000000000..66474cef0e --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -0,0 +1,71 @@ +import { Node } from "prosemirror-model"; + +import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; +import { + BlockIdentifier, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../../../schema/index.js"; +import { blockToNode } from "../../../nodeConversions/blockToNode.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; +import { getNodeById } from "../../../nodeUtil.js"; + +export function insertBlocks< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + blocksToInsert: PartialBlock[], + referenceBlock: BlockIdentifier, + placement: "before" | "after" = "before" +): Block[] { + const id = + typeof referenceBlock === "string" ? referenceBlock : referenceBlock.id; + + const nodesToInsert: Node[] = []; + for (const blockSpec of blocksToInsert) { + nodesToInsert.push( + blockToNode(blockSpec, editor.pmSchema, editor.schema.styleSchema) + ); + } + + const { node, posBeforeNode } = getNodeById( + id, + editor._tiptapEditor.state.doc + ); + + if (placement === "before") { + editor.dispatch( + editor._tiptapEditor.state.tr.insert(posBeforeNode, nodesToInsert) + ); + } + + if (placement === "after") { + editor.dispatch( + editor._tiptapEditor.state.tr.insert( + posBeforeNode + node.nodeSize, + nodesToInsert + ) + ); + } + + // Now that the `PartialBlock`s have been converted to nodes, we can + // re-convert them into full `Block`s. + const insertedBlocks: Block[] = []; + for (const node of nodesToInsert) { + insertedBlocks.push( + nodeToBlock( + node, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ) + ); + } + + return insertedBlocks; +} diff --git a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap similarity index 57% rename from packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap rename to packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index 4da0fdceec..6b2592770c 100644 --- a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -1,13 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Test moveBlockDown > Basic 1`] = ` +exports[`Test mergeBlocks > Basic 1`] = ` [ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph 0Paragraph 1", "type": "text", }, ], @@ -22,15 +22,33 @@ exports[`Test moveBlockDown > Basic 1`] = ` { "children": [ { - "children": [], + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": [ { "styles": {}, - "text": "Nested Paragraph 1", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "nested-paragraph-1", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -42,11 +60,11 @@ exports[`Test moveBlockDown > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph with children", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -59,11 +77,11 @@ exports[`Test moveBlockDown > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 2", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -76,126 +94,59 @@ exports[`Test moveBlockDown > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-2", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "type": "paragraph", }, { "children": [], - "content": undefined, - "id": "image-1", + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", "props": { "backgroundColor": "default", - "caption": "", - "name": "", - "previewWidth": 512, - "showPreview": true, "textAlignment": "left", - "url": "https://via.placeholder.com/150", + "textColor": "default", }, - "type": "image", + "type": "paragraph", }, { "children": [], - "content": { - "rows": [ - { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], - ], - }, - { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], - ], + "content": [ + { + "styles": { + "bold": true, }, - { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], - ], + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, }, - ], - "type": "tableContent", - }, - "id": "table-1", - "props": { - "backgroundColor": "default", - "textColor": "default", - }, - "type": "table", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -203,56 +154,16 @@ exports[`Test moveBlockDown > Basic 1`] = ` }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlockDown > Into children 1`] = ` -[ { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 0", - "type": "text", - }, - ], - "id": "paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph 1", - "type": "text", - }, - ], - "id": "nested-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], + "children": [], "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 4", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-4", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -265,28 +176,29 @@ exports[`Test moveBlockDown > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Heading 1", "type": "text", }, ], - "id": "paragraph-2", + "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 5", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-5", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -297,7 +209,7 @@ exports[`Test moveBlockDown > Into children 1`] = ` { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -309,6 +221,23 @@ exports[`Test moveBlockDown > Into children 1`] = ` }, "type": "image", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": { @@ -391,17 +320,34 @@ exports[`Test moveBlockDown > Into children 1`] = ` ], "type": "tableContent", }, - "id": "table-1", + "id": "table-0", "props": { "backgroundColor": "default", "textColor": "default", }, "type": "table", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "empty-paragraph", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -409,21 +355,16 @@ exports[`Test moveBlockDown > Into children 1`] = ` }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlockDown > Last block 1`] = ` -[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph 8", "type": "text", }, ], - "id": "paragraph-0", + "id": "paragraph-8", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -434,7 +375,25 @@ exports[`Test moveBlockDown > Last block 1`] = ` { "children": [ { - "children": [], + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": [ { "styles": {}, @@ -452,13 +411,39 @@ exports[`Test moveBlockDown > Last block 1`] = ` }, ], "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, { "styles": {}, - "text": "Paragraph 1", + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", "type": "text", }, ], - "id": "paragraph-1", + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -466,16 +451,21 @@ exports[`Test moveBlockDown > Last block 1`] = ` }, "type": "paragraph", }, +] +`; + +exports[`Test mergeBlocks > Blocks have different types 1`] = ` +[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph 0", "type": "text", }, ], - "id": "paragraph-2", + "id": "paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -488,11 +478,11 @@ exports[`Test moveBlockDown > Last block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 1", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -501,113 +491,68 @@ exports[`Test moveBlockDown > Last block 1`] = ` "type": "paragraph", }, { - "children": [], - "content": undefined, - "id": "image-1", - "props": { - "backgroundColor": "default", - "caption": "", - "name": "", - "previewWidth": 512, - "showPreview": true, - "textAlignment": "left", - "url": "https://via.placeholder.com/150", - }, - "type": "image", - }, - { - "children": [], - "content": { - "rows": [ - { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 1", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], - ], - }, - { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], - ], - }, - { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ + "children": [ + { + "children": [ + { + "children": [], + "content": [ { "styles": {}, - "text": "Cell 9", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", }, - ], - "type": "tableContent", - }, - "id": "table-1", + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", "props": { "backgroundColor": "default", + "textAlignment": "left", "textColor": "default", }, - "type": "table", + "type": "paragraph", }, { "children": [], - "content": [], - "id": "trailing-paragraph", + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -615,25 +560,20 @@ exports[`Test moveBlockDown > Last block 1`] = ` }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlockDown > Out of children 1`] = ` -[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-0", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "type": "paragraph", }, @@ -642,11 +582,11 @@ exports[`Test moveBlockDown > Out of children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 3", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-3", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -657,13 +597,27 @@ exports[`Test moveBlockDown > Out of children 1`] = ` { "children": [], "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, { "styles": {}, - "text": "Nested Paragraph 1", + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", "type": "text", }, ], - "id": "nested-paragraph-1", + "id": "paragraph-with-styled-content", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -676,11 +630,11 @@ exports[`Test moveBlockDown > Out of children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph 4", "type": "text", }, ], - "id": "paragraph-2", + "id": "paragraph-4", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -693,22 +647,23 @@ exports[`Test moveBlockDown > Out of children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Heading 1Paragraph 5", "type": "text", }, ], - "id": "paragraph-3", + "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -720,6 +675,23 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, "type": "image", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": { @@ -802,17 +774,34 @@ exports[`Test moveBlockDown > Out of children 1`] = ` ], "type": "tableContent", }, - "id": "table-1", + "id": "table-0", "props": { "backgroundColor": "default", "textColor": "default", }, "type": "table", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "empty-paragraph", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -820,21 +809,16 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlockUp > Basic 1`] = ` -[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph 8", "type": "text", }, ], - "id": "paragraph-0", + "id": "paragraph-8", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -845,7 +829,25 @@ exports[`Test moveBlockUp > Basic 1`] = ` { "children": [ { - "children": [], + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": [ { "styles": {}, @@ -863,13 +865,39 @@ exports[`Test moveBlockUp > Basic 1`] = ` }, ], "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, { "styles": {}, - "text": "Paragraph 1", + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", "type": "text", }, ], - "id": "paragraph-1", + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -877,16 +905,21 @@ exports[`Test moveBlockUp > Basic 1`] = ` }, "type": "paragraph", }, +] +`; + +exports[`Test mergeBlocks > First block has children 1`] = ` +[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 0", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -899,11 +932,11 @@ exports[`Test moveBlockUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph 1", "type": "text", }, ], - "id": "paragraph-2", + "id": "paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -912,135 +945,85 @@ exports[`Test moveBlockUp > Basic 1`] = ` "type": "paragraph", }, { - "children": [], - "content": undefined, - "id": "image-1", - "props": { - "backgroundColor": "default", - "caption": "", - "name": "", - "previewWidth": 512, - "showPreview": true, - "textAlignment": "left", - "url": "https://via.placeholder.com/150", - }, - "type": "image", - }, - { - "children": [], - "content": { - "rows": [ - { - "cells": [ - [ + "children": [ + { + "children": [ + { + "children": [], + "content": [ { "styles": {}, - "text": "Cell 1", + "text": "Double Nested Paragraph 0Paragraph 2", "type": "text", }, ], - [ - { - "styles": {}, - "text": "Cell 2", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 3", - "type": "text", - }, - ], - ], - }, - { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 4", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 5", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 6", - "type": "text", - }, - ], - ], - }, - { - "cells": [ - [ - { - "styles": {}, - "text": "Cell 7", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 8", - "type": "text", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], - ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", }, - ], - "type": "tableContent", - }, - "id": "table-1", + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", "props": { "backgroundColor": "default", + "textAlignment": "left", "textColor": "default", }, - "type": "table", + "type": "paragraph", }, { "children": [], - "content": [], - "id": "trailing-paragraph", + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlockUp > First block 1`] = ` -[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph 3", "type": "text", }, ], - "id": "paragraph-0", + "id": "paragraph-3", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1049,33 +1032,46 @@ exports[`Test moveBlockUp > First block 1`] = ` "type": "paragraph", }, { - "children": [ + "children": [], + "content": [ { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph 1", - "type": "text", - }, - ], - "id": "nested-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "styles": { + "bold": true, }, - "type": "paragraph", + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", }, ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 4", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-4", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1088,28 +1084,29 @@ exports[`Test moveBlockUp > First block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Heading 1", "type": "text", }, ], - "id": "paragraph-2", + "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 5", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-5", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1120,7 +1117,7 @@ exports[`Test moveBlockUp > First block 1`] = ` { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -1132,6 +1129,23 @@ exports[`Test moveBlockUp > First block 1`] = ` }, "type": "image", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": { @@ -1214,17 +1228,34 @@ exports[`Test moveBlockUp > First block 1`] = ` ], "type": "tableContent", }, - "id": "table-1", + "id": "table-0", "props": { "backgroundColor": "default", "textColor": "default", }, "type": "table", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "empty-paragraph", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1232,21 +1263,16 @@ exports[`Test moveBlockUp > First block 1`] = ` }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlockUp > Into children 1`] = ` -[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph 8", "type": "text", }, ], - "id": "paragraph-0", + "id": "paragraph-8", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1257,32 +1283,33 @@ exports[`Test moveBlockUp > Into children 1`] = ` { "children": [ { - "children": [], - "content": [ + "children": [ { - "styles": {}, - "text": "Nested Paragraph 1", - "type": "text", + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", }, ], - "id": "nested-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1292,13 +1319,39 @@ exports[`Test moveBlockUp > Into children 1`] = ` }, ], "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, { "styles": {}, - "text": "Paragraph 1", + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", "type": "text", }, ], - "id": "paragraph-1", + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1306,16 +1359,21 @@ exports[`Test moveBlockUp > Into children 1`] = ` }, "type": "paragraph", }, +] +`; + +exports[`Test mergeBlocks > Second block has children 1`] = ` +[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 0", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1325,26 +1383,229 @@ exports[`Test moveBlockUp > Into children 1`] = ` }, { "children": [], - "content": undefined, - "id": "image-1", + "content": [ + { + "styles": {}, + "text": "Paragraph 1Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-1", "props": { "backgroundColor": "default", - "caption": "", - "name": "", - "previewWidth": 512, - "showPreview": true, "textAlignment": "left", - "url": "https://via.placeholder.com/150", + "textColor": "default", }, - "type": "image", + "type": "paragraph", }, { - "children": [], - "content": { - "rows": [ - { - "cells": [ - [ + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ { "styles": {}, "text": "Cell 1", @@ -1420,13 +1681,126 @@ exports[`Test moveBlockUp > Into children 1`] = ` ], "type": "tableContent", }, - "id": "table-1", + "id": "table-0", "props": { "backgroundColor": "default", "textColor": "default", }, "type": "table", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1441,7 +1815,7 @@ exports[`Test moveBlockUp > Into children 1`] = ` ] `; -exports[`Test moveBlockUp > Out of children 1`] = ` +exports[`Test mergeBlocks > Second block is empty 1`] = ` [ { "children": [], @@ -1465,11 +1839,11 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 1", + "text": "Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-1", + "id": "paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1478,15 +1852,51 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "type": "paragraph", }, { - "children": [], + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph with children", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1511,6 +1921,23 @@ exports[`Test moveBlockUp > Out of children 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -1528,10 +1955,93 @@ exports[`Test moveBlockUp > Out of children 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -1543,6 +2053,23 @@ exports[`Test moveBlockUp > Out of children 1`] = ` }, "type": "image", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": { @@ -1625,13 +2152,115 @@ exports[`Test moveBlockUp > Out of children 1`] = ` ], "type": "tableContent", }, - "id": "table-1", + "id": "table-0", "props": { "backgroundColor": "default", "textColor": "default", }, "type": "table", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts new file mode 100644 index 0000000000..4e33a3b1ea --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -0,0 +1,131 @@ +import { describe, expect, it } from "vitest"; + +import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js"; +import { setupTestEnv } from "../../setupTestEnv.js"; +import { mergeBlocksCommand } from "./mergeBlocks.js"; + +const getEditor = setupTestEnv(); + +function mergeBlocks(posBetweenBlocks: number) { + return getEditor()._tiptapEditor.commands.command( + mergeBlocksCommand(posBetweenBlocks) + ); +} + +function getPosBeforeSelectedBlock() { + return getBlockInfoFromSelection(getEditor()._tiptapEditor.state) + .blockContainer.beforePos; +} + +describe("Test mergeBlocks", () => { + it("Basic", () => { + getEditor().setTextCursorPosition("paragraph-1"); + + mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("First block has children", () => { + getEditor().setTextCursorPosition("paragraph-2"); + + mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Second block has children", () => { + getEditor().setTextCursorPosition("paragraph-with-children"); + + mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Second block is empty", () => { + getEditor().setTextCursorPosition("empty-paragraph"); + + mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Blocks have different types", () => { + getEditor().setTextCursorPosition("paragraph-5"); + + mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Selection is updated", () => { + getEditor().setTextCursorPosition("paragraph-0", "end"); + + const firstBlockEndOffset = + getEditor()._tiptapEditor.state.selection.$anchor.parentOffset; + + getEditor().setTextCursorPosition("paragraph-1"); + + mergeBlocks(getPosBeforeSelectedBlock()); + + const anchorIsAtOldFirstBlockEndPos = + getEditor()._tiptapEditor.state.selection.$anchor.parentOffset === + firstBlockEndOffset; + + expect(anchorIsAtOldFirstBlockEndPos).toBeTruthy(); + }); + + // We expect a no-op for each of the remaining tests as merging should only + // happen for blocks which both have inline content. We also expect + // `mergeBlocks` to return false as TipTap commands should do that instead of + // throwing an error, when the command cannot be executed. + it("First block is empty", () => { + getEditor().setTextCursorPosition("paragraph-8"); + + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toEqual(originalDocument); + expect(ret).toBeFalsy(); + }); + + it("Inline content & no content", () => { + getEditor().setTextCursorPosition("image-0"); + + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toEqual(originalDocument); + expect(ret).toBeFalsy(); + }); + + it("Inline content & table content", () => { + getEditor().setTextCursorPosition("table-0"); + + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toEqual(originalDocument); + expect(ret).toBeFalsy(); + }); + + it("No content & inline content", () => { + getEditor().setTextCursorPosition("paragraph-6"); + + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toEqual(originalDocument); + expect(ret).toBeFalsy(); + }); + + it("Table content & inline content", () => { + getEditor().setTextCursorPosition("paragraph-7"); + + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); + + expect(getEditor().document).toEqual(originalDocument); + expect(ret).toBeFalsy(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts new file mode 100644 index 0000000000..7af11571d6 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -0,0 +1,103 @@ +import { Node, ResolvedPos } from "prosemirror-model"; +import { EditorState } from "prosemirror-state"; + +import { getBlockInfoFromResolvedPos } from "../../../getBlockInfoFromPos.js"; + +export const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { + const prevNode = $nextBlockPos.nodeBefore; + + if (!prevNode) { + throw new Error( + `Attempted to get previous blockContainer node for merge at position ${$nextBlockPos.pos} but a previous node does not exist` + ); + } + + // Finds the nearest previous block, regardless of nesting level. + let prevBlockBeforePos = $nextBlockPos.posAtIndex($nextBlockPos.index() - 1); + let prevBlockInfo = getBlockInfoFromResolvedPos( + doc.resolve(prevBlockBeforePos) + ); + + while (prevBlockInfo.blockGroup) { + const group = prevBlockInfo.blockGroup.node; + + prevBlockBeforePos = doc + .resolve(prevBlockInfo.blockGroup.beforePos + 1) + .posAtIndex(group.childCount - 1); + prevBlockInfo = getBlockInfoFromResolvedPos( + doc.resolve(prevBlockBeforePos) + ); + } + + return doc.resolve(prevBlockBeforePos); +}; + +const canMerge = ($prevBlockPos: ResolvedPos, $nextBlockPos: ResolvedPos) => { + const prevBlockInfo = getBlockInfoFromResolvedPos($prevBlockPos); + const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); + + return ( + prevBlockInfo.blockContent.node.type.spec.content === "inline*" && + nextBlockInfo.blockContent.node.type.spec.content === "inline*" && + prevBlockInfo.blockContent.node.childCount > 0 + ); +}; + +const mergeBlocks = ( + state: EditorState, + dispatch: ((args?: any) => any) | undefined, + $prevBlockPos: ResolvedPos, + $nextBlockPos: ResolvedPos +) => { + const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); + + // Un-nests all children of the next block. + if (nextBlockInfo.blockGroup) { + const childBlocksStart = state.doc.resolve( + nextBlockInfo.blockGroup.beforePos + 1 + ); + const childBlocksEnd = state.doc.resolve( + nextBlockInfo.blockGroup.afterPos - 1 + ); + const childBlocksRange = childBlocksStart.blockRange(childBlocksEnd); + + if (dispatch) { + state.tr.lift(childBlocksRange!, $nextBlockPos.depth); + } + } + + // Deletes the boundary between the two blocks. Can be thought of as + // removing the closing tags of the first block and the opening tags of the + // second one to stitch them together. + if (dispatch) { + const prevBlockInfo = getBlockInfoFromResolvedPos($prevBlockPos); + + dispatch( + state.tr.delete( + prevBlockInfo.blockContent.afterPos - 1, + nextBlockInfo.blockContent.beforePos + 1 + ) + ); + } + + return true; +}; + +export const mergeBlocksCommand = + (posBetweenBlocks: number) => + ({ + state, + dispatch, + }: { + state: EditorState; + dispatch: ((args?: any) => any) | undefined; + }) => { + const $nextBlockPos = state.doc.resolve(posBetweenBlocks); + const $prevBlockPos = getPrevBlockPos(state.doc, $nextBlockPos); + + if (!canMerge($prevBlockPos, $nextBlockPos)) { + return false; + } + + return mergeBlocks(state, dispatch, $prevBlockPos, $nextBlockPos); + }; diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap new file mode 100644 index 0000000000..558e863c78 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap @@ -0,0 +1,3767 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test moveBlockDown > Basic 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlockDown > Into children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlockDown > Last block 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlockDown > Out of children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlockUp > Basic 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlockUp > First block 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlockUp > Into children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlockUp > Out of children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts new file mode 100644 index 0000000000..fdaf6ee096 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts @@ -0,0 +1,192 @@ +import { NodeSelection, TextSelection } from "prosemirror-state"; +import { CellSelection } from "prosemirror-tables"; +import { describe, expect, it } from "vitest"; + +import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js"; +import { setupTestEnv } from "../../setupTestEnv.js"; +import { + moveBlockDown, + moveBlockUp, + moveSelectedBlockAndSelection, +} from "./moveBlock.js"; + +const getEditor = setupTestEnv(); + +function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { + const { blockContent } = getBlockInfoFromSelection( + getEditor()._tiptapEditor.state + ); + + if (selectionType === "cell") { + getEditor()._tiptapEditor.view.dispatch( + getEditor()._tiptapEditor.state.tr.setSelection( + CellSelection.create( + getEditor()._tiptapEditor.state.doc, + getEditor() + ._tiptapEditor.state.doc.resolve(blockContent.beforePos + 3) + .before(), + getEditor() + ._tiptapEditor.state.doc.resolve(blockContent.afterPos - 3) + .before() + ) + ) + ); + } else if (selectionType === "node") { + const resolvedContentStartPos = getEditor()._tiptapEditor.state.doc.resolve( + blockContent.beforePos + ); + + getEditor()._tiptapEditor.view.dispatch( + getEditor()._tiptapEditor.state.tr.setSelection( + NodeSelection.create( + getEditor()._tiptapEditor.state.doc, + getEditor() + ._tiptapEditor.state.doc.resolve( + resolvedContentStartPos.after(resolvedContentStartPos.depth + 1) + ) + .start() + ) + ) + ); + } else { + const resolvedContentStartPos = getEditor()._tiptapEditor.state.doc.resolve( + blockContent.beforePos + ); + const resolvedContentEndPos = getEditor()._tiptapEditor.state.doc.resolve( + blockContent.afterPos + ); + + getEditor()._tiptapEditor.view.dispatch( + getEditor()._tiptapEditor.state.tr.setSelection( + TextSelection.create( + getEditor()._tiptapEditor.state.doc, + getEditor() + ._tiptapEditor.state.doc.resolve( + resolvedContentStartPos.after(resolvedContentStartPos.depth + 1) + ) + .start(), + getEditor() + ._tiptapEditor.state.doc.resolve( + resolvedContentEndPos.before(resolvedContentEndPos.depth + 1) + ) + .end() + ) + ) + ); + } +} + +describe("Test moveSelectedBlockAndSelection", () => { + it("Text selection", () => { + getEditor().setTextCursorPosition("paragraph-1"); + makeSelectionSpanContent("text"); + + moveSelectedBlockAndSelection(getEditor(), "paragraph-0", "before"); + + const selection = getEditor()._tiptapEditor.state.selection; + getEditor().setTextCursorPosition("paragraph-1"); + makeSelectionSpanContent("text"); + + expect( + selection.eq(getEditor()._tiptapEditor.state.selection) + ).toBeTruthy(); + }); + + it("Node selection", () => { + getEditor().setTextCursorPosition("image-0"); + makeSelectionSpanContent("node"); + + moveSelectedBlockAndSelection(getEditor(), "paragraph-0", "before"); + + const selection = getEditor()._tiptapEditor.state.selection; + getEditor().setTextCursorPosition("image-0"); + makeSelectionSpanContent("node"); + + expect( + selection.eq(getEditor()._tiptapEditor.state.selection) + ).toBeTruthy(); + }); + + it("Cell selection", () => { + getEditor().setTextCursorPosition("table-0"); + makeSelectionSpanContent("cell"); + + moveSelectedBlockAndSelection(getEditor(), "paragraph-0", "before"); + + const selection = getEditor()._tiptapEditor.state.selection; + getEditor().setTextCursorPosition("table-0"); + makeSelectionSpanContent("cell"); + + expect( + selection.eq(getEditor()._tiptapEditor.state.selection) + ).toBeTruthy(); + }); +}); + +describe("Test moveBlockUp", () => { + it("Basic", () => { + getEditor().setTextCursorPosition("paragraph-1"); + + moveBlockUp(getEditor()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Into children", () => { + getEditor().setTextCursorPosition("paragraph-2"); + + moveBlockUp(getEditor()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Out of children", () => { + getEditor().setTextCursorPosition("nested-paragraph-1"); + + moveBlockUp(getEditor()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("First block", () => { + getEditor().setTextCursorPosition("paragraph-0"); + + moveBlockUp(getEditor()); + + expect(getEditor().document).toMatchSnapshot(); + }); +}); + +describe("Test moveBlockDown", () => { + it("Basic", () => { + getEditor().setTextCursorPosition("paragraph-0"); + + moveBlockDown(getEditor()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Into children", () => { + getEditor().setTextCursorPosition("paragraph-1"); + + moveBlockDown(getEditor()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Out of children", () => { + getEditor().setTextCursorPosition("nested-paragraph-1"); + + moveBlockDown(getEditor()); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Last block", () => { + getEditor().setTextCursorPosition("trailing-paragraph"); + + moveBlockDown(getEditor()); + + expect(getEditor().document).toMatchSnapshot(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/moveBlock.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts similarity index 91% rename from packages/core/src/api/blockManipulation/moveBlock.ts rename to packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts index 598a6df8f6..89ed457b4e 100644 --- a/packages/core/src/api/blockManipulation/moveBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts @@ -1,10 +1,10 @@ import { NodeSelection, Selection, TextSelection } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; -import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import { BlockIdentifier } from "../../schema/index.js"; -import { getBlockInfoFromPos } from "../getBlockInfoFromPos.js"; -import { getNodeById } from "../nodeUtil.js"; +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; +import { BlockIdentifier } from "../../../../schema/index.js"; +import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js"; +import { getNodeById } from "../../../nodeUtil.js"; type BlockSelectionData = ( | { @@ -31,14 +31,13 @@ type BlockSelectionData = ( function getBlockSelectionData( editor: BlockNoteEditor ): BlockSelectionData { - const { id, startPos } = getBlockInfoFromPos( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.selection.from + const { blockContainer } = getBlockInfoFromSelection( + editor._tiptapEditor.state ); const selectionData = { - blockId: id, - blockPos: startPos - 1, + blockId: blockContainer.node.attrs.id, + blockPos: blockContainer.beforePos, }; if (editor._tiptapEditor.state.selection instanceof CellSelection) { diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap new file mode 100644 index 0000000000..68c3cbfa1b --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap @@ -0,0 +1,1136 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test removeBlocks > Remove multiple consecutive blocks 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test removeBlocks > Remove multiple non-consecutive blocks 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test removeBlocks > Remove single block 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.ts new file mode 100644 index 0000000000..55625e11d7 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; + +import { setupTestEnv } from "../../setupTestEnv.js"; +import { removeBlocks } from "./removeBlocks.js"; + +const getEditor = setupTestEnv(); + +describe("Test removeBlocks", () => { + it("Remove single block", () => { + removeBlocks(getEditor(), ["paragraph-0"]); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Remove multiple consecutive blocks", () => { + removeBlocks(getEditor(), [ + "paragraph-0", + "paragraph-1", + "paragraph-with-children", + ]); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Remove multiple non-consecutive blocks", () => { + removeBlocks(getEditor(), [ + "paragraph-0", + "table-0", + "heading-with-everything", + ]); + + expect(getEditor().document).toMatchSnapshot(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts new file mode 100644 index 0000000000..550e20ecc4 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts @@ -0,0 +1,100 @@ +import { Node } from "prosemirror-model"; +import { Transaction } from "prosemirror-state"; + +import { Block } from "../../../../blocks/defaultBlocks.js"; +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; +import { + BlockIdentifier, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../../../schema/index.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; + +export function removeBlocksWithCallback< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + blocksToRemove: BlockIdentifier[], + // Should return new removedSize. + callback?: ( + node: Node, + pos: number, + tr: Transaction, + removedSize: number + ) => number +): Block[] { + const ttEditor = editor._tiptapEditor; + const tr = ttEditor.state.tr; + + const idsOfBlocksToRemove = new Set( + blocksToRemove.map((block) => + typeof block === "string" ? block : block.id + ) + ); + const removedBlocks: Block[] = []; + let removedSize = 0; + + ttEditor.state.doc.descendants((node, pos) => { + // Skips traversing nodes after all target blocks have been removed. + if (idsOfBlocksToRemove.size === 0) { + return false; + } + + // Keeps traversing nodes if block with target ID has not been found. + if ( + node.type.name !== "blockContainer" || + !idsOfBlocksToRemove.has(node.attrs.id) + ) { + return true; + } + + // Saves the block that is being deleted. + removedBlocks.push( + nodeToBlock( + node, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ) + ); + idsOfBlocksToRemove.delete(node.attrs.id); + + // Removes the block and calculates the change in document size. + removedSize = callback?.(node, pos, tr, removedSize) || removedSize; + const oldDocSize = tr.doc.nodeSize; + tr.delete(pos - removedSize - 1, pos - removedSize + node.nodeSize + 1); + const newDocSize = tr.doc.nodeSize; + removedSize += oldDocSize - newDocSize; + + return false; + }); + + // Throws an error if now all blocks could be found. + if (idsOfBlocksToRemove.size > 0) { + const notFoundIds = [...idsOfBlocksToRemove].join("\n"); + + throw Error( + "Blocks with the following IDs could not be found in the editor: " + + notFoundIds + ); + } + + editor.dispatch(tr); + + return removedBlocks; +} + +export function removeBlocks< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + blocksToRemove: BlockIdentifier[] +): Block[] { + return removeBlocksWithCallback(editor, blocksToRemove); +} diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap new file mode 100644 index 0000000000..add46c0280 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap @@ -0,0 +1,4931 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Remove multiple non-consecutive blocks 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Remove single block 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 1", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 3", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace multiple consecutive blocks with single basic 1`] = ` +[ + { + "children": [], + "content": [], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace multiple consecutive blocks with single complex 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "inserted-heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with multiple 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 1", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 3", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with single basic 1`] = ` +[ + { + "children": [], + "content": [], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with single complex 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "inserted-heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 1", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted paragraph 3", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` +[ + { + "children": [], + "content": [], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "inserted-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "inserted-heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts new file mode 100644 index 0000000000..09d4911a3b --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts @@ -0,0 +1,222 @@ +import { describe, expect, it } from "vitest"; + +import { setupTestEnv } from "../../setupTestEnv.js"; +import { replaceBlocks } from "./replaceBlocks.js"; + +const getEditor = setupTestEnv(); + +describe("Test replaceBlocks", () => { + it("Remove single block", () => { + replaceBlocks(getEditor(), ["paragraph-0"], []); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Remove multiple consecutive blocks", () => { + replaceBlocks( + getEditor(), + ["paragraph-0", "paragraph-1", "paragraph-with-children"], + [] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Remove multiple non-consecutive blocks", () => { + replaceBlocks( + getEditor(), + ["paragraph-0", "table-0", "heading-with-everything"], + [] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace single block with single basic", () => { + replaceBlocks(getEditor(), ["paragraph-0"], [{ type: "paragraph" }]); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace multiple consecutive blocks with single basic", () => { + replaceBlocks( + getEditor(), + ["paragraph-0", "paragraph-1", "paragraph-with-children"], + [{ type: "paragraph" }] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace multiple non-consecutive blocks with single basic", () => { + replaceBlocks( + getEditor(), + ["paragraph-0", "table-0", "heading-with-everything"], + [{ type: "paragraph" }] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace single block with multiple", () => { + replaceBlocks( + getEditor(), + ["paragraph-0"], + [ + { type: "paragraph", content: "Inserted paragraph 1" }, + { type: "paragraph", content: "Inserted paragraph 2" }, + { type: "paragraph", content: "Inserted paragraph 3" }, + ] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace multiple consecutive blocks with multiple", () => { + replaceBlocks( + getEditor(), + ["paragraph-0", "paragraph-1", "paragraph-with-children"], + [ + { type: "paragraph", content: "Inserted paragraph 1" }, + { type: "paragraph", content: "Inserted paragraph 2" }, + { type: "paragraph", content: "Inserted paragraph 3" }, + ] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace multiple non-consecutive blocks with multiple", () => { + replaceBlocks( + getEditor(), + ["paragraph-0", "table-0", "heading-with-everything"], + [ + { type: "paragraph", content: "Inserted paragraph 1" }, + { type: "paragraph", content: "Inserted paragraph 2" }, + { type: "paragraph", content: "Inserted paragraph 3" }, + ] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace single block with single complex", () => { + replaceBlocks( + getEditor(), + ["paragraph-0"], + [ + { + id: "inserted-heading-with-everything", + type: "heading", + props: { + backgroundColor: "red", + level: 2, + textAlignment: "center", + textColor: "red", + }, + content: [ + { type: "text", text: "Heading", styles: { bold: true } }, + { type: "text", text: " with styled ", styles: {} }, + { type: "text", text: "content", styles: { italic: true } }, + ], + children: [ + { + id: "inserted-nested-paragraph-2", + type: "paragraph", + content: "Nested Paragraph 2", + children: [ + { + id: "inserted-double-nested-paragraph-2", + type: "paragraph", + content: "Double Nested Paragraph 2", + }, + ], + }, + ], + }, + ] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace multiple consecutive blocks with single complex", () => { + replaceBlocks( + getEditor(), + ["paragraph-0", "paragraph-1", "paragraph-with-children"], + [ + { + id: "inserted-heading-with-everything", + type: "heading", + props: { + backgroundColor: "red", + level: 2, + textAlignment: "center", + textColor: "red", + }, + content: [ + { type: "text", text: "Heading", styles: { bold: true } }, + { type: "text", text: " with styled ", styles: {} }, + { type: "text", text: "content", styles: { italic: true } }, + ], + children: [ + { + id: "inserted-nested-paragraph-2", + type: "paragraph", + content: "Nested Paragraph 2", + children: [ + { + id: "inserted-double-nested-paragraph-2", + type: "paragraph", + content: "Double Nested Paragraph 2", + }, + ], + }, + ], + }, + ] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Replace multiple non-consecutive blocks with single complex", () => { + replaceBlocks( + getEditor(), + ["paragraph-0", "table-0", "heading-with-everything"], + [ + { + id: "inserted-heading-with-everything", + type: "heading", + props: { + backgroundColor: "red", + level: 2, + textAlignment: "center", + textColor: "red", + }, + content: [ + { type: "text", text: "Heading", styles: { bold: true } }, + { type: "text", text: " with styled ", styles: {} }, + { type: "text", text: "content", styles: { italic: true } }, + ], + children: [ + { + id: "inserted-nested-paragraph-2", + type: "paragraph", + content: "Nested Paragraph 2", + children: [ + { + id: "inserted-double-nested-paragraph-2", + type: "paragraph", + content: "Double Nested Paragraph 2", + }, + ], + }, + ], + }, + ] + ); + + expect(getEditor().document).toMatchSnapshot(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts new file mode 100644 index 0000000000..536c8b5d89 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -0,0 +1,70 @@ +import { Node } from "prosemirror-model"; + +import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; +import { + BlockIdentifier, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../../../schema/index.js"; +import { blockToNode } from "../../../nodeConversions/blockToNode.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; +import { removeBlocksWithCallback } from "../removeBlocks/removeBlocks.js"; + +export function replaceBlocks< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + blocksToRemove: BlockIdentifier[], + blocksToInsert: PartialBlock[] +): { + insertedBlocks: Block[]; + removedBlocks: Block[]; +} { + const nodesToInsert: Node[] = []; + for (const block of blocksToInsert) { + nodesToInsert.push( + blockToNode(block, editor.pmSchema, editor.schema.styleSchema) + ); + } + + const idOfFirstBlock = + typeof blocksToRemove[0] === "string" + ? blocksToRemove[0] + : blocksToRemove[0].id; + const removedBlocks = removeBlocksWithCallback( + editor, + blocksToRemove, + (node, pos, tr, removedSize) => { + if (node.attrs.id === idOfFirstBlock) { + const oldDocSize = tr.doc.nodeSize; + tr.insert(pos, nodesToInsert); + const newDocSize = tr.doc.nodeSize; + + return removedSize + oldDocSize - newDocSize; + } + + return removedSize; + } + ); + + // Now that the `PartialBlock`s have been converted to nodes, we can + // re-convert them into full `Block`s. + const insertedBlocks: Block[] = []; + for (const node of nodesToInsert) { + insertedBlocks.push( + nodeToBlock( + node, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ) + ); + } + + return { insertedBlocks, removedBlocks }; +} diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap new file mode 100644 index 0000000000..d308e00467 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap @@ -0,0 +1,2924 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test splitBlocks > Basic 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Para", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "graph 0", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test splitBlocks > Block has children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Para", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "graph with children", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test splitBlocks > Don't keep props 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Para", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "graph with props", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test splitBlocks > Don't keep type 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Head", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "ing 1", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test splitBlocks > End of content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test splitBlocks > Keep type 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Head", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "ing 1", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts new file mode 100644 index 0000000000..5d074ee1c8 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -0,0 +1,136 @@ +import { Node } from "prosemirror-model"; +import { TextSelection } from "prosemirror-state"; +import { describe, expect, it } from "vitest"; + +import { + getBlockInfo, + getBlockInfoFromSelection, +} from "../../../getBlockInfoFromPos.js"; +import { getNodeById } from "../../../nodeUtil.js"; +import { setupTestEnv } from "../../setupTestEnv.js"; +import { splitBlockCommand } from "./splitBlock.js"; + +const getEditor = setupTestEnv(); + +function splitBlock( + posInBlock: number, + keepType?: boolean, + keepProps?: boolean +) { + getEditor()._tiptapEditor.commands.command( + splitBlockCommand(posInBlock, keepType, keepProps) + ); +} + +function setSelectionWithOffset( + doc: Node, + targetBlockId: string, + offset: number +) { + const posInfo = getNodeById(targetBlockId, doc); + const { blockContent } = getBlockInfo(posInfo); + + getEditor()._tiptapEditor.view.dispatch( + getEditor()._tiptapEditor.state.tr.setSelection( + TextSelection.create(doc, blockContent.beforePos + offset + 1) + ) + ); +} + +describe("Test splitBlocks", () => { + it("Basic", () => { + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-0", + 4 + ); + + splitBlock(getEditor()._tiptapEditor.state.selection.anchor); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("End of content", () => { + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-0", + 11 + ); + + splitBlock(getEditor()._tiptapEditor.state.selection.anchor); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Block has children", () => { + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-with-children", + 4 + ); + + splitBlock(getEditor()._tiptapEditor.state.selection.anchor); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Keep type", () => { + setSelectionWithOffset(getEditor()._tiptapEditor.state.doc, "heading-0", 4); + + splitBlock(getEditor()._tiptapEditor.state.selection.anchor, true); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Don't keep type", () => { + setSelectionWithOffset(getEditor()._tiptapEditor.state.doc, "heading-0", 4); + + splitBlock(getEditor()._tiptapEditor.state.selection.anchor, false); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it.skip("Keep props", () => { + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-with-props", + 4 + ); + + splitBlock(getEditor()._tiptapEditor.state.selection.anchor, false, true); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Don't keep props", () => { + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-with-props", + 4 + ); + + splitBlock(getEditor()._tiptapEditor.state.selection.anchor, false, false); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Selection is set", () => { + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-0", + 4 + ); + + splitBlock(getEditor()._tiptapEditor.state.selection.anchor); + + const { blockContainer } = getBlockInfoFromSelection( + getEditor()._tiptapEditor.state + ); + + const anchorIsAtStartOfNewBlock = + blockContainer.node.attrs.id === "0" && + getEditor()._tiptapEditor.state.selection.$anchor.parentOffset === 0; + + expect(anchorIsAtStartOfNewBlock).toBeTruthy(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts new file mode 100644 index 0000000000..07df1a9a52 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -0,0 +1,48 @@ +import { EditorState } from "prosemirror-state"; + +import { + getBlockInfo, + getNearestBlockContainerPos, +} from "../../../getBlockInfoFromPos.js"; + +export const splitBlockCommand = ( + posInBlock: number, + keepType?: boolean, + keepProps?: boolean +) => { + return ({ + state, + dispatch, + }: { + state: EditorState; + dispatch: ((args?: any) => any) | undefined; + }) => { + const nearestBlockContainerPos = getNearestBlockContainerPos( + state.doc, + posInBlock + ); + + const { blockContainer, blockContent } = getBlockInfo( + nearestBlockContainerPos + ); + + const types = [ + { + type: blockContainer.node.type, // always keep blockcontainer type + attrs: keepProps ? { ...blockContainer.node.attrs, id: undefined } : {}, + }, + { + type: keepType + ? blockContent.node.type + : state.schema.nodes["paragraph"], + attrs: keepProps ? { ...blockContent.node.attrs } : {}, + }, + ]; + + if (dispatch) { + state.tr.split(posInBlock, 2, types); + } + + return true; + }; +}; diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap new file mode 100644 index 0000000000..a069e613a3 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap @@ -0,0 +1,8376 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test updateBlock > Revert all props 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Revert single prop 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 1, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update all props 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "blue", + "level": 3, + "textAlignment": "right", + "textColor": "blue", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "New double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "new-double-nested-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "New nested Paragraph 2", + "type": "text", + }, + ], + "id": "new-nested-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update inline content to empty table content 1`] = ` +[ + { + "children": [], + "content": { + "rows": [], + "type": "tableContent", + }, + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update inline content to no content 1`] = ` +[ + { + "children": [], + "content": undefined, + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update inline content to table content 1`] = ` +[ + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update no content to empty inline content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "image-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update no content to empty table content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [], + "type": "tableContent", + }, + "id": "image-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update no content to inline content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph", + "type": "text", + }, + ], + "id": "image-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update no content to table content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "image-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update single prop 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 3, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update table content to empty inline content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "table-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update table content to inline content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph", + "type": "text", + }, + ], + "id": "table-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update table content to no content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "table-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update type 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update with plain content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "New content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update with styled content 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "rows": [ + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + ], + }, + { + "cells": [ + [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "backgroundColor": "blue", + }, + "text": "New", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "backgroundColor": "blue", + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts new file mode 100644 index 0000000000..bf548f9141 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts @@ -0,0 +1,300 @@ +import { describe, expect, it } from "vitest"; + +import { setupTestEnv } from "../../setupTestEnv.js"; +import { updateBlock } from "./updateBlock.js"; + +const getEditor = setupTestEnv(); + +describe("Test updateBlock typing", () => { + it("Type is inferred correctly", () => { + try { + getEditor().updateBlock( + { id: "placeholder-id" }, + { + // @ts-expect-error invalid type + type: "non-existing", + } + ); + } catch (e) { + // ID doesn't exist, which is fine - this is a compile-time check + } + }); + + it("Props are inferred correctly", () => { + try { + getEditor().updateBlock( + { id: "placeholder-id" }, + { + type: "paragraph", + props: { + // @ts-expect-error invalid type + level: 1, + }, + } + ); + } catch (e) { + // ID doesn't exist, which is fine - this is a compile-time check + } + try { + getEditor().updateBlock( + { id: "placeholder-id" }, + { + type: "heading", + props: { + level: 1, + }, + } + ); + } catch (e) { + // ID doesn't exist, which is fine - this is a compile-time check + } + }); +}); + +describe("Test updateBlock", () => { + it.skip("Update ID", () => { + updateBlock(getEditor(), "heading-with-everything", { + id: "new-id", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update type", () => { + updateBlock(getEditor(), "heading-with-everything", { + type: "paragraph", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update single prop", () => { + updateBlock(getEditor(), "heading-with-everything", { + props: { + level: 3, + }, + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update all props", () => { + updateBlock(getEditor(), "heading-with-everything", { + props: { + backgroundColor: "blue", + level: 3, + textAlignment: "right", + textColor: "blue", + }, + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Revert single prop", () => { + updateBlock(getEditor(), "heading-with-everything", { + props: { + level: undefined, + }, + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Revert all props", () => { + updateBlock(getEditor(), "heading-with-everything", { + props: { + backgroundColor: undefined, + level: undefined, + textAlignment: undefined, + textColor: undefined, + }, + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update with plain content", () => { + updateBlock(getEditor(), "heading-with-everything", { + content: "New content", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update with styled content", () => { + updateBlock(getEditor(), "heading-with-everything", { + content: [ + { type: "text", text: "New", styles: { backgroundColor: "blue" } }, + { type: "text", text: " ", styles: {} }, + { type: "text", text: "content", styles: { backgroundColor: "blue" } }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update children", () => { + updateBlock(getEditor(), "heading-with-everything", { + children: [ + { + id: "new-nested-paragraph", + type: "paragraph", + content: "New nested Paragraph 2", + children: [ + { + id: "new-double-nested-paragraph", + type: "paragraph", + content: "New double Nested Paragraph 2", + }, + ], + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it.skip("Update everything", () => { + updateBlock(getEditor(), "heading-with-everything", { + id: "new-id", + type: "paragraph", + props: { + backgroundColor: "blue", + textAlignment: "right", + textColor: "blue", + }, + content: [ + { type: "text", text: "New", styles: { backgroundColor: "blue" } }, + { type: "text", text: " ", styles: {} }, + { type: "text", text: "content", styles: { backgroundColor: "blue" } }, + ], + children: [ + { + id: "new-nested-paragraph", + type: "paragraph", + content: "New nested Paragraph 2", + children: [ + { + id: "new-double-nested-paragraph", + type: "paragraph", + content: "New double Nested Paragraph 2", + }, + ], + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update inline content to empty table content", () => { + updateBlock(getEditor(), "paragraph-0", { + type: "table", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update table content to empty inline content", () => { + updateBlock(getEditor(), "table-0", { + type: "paragraph", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update inline content to table content", () => { + updateBlock(getEditor(), "paragraph-0", { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: ["Cell 1", "Cell 2", "Cell 3"], + }, + { + cells: ["Cell 4", "Cell 5", "Cell 6"], + }, + { + cells: ["Cell 7", "Cell 8", "Cell 9"], + }, + ], + }, + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update table content to inline content", () => { + updateBlock(getEditor(), "table-0", { + type: "paragraph", + content: "Paragraph", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update inline content to no content", () => { + updateBlock(getEditor(), "paragraph-0", { + type: "image", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update no content to empty inline content", () => { + updateBlock(getEditor(), "image-0", { + type: "paragraph", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update no content to inline content", () => { + updateBlock(getEditor(), "image-0", { + type: "paragraph", + content: "Paragraph", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update no content to empty table content", () => { + updateBlock(getEditor(), "image-0", { + type: "table", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update no content to table content", () => { + updateBlock(getEditor(), "image-0", { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: ["Cell 1", "Cell 2", "Cell 3"], + }, + { + cells: ["Cell 4", "Cell 5", "Cell 6"], + }, + { + cells: ["Cell 7", "Cell 8", "Cell 9"], + }, + ], + }, + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update table content to no content", () => { + updateBlock(getEditor(), "table-0", { + type: "image", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts new file mode 100644 index 0000000000..4055416092 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -0,0 +1,199 @@ +import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; +import { EditorState } from "prosemirror-state"; + +import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; +import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; +import { + BlockIdentifier, + BlockSchema, +} from "../../../../schema/blocks/types.js"; +import { InlineContentSchema } from "../../../../schema/inlineContent/types.js"; +import { StyleSchema } from "../../../../schema/styles/types.js"; +import { UnreachableCaseError } from "../../../../util/typescript.js"; +import { getBlockInfoFromResolvedPos } from "../../../getBlockInfoFromPos.js"; +import { + blockToNode, + inlineContentToNodes, + tableContentToNodes, +} from "../../../nodeConversions/blockToNode.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; +import { getNodeById } from "../../../nodeUtil.js"; + +export const updateBlockCommand = + < + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema + >( + editor: BlockNoteEditor, + posBeforeBlock: number, + block: PartialBlock + ) => + ({ + state, + dispatch, + }: { + state: EditorState; + dispatch: ((args?: any) => any) | undefined; + }) => { + const { blockContainer, blockContent, blockGroup } = + getBlockInfoFromResolvedPos(state.doc.resolve(posBeforeBlock)); + + if (dispatch) { + // Adds blockGroup node with child blocks if necessary. + if (block.children !== undefined) { + const childNodes = []; + + // Creates ProseMirror nodes for each child block, including their descendants. + for (const child of block.children) { + childNodes.push( + blockToNode(child, state.schema, editor.schema.styleSchema) + ); + } + + // Checks if a blockGroup node already exists. + if (blockGroup) { + // Replaces all child nodes in the existing blockGroup with the ones created earlier. + state.tr.replace( + blockGroup.beforePos + 1, + blockGroup.afterPos - 1, + new Slice(Fragment.from(childNodes), 0, 0) + ); + } else { + // Inserts a new blockGroup containing the child nodes created earlier. + state.tr.insert( + blockContent.afterPos, + state.schema.nodes["blockGroup"].create({}, childNodes) + ); + } + } + + const oldType = blockContent.node.type.name; + const newType = block.type || oldType; + + // The code below determines the new content of the block. + // or "keep" to keep as-is + let content: PMNode[] | "keep" = "keep"; + + // Has there been any custom content provided? + if (block.content) { + if (typeof block.content === "string") { + // Adds a single text node with no marks to the content. + content = inlineContentToNodes( + [block.content], + state.schema, + editor.schema.styleSchema + ); + } else if (Array.isArray(block.content)) { + // Adds a text node with the provided styles converted into marks to the content, + // for each InlineContent object. + content = inlineContentToNodes( + block.content, + state.schema, + editor.schema.styleSchema + ); + } else if (block.content.type === "tableContent") { + content = tableContentToNodes( + block.content, + state.schema, + editor.schema.styleSchema + ); + } else { + throw new UnreachableCaseError(block.content.type); + } + } else { + // no custom content has been provided, use existing content IF possible + + // Since some block types contain inline content and others don't, + // we either need to call setNodeMarkup to just update type & + // attributes, or replaceWith to replace the whole blockContent. + const oldContentType = state.schema.nodes[oldType].spec.content; + const newContentType = state.schema.nodes[newType].spec.content; + + if (oldContentType === "") { + // keep old content, because it's empty anyway and should be compatible with + // any newContentType + } else if (newContentType !== oldContentType) { + // the content type changed, replace the previous content + content = []; + } else { + // keep old content, because the content type is the same and should be compatible + } + } + + // Now, changes the blockContent node type and adds the provided props + // as attributes. Also preserves all existing attributes that are + // compatible with the new type. + // + // Use either setNodeMarkup or replaceWith depending on whether the + // content is being replaced or not. + if (content === "keep") { + // use setNodeMarkup to only update the type and attributes + state.tr.setNodeMarkup( + blockContent.beforePos, + block.type === undefined ? undefined : state.schema.nodes[block.type], + { + ...blockContent.node.attrs, + ...block.props, + } + ); + } else { + // use replaceWith to replace the content and the block itself + // also reset the selection since replacing the block content + // sets it to the next block. + state.tr.replaceWith( + blockContent.beforePos, + blockContent.afterPos, + state.schema.nodes[newType].create( + { + ...blockContent.node.attrs, + ...block.props, + }, + content + ) + ); + } + + // Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing + // attributes. + state.tr.setNodeMarkup(blockContainer.beforePos, undefined, { + ...blockContainer.node.attrs, + ...block.props, + }); + } + + return true; + }; + +export function updateBlock< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + blockToUpdate: BlockIdentifier, + update: PartialBlock +): Block { + const ttEditor = editor._tiptapEditor; + + const id = + typeof blockToUpdate === "string" ? blockToUpdate : blockToUpdate.id; + const { posBeforeNode } = getNodeById(id, ttEditor.state.doc); + + ttEditor.commands.command(({ state, dispatch }) => { + updateBlockCommand(editor, posBeforeNode, update)({ state, dispatch }); + return true; + }); + + const blockContainerNode = ttEditor.state.doc + .resolve(posBeforeNode + 1) // TODO: clean? + .node(); + + return nodeToBlock( + blockContainerNode, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ); +} diff --git a/packages/core/src/api/blockManipulation/insertContentAt.ts b/packages/core/src/api/blockManipulation/insertContentAt.ts new file mode 100644 index 0000000000..64791083d3 --- /dev/null +++ b/packages/core/src/api/blockManipulation/insertContentAt.ts @@ -0,0 +1,96 @@ +import { selectionToInsertionEnd } from "@tiptap/core"; +import { Node } from "prosemirror-model"; + +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import { + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../schema/index.js"; + +// similar to tiptap insertContentAt +export function insertContentAt< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + position: any, + nodes: Node[], + editor: BlockNoteEditor, + options: { + updateSelection: boolean; + } = { updateSelection: true } +) { + const tr = editor._tiptapEditor.state.tr; + + // don’t dispatch an empty fragment because this can lead to strange errors + // if (content.toString() === "<>") { + // return true; + // } + + let { from, to } = + typeof position === "number" + ? { from: position, to: position } + : { from: position.from, to: position.to }; + + let isOnlyTextContent = true; + let isOnlyBlockContent = true; + // const nodes = isFragment(content) ? content : [content]; + + let text = ""; + + nodes.forEach((node) => { + // check if added node is valid + node.check(); + + if (isOnlyTextContent && node.isText && node.marks.length === 0) { + text += node.text; + } else { + isOnlyTextContent = false; + } + + isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false; + }); + + // check if we can replace the wrapping node by + // the newly inserted content + // example: + // replace an empty paragraph by an inserted image + // instead of inserting the image below the paragraph + if (from === to && isOnlyBlockContent) { + const { parent } = tr.doc.resolve(from); + const isEmptyTextBlock = + parent.isTextblock && !parent.type.spec.code && !parent.childCount; + + if (isEmptyTextBlock) { + from -= 1; + to += 1; + } + } + + // if there is only plain text we have to use `insertText` + // because this will keep the current marks + if (isOnlyTextContent) { + // if value is string, we can use it directly + // otherwise if it is an array, we have to join it + // if (Array.isArray(value)) { + // tr.insertText(value.map((v) => v.text || "").join(""), from, to); + // } else if (typeof value === "object" && !!value && !!value.text) { + // tr.insertText(value.text, from, to); + // } else { + // tr.insertText(value as string, from, to); + // } + tr.insertText(text, from, to); + } else { + tr.replaceWith(from, to, nodes); + } + + // set cursor at end of inserted content + if (options.updateSelection) { + selectionToInsertionEnd(tr, tr.steps.length - 1, -1); + } + + editor.dispatch(tr); + + return true; +} diff --git a/packages/core/src/api/blockManipulation/moveBlock.test.ts b/packages/core/src/api/blockManipulation/moveBlock.test.ts deleted file mode 100644 index 35fc4d59e2..0000000000 --- a/packages/core/src/api/blockManipulation/moveBlock.test.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { NodeSelection, TextSelection } from "prosemirror-state"; -import { CellSelection } from "prosemirror-tables"; -import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { PartialBlock } from "../../blocks/defaultBlocks.js"; -import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import { getBlockInfoFromPos } from "../getBlockInfoFromPos.js"; -import { - moveBlockDown, - moveBlockUp, - moveSelectedBlockAndSelection, -} from "./moveBlock.js"; - -const blocks: PartialBlock[] = [ - { - id: "paragraph-0", - type: "paragraph", - content: "Paragraph 0", - }, - { - id: "paragraph-1", - type: "paragraph", - content: "Paragraph 1", - children: [ - { - id: "nested-paragraph-1", - type: "paragraph", - content: "Nested Paragraph 1", - }, - ], - }, - { - id: "paragraph-2", - type: "paragraph", - content: "Paragraph 2", - }, - { - id: "paragraph-3", - type: "paragraph", - content: "Paragraph 3", - }, - { - id: "image-1", - type: "image", - props: { - url: "https://via.placeholder.com/150", - }, - }, - { - id: "table-1", - type: "table", - content: { - type: "tableContent", - rows: [ - { - cells: ["Cell 1", "Cell 2", "Cell 3"], - }, - { - cells: ["Cell 4", "Cell 5", "Cell 6"], - }, - { - cells: ["Cell 7", "Cell 8", "Cell 9"], - }, - ], - }, - }, - { - id: "trailing-paragraph", - type: "paragraph", - }, -]; - -let editor: BlockNoteEditor; -const div = document.createElement("div"); - -function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { - const { startPos, contentNode } = getBlockInfoFromPos( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.selection.from - ); - - if (selectionType === "cell") { - editor._tiptapEditor.view.dispatch( - editor._tiptapEditor.state.tr.setSelection( - CellSelection.create( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.doc.resolve(startPos + 3).before(), - editor._tiptapEditor.state.doc - .resolve(startPos + contentNode.nodeSize - 3) - .before() - ) - ) - ); - } else if (selectionType === "node") { - const resolvedContentStartPos = - editor._tiptapEditor.state.doc.resolve(startPos); - - editor._tiptapEditor.view.dispatch( - editor._tiptapEditor.state.tr.setSelection( - NodeSelection.create( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.doc - .resolve( - resolvedContentStartPos.after(resolvedContentStartPos.depth + 1) - ) - .start() - ) - ) - ); - } else { - const resolvedContentStartPos = - editor._tiptapEditor.state.doc.resolve(startPos); - const resolvedContentEndPos = editor._tiptapEditor.state.doc.resolve( - startPos + contentNode.nodeSize - ); - - editor._tiptapEditor.view.dispatch( - editor._tiptapEditor.state.tr.setSelection( - TextSelection.create( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.doc - .resolve( - resolvedContentStartPos.after(resolvedContentStartPos.depth + 1) - ) - .start(), - editor._tiptapEditor.state.doc - .resolve( - resolvedContentEndPos.before(resolvedContentEndPos.depth + 1) - ) - .end() - ) - ) - ); - } -} - -beforeAll(() => { - editor = BlockNoteEditor.create(); - editor.mount(div); -}); - -afterAll(() => { - editor.mount(undefined); - editor._tiptapEditor.destroy(); - editor = undefined as any; -}); - -beforeEach(() => { - editor.replaceBlocks(editor.document, blocks); -}); - -describe("Test moveSelectedBlockAndSelection", () => { - it("Text selection", () => { - editor.setTextCursorPosition("paragraph-2"); - makeSelectionSpanContent("text"); - - moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); - - const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("paragraph-2"); - makeSelectionSpanContent("text"); - - expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); - }); - - it("Node selection", () => { - editor.setTextCursorPosition("image-1"); - makeSelectionSpanContent("node"); - - moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); - - const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("image-1"); - makeSelectionSpanContent("node"); - - expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); - }); - - it("Cell selection", () => { - editor.setTextCursorPosition("table-1"); - makeSelectionSpanContent("cell"); - - moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); - - const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("table-1"); - makeSelectionSpanContent("cell"); - - expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); - }); -}); - -describe("Test moveBlockUp", () => { - it("Basic", () => { - editor.setTextCursorPosition("paragraph-3"); - - moveBlockUp(editor); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Into children", () => { - editor.setTextCursorPosition("paragraph-2"); - - moveBlockUp(editor); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Out of children", () => { - editor.setTextCursorPosition("nested-paragraph-1"); - - moveBlockUp(editor); - - expect(editor.document).toMatchSnapshot(); - }); - - it("First block", () => { - editor.setTextCursorPosition("paragraph-0"); - - moveBlockUp(editor); - - expect(editor.document).toMatchSnapshot(); - }); -}); - -describe("Test moveBlockDown", () => { - it("Basic", () => { - editor.setTextCursorPosition("paragraph-2"); - - moveBlockDown(editor); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Into children", () => { - editor.setTextCursorPosition("paragraph-0"); - - moveBlockDown(editor); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Out of children", () => { - editor.setTextCursorPosition("nested-paragraph-1"); - - moveBlockDown(editor); - - expect(editor.document).toMatchSnapshot(); - }); - - it("Last block", () => { - editor.setTextCursorPosition("trailing-paragraph"); - - moveBlockDown(editor); - - expect(editor.document).toMatchSnapshot(); - }); -}); diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap b/packages/core/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap new file mode 100644 index 0000000000..7e506a213e --- /dev/null +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap @@ -0,0 +1,316 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test getTextCursorPosition & setTextCursorPosition > Basic 1`] = ` +{ + "block": { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "nextBlock": { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "parentBlock": undefined, + "prevBlock": { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +} +`; + +exports[`Test getTextCursorPosition & setTextCursorPosition > First block 1`] = ` +{ + "block": { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "nextBlock": { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "parentBlock": undefined, + "prevBlock": undefined, +} +`; + +exports[`Test getTextCursorPosition & setTextCursorPosition > Last block 1`] = ` +{ + "block": { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "nextBlock": undefined, + "parentBlock": undefined, + "prevBlock": { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, +} +`; + +exports[`Test getTextCursorPosition & setTextCursorPosition > Nested block 1`] = ` +{ + "block": { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "nextBlock": undefined, + "parentBlock": { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "prevBlock": undefined, +} +`; diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts new file mode 100644 index 0000000000..f7ed692796 --- /dev/null +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; + +import { setupTestEnv } from "../../setupTestEnv.js"; +import { + getTextCursorPosition, + setTextCursorPosition, +} from "./textCursorPosition.js"; + +const getEditor = setupTestEnv(); + +describe("Test getTextCursorPosition & setTextCursorPosition", () => { + it("Basic", () => { + setTextCursorPosition(getEditor(), "paragraph-1"); + + expect(getTextCursorPosition(getEditor())).toMatchSnapshot(); + }); + + it("First block", () => { + setTextCursorPosition(getEditor(), "paragraph-0"); + + expect(getTextCursorPosition(getEditor())).toMatchSnapshot(); + }); + + it("Last block", () => { + setTextCursorPosition(getEditor(), "trailing-paragraph"); + + expect(getTextCursorPosition(getEditor())).toMatchSnapshot(); + }); + + it("Nested block", () => { + setTextCursorPosition(getEditor(), "nested-paragraph-0"); + + expect(getTextCursorPosition(getEditor())).toMatchSnapshot(); + }); + + it("Set to start", () => { + setTextCursorPosition(getEditor(), "paragraph-1", "start"); + + expect( + getEditor()._tiptapEditor.state.selection.$from.parentOffset === 0 + ).toBeTruthy(); + }); + + it("Set to end", () => { + setTextCursorPosition(getEditor(), "paragraph-1", "end"); + + expect( + getEditor()._tiptapEditor.state.selection.$from.parentOffset === + getEditor()._tiptapEditor.state.selection.$from.node().firstChild! + .nodeSize + ).toBeTruthy(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts new file mode 100644 index 0000000000..d7237b0b2c --- /dev/null +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -0,0 +1,130 @@ +import { Node } from "prosemirror-model"; + +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; +import { TextCursorPosition } from "../../../../editor/cursorPositionTypes.js"; +import { + BlockIdentifier, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../../../schema/index.js"; +import { UnreachableCaseError } from "../../../../util/typescript.js"; +import { + getBlockInfo, + getBlockInfoFromSelection, +} from "../../../getBlockInfoFromPos.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; +import { getNodeById } from "../../../nodeUtil.js"; + +export function getTextCursorPosition< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>(editor: BlockNoteEditor): TextCursorPosition { + const { blockContainer } = getBlockInfoFromSelection( + editor._tiptapEditor.state + ); + + const resolvedPos = editor._tiptapEditor.state.doc.resolve( + blockContainer.beforePos + ); + // Gets previous blockContainer node at the same nesting level, if the current node isn't the first child. + const prevNode = resolvedPos.nodeBefore; + + // Gets next blockContainer node at the same nesting level, if the current node isn't the last child. + const nextNode = editor._tiptapEditor.state.doc.resolve( + blockContainer.afterPos + ).nodeAfter; + + // Gets parent blockContainer node, if the current node is nested. + let parentNode: Node | undefined = undefined; + if (resolvedPos.depth > 1) { + parentNode = resolvedPos.node(resolvedPos.depth - 1); + } + + return { + block: nodeToBlock( + blockContainer.node, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ), + prevBlock: + prevNode === null + ? undefined + : nodeToBlock( + prevNode, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ), + nextBlock: + nextNode === null + ? undefined + : nodeToBlock( + nextNode, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ), + parentBlock: + parentNode === undefined + ? undefined + : nodeToBlock( + parentNode, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ), + }; +} + +export function setTextCursorPosition< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + targetBlock: BlockIdentifier, + placement: "start" | "end" = "start" +) { + const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id; + + const posInfo = getNodeById(id, editor._tiptapEditor.state.doc); + const { blockContent } = getBlockInfo(posInfo); + + const contentType: "none" | "inline" | "table" = + editor.schema.blockSchema[blockContent.node.type.name]!.content; + + if (contentType === "none") { + editor._tiptapEditor.commands.setNodeSelection(blockContent.beforePos); + return; + } + + if (contentType === "inline") { + if (placement === "start") { + editor._tiptapEditor.commands.setTextSelection( + blockContent.beforePos + 1 + ); + } else { + editor._tiptapEditor.commands.setTextSelection(blockContent.afterPos - 1); + } + } else if (contentType === "table") { + if (placement === "start") { + // Need to offset the position as we have to get through the `tableRow` + // and `tableCell` nodes to get to the `tableParagraph` node we want to + // set the selection in. + editor._tiptapEditor.commands.setTextSelection( + blockContent.beforePos + 4 + ); + } else { + editor._tiptapEditor.commands.setTextSelection(blockContent.afterPos - 4); + } + } else { + throw new UnreachableCaseError(contentType); + } +} diff --git a/packages/core/src/api/blockManipulation/setupTestEnv.ts b/packages/core/src/api/blockManipulation/setupTestEnv.ts new file mode 100644 index 0000000000..3182816494 --- /dev/null +++ b/packages/core/src/api/blockManipulation/setupTestEnv.ts @@ -0,0 +1,179 @@ +import { afterAll, beforeAll, beforeEach } from "vitest"; + +import { PartialBlock } from "../../blocks/defaultBlocks.js"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; + +export function setupTestEnv() { + let editor: BlockNoteEditor; + const div = document.createElement("div"); + + beforeAll(() => { + editor = BlockNoteEditor.create(); + editor.mount(div); + }); + + afterAll(() => { + editor.mount(undefined); + editor._tiptapEditor.destroy(); + editor = undefined as any; + }); + + beforeEach(() => { + editor.replaceBlocks(editor.document, testDocument); + }); + + return () => editor; +} + +const testDocument: PartialBlock[] = [ + { + id: "paragraph-0", + type: "paragraph", + content: "Paragraph 0", + }, + { + id: "paragraph-1", + type: "paragraph", + content: "Paragraph 1", + }, + { + id: "paragraph-with-children", + type: "paragraph", + content: "Paragraph with children", + children: [ + { + id: "nested-paragraph-0", + type: "paragraph", + content: "Nested Paragraph 0", + children: [ + { + id: "double-nested-paragraph-0", + type: "paragraph", + content: "Double Nested Paragraph 0", + }, + ], + }, + ], + }, + { + id: "paragraph-2", + type: "paragraph", + content: "Paragraph 2", + }, + { + id: "paragraph-with-props", + type: "paragraph", + props: { + textAlignment: "center", + textColor: "red", + }, + content: "Paragraph with props", + }, + { + id: "paragraph-3", + type: "paragraph", + content: "Paragraph 3", + }, + { + id: "paragraph-with-styled-content", + type: "paragraph", + content: [ + { type: "text", text: "Paragraph", styles: { bold: true } }, + { type: "text", text: " with styled ", styles: {} }, + { type: "text", text: "content", styles: { italic: true } }, + ], + }, + { + id: "paragraph-4", + type: "paragraph", + content: "Paragraph 4", + }, + { + id: "heading-0", + type: "heading", + content: "Heading 1", + }, + { + id: "paragraph-5", + type: "paragraph", + content: "Paragraph 5", + }, + { + id: "image-0", + type: "image", + props: { + url: "https://via.placeholder.com/150", + }, + }, + { + id: "paragraph-6", + type: "paragraph", + content: "Paragraph 6", + }, + { + id: "table-0", + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: ["Cell 1", "Cell 2", "Cell 3"], + }, + { + cells: ["Cell 4", "Cell 5", "Cell 6"], + }, + { + cells: ["Cell 7", "Cell 8", "Cell 9"], + }, + ], + }, + }, + { + id: "paragraph-7", + type: "paragraph", + content: "Paragraph 7", + }, + { + id: "empty-paragraph", + type: "paragraph", + content: "", + }, + { + id: "paragraph-8", + type: "paragraph", + content: "Paragraph 8", + }, + { + id: "heading-with-everything", + type: "heading", + props: { + backgroundColor: "red", + level: 2, + textAlignment: "center", + textColor: "red", + }, + content: [ + { type: "text", text: "Heading", styles: { bold: true } }, + { type: "text", text: " with styled ", styles: {} }, + { type: "text", text: "content", styles: { italic: true } }, + ], + children: [ + { + id: "nested-paragraph-1", + type: "paragraph", + content: "Nested Paragraph 1", + children: [ + { + id: "double-nested-paragraph-1", + type: "paragraph", + content: "Double Nested Paragraph 1", + }, + ], + }, + ], + }, + { + id: "trailing-paragraph", + type: "paragraph", + }, +]; diff --git a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts index f27ac25b93..c7944bf4a2 100644 --- a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts +++ b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts @@ -6,7 +6,10 @@ import { InlineContentSchema, StyleSchema, } from "../../../schema/index.js"; -import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; +import { + getBlockInfo, + getNearestBlockContainerPos, +} from "../../getBlockInfoFromPos.js"; import { acceptedMIMETypes } from "./acceptedMIMETypes.js"; function checkFileExtensionsMatch( @@ -132,14 +135,16 @@ export async function handleFileInsertion< return; } - const blockInfo = getBlockInfoFromPos( + const posInfo = getNearestBlockContainerPos( editor._tiptapEditor.state.doc, pos.pos ); + const blockInfo = getBlockInfo(posInfo); + insertedBlockId = editor.insertBlocks( [fileBlock], - blockInfo.id, + blockInfo.blockContainer.node.attrs.id, "after" )[0].id; } else { diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index 14d7fbad11..14df079029 100644 --- a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts +++ b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts @@ -18,7 +18,7 @@ import { fragmentToBlocks } from "../../nodeConversions/fragmentToBlocks.js"; import { contentNodeToInlineContent, contentNodeToTableContent, -} from "../../nodeConversions/nodeConversions.js"; +} from "../../nodeConversions/nodeToBlock.js"; async function fragmentToExternalHTML< BSchema extends BlockSchema, diff --git a/packages/core/src/api/exporters/html/util/sharedHTMLConversion.ts b/packages/core/src/api/exporters/html/util/sharedHTMLConversion.ts index 62e89a14a1..ac8ff29f14 100644 --- a/packages/core/src/api/exporters/html/util/sharedHTMLConversion.ts +++ b/packages/core/src/api/exporters/html/util/sharedHTMLConversion.ts @@ -11,7 +11,7 @@ import { UnreachableCaseError } from "../../../../util/typescript.js"; import { inlineContentToNodes, tableContentToNodes, -} from "../../../nodeConversions/nodeConversions.js"; +} from "../../../nodeConversions/blockToNode.js"; export function serializeInlineContent< BSchema extends BlockSchema, diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index 91a5c85435..e5f3562d28 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -1,114 +1,196 @@ -import { Node, NodeType } from "prosemirror-model"; +import { Node, ResolvedPos } from "prosemirror-model"; +import { EditorState } from "prosemirror-state"; -export type BlockInfoWithoutPositions = { - id: string; +type SingleBlockInfo = { node: Node; - contentNode: Node; - contentType: NodeType; - numChildBlocks: number; + beforePos: number; + afterPos: number; }; -export type BlockInfo = BlockInfoWithoutPositions & { - startPos: number; - endPos: number; - depth: number; +export type BlockInfo = { + blockContainer: SingleBlockInfo; + blockContent: SingleBlockInfo; + blockGroup?: SingleBlockInfo; }; /** - * Helper function for `getBlockInfoFromPos`, returns information regarding - * provided blockContainer node. - * @param blockContainer The blockContainer node to retrieve info for. + * Retrieves the position just before the nearest blockContainer node in a + * ProseMirror doc, relative to a position. If the position is within a + * blockContainer node or its descendants, the position just before it is + * returned. If the position is not within a blockContainer node or its + * descendants, the position just before the next closest blockContainer node + * is returned. If the position is beyond the last blockContainer, the position + * just before the last blockContainer is returned. + * @param doc The ProseMirror doc. + * @param pos An integer position in the document. + * @returns The position just before the nearest blockContainer node. */ -export function getBlockInfo(blockContainer: Node): BlockInfoWithoutPositions { - const id = blockContainer.attrs["id"]; - const contentNode = blockContainer.firstChild!; - const contentType = contentNode.type; - const numChildBlocks = - blockContainer.childCount === 2 ? blockContainer.lastChild!.childCount : 0; +export function getNearestBlockContainerPos(doc: Node, pos: number) { + const $pos = doc.resolve(pos); - return { - id, - node: blockContainer, - contentNode, - contentType, - numChildBlocks, - }; -} + // Checks if the position provided is already just before a blockContainer + // node, in which case we return the position. + if ($pos.nodeAfter && $pos.nodeAfter.type.name === "blockContainer") { + return { + posBeforeNode: $pos.pos, + node: $pos.nodeAfter, + }; + } -/** - * Retrieves information regarding the nearest blockContainer node in a - * ProseMirror doc, relative to a position. - * @param doc The ProseMirror doc. - * @param pos An integer position. - * @returns A BlockInfo object for the nearest blockContainer node. - */ -export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo { - // If the position is outside the outer block group, we need to move it to the - // nearest block. This happens when the collaboration plugin is active, where - // the selection is placed at the very end of the doc. - const outerBlockGroupStartPos = 1; - const outerBlockGroupEndPos = doc.nodeSize - 2; - if (pos <= outerBlockGroupStartPos) { - pos = outerBlockGroupStartPos + 1; - - while ( - doc.resolve(pos).parent.type.name !== "blockContainer" && - pos < outerBlockGroupEndPos - ) { - pos++; - } - } else if (pos >= outerBlockGroupEndPos) { - pos = outerBlockGroupEndPos - 1; - - while ( - doc.resolve(pos).parent.type.name !== "blockContainer" && - pos > outerBlockGroupStartPos - ) { - pos--; + // Checks the node containing the position and its ancestors until a + // blockContainer node is found and returned. + let depth = $pos.depth; + let node = $pos.node(depth); + while (depth > 0) { + if (node.type.name === "blockContainer") { + return { + posBeforeNode: $pos.before(depth), + node: node, + }; } - } - // This gets triggered when a node selection on a block is active, i.e. when - // you drag and drop a block. - if (doc.resolve(pos).parent.type.name === "blockGroup") { - pos++; + depth--; + node = $pos.node(depth); } - const $pos = doc.resolve(pos); + // If the position doesn't lie within a blockContainer node, we instead find + // the position of the next closest one. If the position is beyond the last + // blockContainer, we return the position of the last blockContainer. While + // running `doc.descendants` is expensive, this case should be very rarely + // triggered. However, it's possible for the position to sometimes be beyond + // the last blockContainer node. This is a problem specifically when using the + // collaboration plugin. + const allBlockContainerPositions: number[] = []; + doc.descendants((node, pos) => { + if (node.type.name === "blockContainer") { + allBlockContainerPositions.push(pos); + } + }); - const maxDepth = $pos.depth; - let node = $pos.node(maxDepth); - let depth = maxDepth; + // eslint-disable-next-line no-console + console.warn(`Position ${pos} is not within a blockContainer node.`); - // eslint-disable-next-line no-constant-condition - while (true) { - if (depth < 0) { - throw new Error( - "Could not find blockContainer node. This can only happen if the underlying BlockNote schema has been edited." - ); - } + const resolvedPos = doc.resolve( + allBlockContainerPositions.find((position) => position >= pos) || + allBlockContainerPositions[allBlockContainerPositions.length - 1] + ); + return { + posBeforeNode: resolvedPos.pos, + node: resolvedPos.nodeAfter!, + }; +} - if (node.type.name === "blockContainer") { - break; +/** + * Gets information regarding the ProseMirror nodes that make up a block in a + * BlockNote document. This includes the main `blockContainer` node, the + * `blockContent` node with the block's main body, and the optional `blockGroup` + * node which contains the block's children. As well as the nodes, also returns + * the ProseMirror positions just before & after each node. + * @param node The main `blockContainer` node that the block information should + * be retrieved from, + * @param blockContainerBeforePosOffset the position just before the + * `blockContainer` node in the document. + */ +export function getBlockInfoWithManualOffset( + node: Node, + blockContainerBeforePosOffset: number +): BlockInfo { + const blockContainerNode = node; + const blockContainerBeforePos = blockContainerBeforePosOffset; + const blockContainerAfterPos = + blockContainerBeforePos + blockContainerNode.nodeSize; + + const blockContainer: SingleBlockInfo = { + node: blockContainerNode, + beforePos: blockContainerBeforePos, + afterPos: blockContainerAfterPos, + }; + let blockContent: SingleBlockInfo | undefined = undefined; + let blockGroup: SingleBlockInfo | undefined = undefined; + + blockContainerNode.forEach((node, offset) => { + if (node.type.spec.group === "blockContent") { + // console.log(beforePos, offset); + const blockContentNode = node; + const blockContentBeforePos = blockContainerBeforePos + offset + 1; + const blockContentAfterPos = blockContentBeforePos + node.nodeSize; + + blockContent = { + node: blockContentNode, + beforePos: blockContentBeforePos, + afterPos: blockContentAfterPos, + }; + } else if (node.type.name === "blockGroup") { + const blockGroupNode = node; + const blockGroupBeforePos = blockContainerBeforePos + offset + 1; + const blockGroupAfterPos = blockGroupBeforePos + node.nodeSize; + + blockGroup = { + node: blockGroupNode, + beforePos: blockGroupBeforePos, + afterPos: blockGroupAfterPos, + }; } + }); - depth -= 1; - node = $pos.node(depth); + if (!blockContent) { + throw new Error( + `blockContainer node does not contain a blockContent node in its children: ${blockContainerNode}` + ); } - const { id, contentNode, contentType, numChildBlocks } = getBlockInfo(node); - - const startPos = $pos.start(depth); - const endPos = $pos.end(depth); - return { - id, - node, - contentNode, - contentType, - numChildBlocks, - startPos, - endPos, - depth, + blockContainer, + blockContent, + blockGroup, }; } + +/** + * Gets information regarding the ProseMirror nodes that make up a block in a + * BlockNote document. This includes the main `blockContainer` node, the + * `blockContent` node with the block's main body, and the optional `blockGroup` + * node which contains the block's children. As well as the nodes, also returns + * the ProseMirror positions just before & after each node. + * @param posInfo An object with the main `blockContainer` node that the block + * information should be retrieved from, and the position just before it in the + * document. + */ +export function getBlockInfo(posInfo: { posBeforeNode: number; node: Node }) { + return getBlockInfoWithManualOffset(posInfo.node, posInfo.posBeforeNode); +} + +/** + * Gets information regarding the ProseMirror nodes that make up a block from a + * resolved position just before the `blockContainer` node in the document that + * corresponds to it. + * @param resolvedPos The resolved position just before the `blockContainer` + * node. + */ +export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) { + if (!resolvedPos.nodeAfter) { + throw new Error( + `Attempted to get blockContainer node at position ${resolvedPos.pos} but a node at this position does not exist` + ); + } + if (resolvedPos.nodeAfter.type.name !== "blockContainer") { + throw new Error( + `Attempted to get blockContainer node at position ${resolvedPos.pos} but found node of different type ${resolvedPos.nodeAfter}` + ); + } + return getBlockInfoWithManualOffset(resolvedPos.nodeAfter, resolvedPos.pos); +} + +/** + * Gets information regarding the ProseMirror nodes that make up a block. The + * block chosen is the one currently containing the current ProseMirror + * selection. + * @param state The ProseMirror editor state. + */ +export function getBlockInfoFromSelection(state: EditorState) { + const posInfo = getNearestBlockContainerPos( + state.doc, + state.selection.anchor + ); + return getBlockInfo(posInfo); +} diff --git a/packages/core/src/api/getCurrentBlockContentType.ts b/packages/core/src/api/getCurrentBlockContentType.ts deleted file mode 100644 index b0345203f1..0000000000 --- a/packages/core/src/api/getCurrentBlockContentType.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Editor } from "@tiptap/core"; -import { getBlockInfoFromPos } from "./getBlockInfoFromPos.js"; - -// Used to get the content type of the block that the text cursor is in. This is -// a band-aid fix to prevent input rules and keyboard shortcuts from triggering -// in tables, but really those should be extended to work with block selections. -export const getCurrentBlockContentType = (editor: Editor) => { - const { contentType } = getBlockInfoFromPos( - editor.state.doc, - editor.state.selection.from - ); - - return contentType.spec.content; -}; diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts new file mode 100644 index 0000000000..f63ee09977 --- /dev/null +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -0,0 +1,257 @@ +import { Mark, Node, Schema } from "@tiptap/pm/model"; + +import UniqueID from "../../extensions/UniqueID/UniqueID.js"; +import type { + InlineContentSchema, + PartialCustomInlineContentFromConfig, + PartialInlineContent, + PartialLink, + PartialTableContent, + StyleSchema, + StyledText, +} from "../../schema"; + +import type { PartialBlock } from "../../blocks/defaultBlocks"; +import { + isPartialLinkInlineContent, + isStyledTextInlineContent, +} from "../../schema/inlineContent/types.js"; +import { UnreachableCaseError } from "../../util/typescript.js"; + +/** + * Convert a StyledText inline element to a + * prosemirror text node with the appropriate marks + */ +function styledTextToNodes( + styledText: StyledText, + schema: Schema, + styleSchema: T +): Node[] { + const marks: Mark[] = []; + + for (const [style, value] of Object.entries(styledText.styles)) { + const config = styleSchema[style]; + if (!config) { + throw new Error(`style ${style} not found in styleSchema`); + } + + if (config.propSchema === "boolean") { + marks.push(schema.mark(style)); + } else if (config.propSchema === "string") { + marks.push(schema.mark(style, { stringValue: value })); + } else { + throw new UnreachableCaseError(config.propSchema); + } + } + + return ( + styledText.text + // Splits text & line breaks. + .split(/(\n)/g) + // If the content ends with a line break, an empty string is added to the + // end, which this removes. + .filter((text) => text.length > 0) + // Converts text & line breaks to nodes. + .map((text) => { + if (text === "\n") { + return schema.nodes["hardBreak"].create(); + } else { + return schema.text(text, marks); + } + }) + ); +} + +/** + * Converts a Link inline content element to + * prosemirror text nodes with the appropriate marks + */ +function linkToNodes( + link: PartialLink, + schema: Schema, + styleSchema: StyleSchema +): Node[] { + const linkMark = schema.marks.link.create({ + href: link.href, + }); + + return styledTextArrayToNodes(link.content, schema, styleSchema).map( + (node) => { + if (node.type.name === "text") { + return node.mark([...node.marks, linkMark]); + } + + if (node.type.name === "hardBreak") { + return node; + } + throw new Error("unexpected node type"); + } + ); +} + +/** + * Converts an array of StyledText inline content elements to + * prosemirror text nodes with the appropriate marks + */ +function styledTextArrayToNodes( + content: string | StyledText[], + schema: Schema, + styleSchema: S +): Node[] { + const nodes: Node[] = []; + + if (typeof content === "string") { + nodes.push( + ...styledTextToNodes( + { type: "text", text: content, styles: {} }, + schema, + styleSchema + ) + ); + return nodes; + } + + for (const styledText of content) { + nodes.push(...styledTextToNodes(styledText, schema, styleSchema)); + } + return nodes; +} + +/** + * converts an array of inline content elements to prosemirror nodes + */ +export function inlineContentToNodes< + I extends InlineContentSchema, + S extends StyleSchema +>( + blockContent: PartialInlineContent, + schema: Schema, + styleSchema: S +): Node[] { + const nodes: Node[] = []; + + for (const content of blockContent) { + if (typeof content === "string") { + nodes.push(...styledTextArrayToNodes(content, schema, styleSchema)); + } else if (isPartialLinkInlineContent(content)) { + nodes.push(...linkToNodes(content, schema, styleSchema)); + } else if (isStyledTextInlineContent(content)) { + nodes.push(...styledTextArrayToNodes([content], schema, styleSchema)); + } else { + nodes.push( + blockOrInlineContentToContentNode(content, schema, styleSchema) + ); + } + } + return nodes; +} + +/** + * converts an array of inline content elements to prosemirror nodes + */ +export function tableContentToNodes< + I extends InlineContentSchema, + S extends StyleSchema +>( + tableContent: PartialTableContent, + schema: Schema, + styleSchema: StyleSchema +): Node[] { + const rowNodes: Node[] = []; + + for (const row of tableContent.rows) { + const columnNodes: Node[] = []; + for (const cell of row.cells) { + let pNode: Node; + if (!cell) { + pNode = schema.nodes["tableParagraph"].create({}); + } else if (typeof cell === "string") { + pNode = schema.nodes["tableParagraph"].create({}, schema.text(cell)); + } else { + const textNodes = inlineContentToNodes(cell, schema, styleSchema); + pNode = schema.nodes["tableParagraph"].create({}, textNodes); + } + + const cellNode = schema.nodes["tableCell"].create({}, pNode); + columnNodes.push(cellNode); + } + const rowNode = schema.nodes["tableRow"].create({}, columnNodes); + rowNodes.push(rowNode); + } + return rowNodes; +} + +function blockOrInlineContentToContentNode( + block: + | PartialBlock + | PartialCustomInlineContentFromConfig, + schema: Schema, + styleSchema: StyleSchema +) { + let contentNode: Node; + let type = block.type; + + // TODO: needed? came from previous code + if (type === undefined) { + type = "paragraph"; + } + + if (!schema.nodes[type]) { + throw new Error(`node type ${type} not found in schema`); + } + + if (!block.content) { + contentNode = schema.nodes[type].create(block.props); + } else if (typeof block.content === "string") { + const nodes = inlineContentToNodes([block.content], schema, styleSchema); + contentNode = schema.nodes[type].create(block.props, nodes); + } else if (Array.isArray(block.content)) { + const nodes = inlineContentToNodes(block.content, schema, styleSchema); + contentNode = schema.nodes[type].create(block.props, nodes); + } else if (block.content.type === "tableContent") { + const nodes = tableContentToNodes(block.content, schema, styleSchema); + contentNode = schema.nodes[type].create(block.props, nodes); + } else { + throw new UnreachableCaseError(block.content.type); + } + return contentNode; +} + +/** + * Converts a BlockNote block to a TipTap node. + */ +export function blockToNode( + block: PartialBlock, + schema: Schema, + styleSchema: StyleSchema +) { + let id = block.id; + + if (id === undefined) { + id = UniqueID.options.generateID(); + } + + const contentNode = blockOrInlineContentToContentNode( + block, + schema, + styleSchema + ); + + const children: Node[] = []; + + if (block.children) { + for (const child of block.children) { + children.push(blockToNode(child, schema, styleSchema)); + } + } + + const groupNode = schema.nodes["blockGroup"].create({}, children); + + return schema.nodes["blockContainer"].create( + { + id: id, + ...block.props, + }, + children.length > 0 ? [contentNode, groupNode] : contentNode + ); +} diff --git a/packages/core/src/api/nodeConversions/fragmentToBlocks.ts b/packages/core/src/api/nodeConversions/fragmentToBlocks.ts index dcbfeeb8b8..8b0cd21030 100644 --- a/packages/core/src/api/nodeConversions/fragmentToBlocks.ts +++ b/packages/core/src/api/nodeConversions/fragmentToBlocks.ts @@ -6,7 +6,7 @@ import { InlineContentSchema, StyleSchema, } from "../../schema/index.js"; -import { nodeToBlock } from "./nodeConversions.js"; +import { nodeToBlock } from "./nodeToBlock.js"; /** * Converts all Blocks within a fragment to BlockNote blocks. diff --git a/packages/core/src/api/nodeConversions/nodeConversions.test.ts b/packages/core/src/api/nodeConversions/nodeConversions.test.ts index b2a84c9311..b196af924a 100644 --- a/packages/core/src/api/nodeConversions/nodeConversions.test.ts +++ b/packages/core/src/api/nodeConversions/nodeConversions.test.ts @@ -11,7 +11,8 @@ import { addIdsToBlock, partialBlockToBlockForTesting, } from "../testUtil/partialBlockTestUtil.js"; -import { blockToNode, nodeToBlock } from "./nodeConversions.js"; +import { blockToNode } from "./blockToNode.js"; +import { nodeToBlock } from "./nodeToBlock.js"; function validateConversion( block: PartialBlock, diff --git a/packages/core/src/api/nodeConversions/nodeConversions.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts similarity index 58% rename from packages/core/src/api/nodeConversions/nodeConversions.ts rename to packages/core/src/api/nodeConversions/nodeToBlock.ts index 669cdac081..55cfa045a3 100644 --- a/packages/core/src/api/nodeConversions/nodeConversions.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -1,4 +1,4 @@ -import { Mark, Node, Schema } from "@tiptap/pm/model"; +import { Mark, Node } from "@tiptap/pm/model"; import UniqueID from "../../extensions/UniqueID/UniqueID.js"; import type { @@ -8,262 +8,19 @@ import type { InlineContent, InlineContentFromConfig, InlineContentSchema, - PartialCustomInlineContentFromConfig, - PartialInlineContent, - PartialLink, - PartialTableContent, StyleSchema, - StyledText, Styles, TableContent, } from "../../schema/index.js"; -import { getBlockInfo } from "../getBlockInfoFromPos.js"; +import { getBlockInfoWithManualOffset } from "../getBlockInfoFromPos.js"; -import type { Block, PartialBlock } from "../../blocks/defaultBlocks.js"; +import type { Block } from "../../blocks/defaultBlocks.js"; import { isLinkInlineContent, - isPartialLinkInlineContent, isStyledTextInlineContent, } from "../../schema/inlineContent/types.js"; import { UnreachableCaseError } from "../../util/typescript.js"; -/** - * Convert a StyledText inline element to a - * prosemirror text node with the appropriate marks - */ -function styledTextToNodes( - styledText: StyledText, - schema: Schema, - styleSchema: T -): Node[] { - const marks: Mark[] = []; - - for (const [style, value] of Object.entries(styledText.styles)) { - const config = styleSchema[style]; - if (!config) { - throw new Error(`style ${style} not found in styleSchema`); - } - - if (config.propSchema === "boolean") { - marks.push(schema.mark(style)); - } else if (config.propSchema === "string") { - marks.push(schema.mark(style, { stringValue: value })); - } else { - throw new UnreachableCaseError(config.propSchema); - } - } - - return ( - styledText.text - // Splits text & line breaks. - .split(/(\n)/g) - // If the content ends with a line break, an empty string is added to the - // end, which this removes. - .filter((text) => text.length > 0) - // Converts text & line breaks to nodes. - .map((text) => { - if (text === "\n") { - return schema.nodes["hardBreak"].create(); - } else { - return schema.text(text, marks); - } - }) - ); -} - -/** - * Converts a Link inline content element to - * prosemirror text nodes with the appropriate marks - */ -function linkToNodes( - link: PartialLink, - schema: Schema, - styleSchema: StyleSchema -): Node[] { - const linkMark = schema.marks.link.create({ - href: link.href, - }); - - return styledTextArrayToNodes(link.content, schema, styleSchema).map( - (node) => { - if (node.type.name === "text") { - return node.mark([...node.marks, linkMark]); - } - - if (node.type.name === "hardBreak") { - return node; - } - throw new Error("unexpected node type"); - } - ); -} - -/** - * Converts an array of StyledText inline content elements to - * prosemirror text nodes with the appropriate marks - */ -function styledTextArrayToNodes( - content: string | StyledText[], - schema: Schema, - styleSchema: S -): Node[] { - const nodes: Node[] = []; - - if (typeof content === "string") { - nodes.push( - ...styledTextToNodes( - { type: "text", text: content, styles: {} }, - schema, - styleSchema - ) - ); - return nodes; - } - - for (const styledText of content) { - nodes.push(...styledTextToNodes(styledText, schema, styleSchema)); - } - return nodes; -} - -/** - * converts an array of inline content elements to prosemirror nodes - */ -export function inlineContentToNodes< - I extends InlineContentSchema, - S extends StyleSchema ->( - blockContent: PartialInlineContent, - schema: Schema, - styleSchema: S -): Node[] { - const nodes: Node[] = []; - - for (const content of blockContent) { - if (typeof content === "string") { - nodes.push(...styledTextArrayToNodes(content, schema, styleSchema)); - } else if (isPartialLinkInlineContent(content)) { - nodes.push(...linkToNodes(content, schema, styleSchema)); - } else if (isStyledTextInlineContent(content)) { - nodes.push(...styledTextArrayToNodes([content], schema, styleSchema)); - } else { - nodes.push( - blockOrInlineContentToContentNode(content, schema, styleSchema) - ); - } - } - return nodes; -} - -/** - * converts an array of inline content elements to prosemirror nodes - */ -export function tableContentToNodes< - I extends InlineContentSchema, - S extends StyleSchema ->( - tableContent: PartialTableContent, - schema: Schema, - styleSchema: StyleSchema -): Node[] { - const rowNodes: Node[] = []; - - for (const row of tableContent.rows) { - const columnNodes: Node[] = []; - for (const cell of row.cells) { - let pNode: Node; - if (!cell) { - pNode = schema.nodes["tableParagraph"].create({}); - } else if (typeof cell === "string") { - pNode = schema.nodes["tableParagraph"].create({}, schema.text(cell)); - } else { - const textNodes = inlineContentToNodes(cell, schema, styleSchema); - pNode = schema.nodes["tableParagraph"].create({}, textNodes); - } - - const cellNode = schema.nodes["tableCell"].create({}, pNode); - columnNodes.push(cellNode); - } - const rowNode = schema.nodes["tableRow"].create({}, columnNodes); - rowNodes.push(rowNode); - } - return rowNodes; -} - -export function blockOrInlineContentToContentNode( - block: - | PartialBlock - | PartialCustomInlineContentFromConfig, - schema: Schema, - styleSchema: StyleSchema -) { - let contentNode: Node; - let type = block.type; - - // TODO: needed? came from previous code - if (type === undefined) { - type = "paragraph"; - } - - if (!schema.nodes[type]) { - throw new Error(`node type ${type} not found in schema`); - } - - if (!block.content) { - contentNode = schema.nodes[type].create(block.props); - } else if (typeof block.content === "string") { - const nodes = inlineContentToNodes([block.content], schema, styleSchema); - contentNode = schema.nodes[type].create(block.props, nodes); - } else if (Array.isArray(block.content)) { - const nodes = inlineContentToNodes(block.content, schema, styleSchema); - contentNode = schema.nodes[type].create(block.props, nodes); - } else if (block.content.type === "tableContent") { - const nodes = tableContentToNodes(block.content, schema, styleSchema); - contentNode = schema.nodes[type].create(block.props, nodes); - } else { - throw new UnreachableCaseError(block.content.type); - } - return contentNode; -} -/** - * Converts a BlockNote block to a TipTap node. - */ -export function blockToNode( - block: PartialBlock, - schema: Schema, - styleSchema: StyleSchema -) { - let id = block.id; - - if (id === undefined) { - id = UniqueID.options.generateID(); - } - - const contentNode = blockOrInlineContentToContentNode( - block, - schema, - styleSchema - ); - - const children: Node[] = []; - - if (block.children) { - for (const child of block.children) { - children.push(blockToNode(child, schema, styleSchema)); - } - } - - const groupNode = schema.nodes["blockGroup"].create({}, children); - - return schema.nodes["blockContainer"].create( - { - id: id, - ...block.props, - }, - children.length > 0 ? [contentNode, groupNode] : contentNode - ); -} - /** * Converts an internal (prosemirror) table node contentto a BlockNote Tablecontent */ @@ -566,9 +323,10 @@ export function nodeToBlock< return cachedBlock; } - const blockInfo = getBlockInfo(node); + const { blockContainer, blockContent, blockGroup } = + getBlockInfoWithManualOffset(node, 0); - let id = blockInfo.id; + let id = blockContainer.node.attrs.id; // Only used for blocks converted from other formats. if (id === null) { @@ -578,13 +336,13 @@ export function nodeToBlock< const props: any = {}; for (const [attr, value] of Object.entries({ ...node.attrs, - ...blockInfo.contentNode.attrs, + ...blockContent.node.attrs, })) { - const blockSpec = blockSchema[blockInfo.contentType.name]; + const blockSpec = blockSchema[blockContent.node.type.name]; if (!blockSpec) { throw Error( - "Block is of an unrecognized type: " + blockInfo.contentType.name + "Block is of an unrecognized type: " + blockContent.node.type.name ); } @@ -595,32 +353,32 @@ export function nodeToBlock< } } - const blockConfig = blockSchema[blockInfo.contentType.name]; + const blockConfig = blockSchema[blockContent.node.type.name]; const children: Block[] = []; - for (let i = 0; i < blockInfo.numChildBlocks; i++) { + blockGroup?.node.forEach((child) => { children.push( nodeToBlock( - node.lastChild!.child(i), + child, blockSchema, inlineContentSchema, styleSchema, blockCache ) ); - } + }); let content: Block["content"]; if (blockConfig.content === "inline") { content = contentNodeToInlineContent( - blockInfo.contentNode, + blockContent.node, inlineContentSchema, styleSchema ); } else if (blockConfig.content === "table") { content = contentNodeToTableContent( - blockInfo.contentNode, + blockContent.node, inlineContentSchema, styleSchema ); diff --git a/packages/core/src/api/parsers/html/parseHTML.ts b/packages/core/src/api/parsers/html/parseHTML.ts index 24852c47ec..04f8b0f02b 100644 --- a/packages/core/src/api/parsers/html/parseHTML.ts +++ b/packages/core/src/api/parsers/html/parseHTML.ts @@ -6,7 +6,7 @@ import { } from "../../../schema/index.js"; import { Block } from "../../../blocks/defaultBlocks.js"; -import { nodeToBlock } from "../../nodeConversions/nodeConversions.js"; +import { nodeToBlock } from "../../nodeConversions/nodeToBlock.js"; import { nestedListsToBlockNoteStructure } from "./util/nestedLists.js"; export async function HTMLToBlocks< BSchema extends BlockSchema, diff --git a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index 1c61bcf404..96eb8a004d 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -1,5 +1,6 @@ import { InputRule } from "@tiptap/core"; -import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; +import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -46,19 +47,27 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ return new InputRule({ find: new RegExp(`^(#{${level}})\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() - .BNUpdateBlock(state.selection.from, { - type: "heading", - props: { - level: level as any, - }, - }) + .command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "heading", + props: { + level: level as any, + }, + } + ) + ) // Removes the "#" character(s) used to set the heading. - .deleteRange({ from: range.from, to: range.to }); + .deleteRange({ from: range.from, to: range.to }) + .run(); }, }); }), @@ -68,48 +77,61 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { "Mod-Alt-1": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } - return this.editor.commands.BNUpdateBlock( - this.editor.state.selection.anchor, - { - type: "heading", - props: { - level: 1 as any, - }, - } + // call updateBlockCommand + return this.editor.commands.command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "heading", + props: { + level: 1 as any, + }, + } + ) ); }, "Mod-Alt-2": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } - return this.editor.commands.BNUpdateBlock( - this.editor.state.selection.anchor, - { - type: "heading", - props: { - level: 2 as any, - }, - } + return this.editor.commands.command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "heading", + props: { + level: 2 as any, + }, + } + ) ); }, "Mod-Alt-3": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } - return this.editor.commands.BNUpdateBlock( - this.editor.state.selection.anchor, - { - type: "heading", - props: { - level: 3 as any, - }, - } + return this.editor.commands.command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "heading", + props: { + level: 3 as any, + }, + } + ) ); }, }; diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts index 4d427b3a7f..3f19c403ac 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -1,5 +1,6 @@ import { InputRule } from "@tiptap/core"; -import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; +import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -26,15 +27,22 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`^[-+*]\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() - .BNUpdateBlock(state.selection.from, { - type: "bulletListItem", - props: {}, - }) + .command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "bulletListItem", + props: {}, + } + ) + ) // Removes the "-", "+", or "*" character used to set the list. .deleteRange({ from: range.from, to: range.to }); }, @@ -44,18 +52,22 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { - Enter: () => handleEnter(this.editor), + Enter: () => handleEnter(this.options.editor), "Mod-Shift-8": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } - return this.editor.commands.BNUpdateBlock( - this.editor.state.selection.anchor, - { - type: "bulletListItem", - props: {}, - } + return this.options.editor.commands.command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "bulletListItem", + props: {}, + } + ) ); }, }; diff --git a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts index da69ffb52f..d6df128556 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -1,5 +1,9 @@ import { InputRule } from "@tiptap/core"; -import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; +import { + getBlockInfoFromSelection, + getNearestBlockContainerPos, +} from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -44,17 +48,24 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`\\[\\s*\\]\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() - .BNUpdateBlock(state.selection.from, { - type: "checkListItem", - props: { - checked: false as any, - }, - }) + .command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "checkListItem", + props: { + checked: false as any, + }, + } + ) + ) // Removes the characters used to set the list. .deleteRange({ from: range.from, to: range.to }); }, @@ -62,17 +73,25 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`\\[[Xx]\\]\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(state); + + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() - .BNUpdateBlock(state.selection.from, { - type: "checkListItem", - props: { - checked: true as any, - }, - }) + .command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "checkListItem", + props: { + checked: true as any, + }, + } + ) + ) // Removes the characters used to set the list. .deleteRange({ from: range.from, to: range.to }); }, @@ -82,18 +101,22 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { - Enter: () => handleEnter(this.editor), + Enter: () => handleEnter(this.options.editor), "Mod-Shift-9": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(this.options.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } - return this.editor.commands.BNUpdateBlock( - this.editor.state.selection.anchor, - { - type: "checkListItem", - props: {}, - } + return this.editor.commands.command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "checkListItem", + props: {}, + } + ) ); }, }; @@ -211,13 +234,24 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ return; } + // TODO: test if (typeof getPos !== "boolean") { - this.editor.commands.BNUpdateBlock(getPos(), { - type: "checkListItem", - props: { - checked: checkbox.checked as any, - }, - }); + const beforeBlockContainerPos = getNearestBlockContainerPos( + editor.state.doc, + getPos() + ); + this.editor.commands.command( + updateBlockCommand( + this.options.editor, + beforeBlockContainerPos.posBeforeNode, + { + type: "checkListItem", + props: { + checked: checkbox.checked as any, + }, + } + ) + ); } }; checkbox.addEventListener("change", changeHandler); diff --git a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts index 3a740b0213..a9ffa5d684 100644 --- a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +++ b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts @@ -1,35 +1,39 @@ -import { Editor } from "@tiptap/core"; -import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock/splitBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; +import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -export const handleEnter = (editor: Editor) => { - const { contentNode, contentType } = getBlockInfoFromPos( - editor.state.doc, - editor.state.selection.from - )!; +export const handleEnter = (editor: BlockNoteEditor) => { + const ttEditor = editor._tiptapEditor; + const { blockContent, blockContainer } = getBlockInfoFromSelection( + ttEditor.state + ); const selectionEmpty = - editor.state.selection.anchor === editor.state.selection.head; + ttEditor.state.selection.anchor === ttEditor.state.selection.head; if ( !( - contentType.name === "bulletListItem" || - contentType.name === "numberedListItem" || - contentType.name === "checkListItem" + blockContent.node.type.name === "bulletListItem" || + blockContent.node.type.name === "numberedListItem" || + blockContent.node.type.name === "checkListItem" ) || !selectionEmpty ) { return false; } - return editor.commands.first(({ state, chain, commands }) => [ + return ttEditor.commands.first(({ state, chain, commands }) => [ () => // Changes list item block to a paragraph block if the content is empty. commands.command(() => { - if (contentNode.childCount === 0) { - return commands.BNUpdateBlock(state.selection.from, { - type: "paragraph", - props: {}, - }); + if (blockContent.node.childCount === 0) { + return commands.command( + updateBlockCommand(editor, blockContainer.beforePos, { + type: "paragraph", + props: {}, + }) + ); } return false; @@ -39,10 +43,10 @@ export const handleEnter = (editor: Editor) => { // Splits the current block, moving content inside that's after the cursor // to a new block of the same type below. commands.command(() => { - if (contentNode.childCount > 0) { + if (blockContent.node.childCount > 0) { chain() .deleteSelection() - .BNSplitBlock(state.selection.from, true) + .command(splitBlockCommand(state.selection.from, true)) .run(); return true; diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts index 4f4b95b942..ae77be402b 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts @@ -1,5 +1,5 @@ import { Plugin, PluginKey } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../../../api/getBlockInfoFromPos.js"; +import { getBlockInfo } from "../../../api/getBlockInfoFromPos.js"; // ProseMirror Plugin which automatically assigns indices to ordered list items per nesting level. const PLUGIN_KEY = new PluginKey(`numbered-list-indexing`); @@ -21,46 +21,44 @@ export const NumberedListIndexingPlugin = () => { node.firstChild!.type.name === "numberedListItem" ) { let newIndex = "1"; - const isFirstBlockInDoc = pos === 1; - const blockInfo = getBlockInfoFromPos(tr.doc, pos + 1)!; - if (blockInfo === undefined) { - return; - } + const blockInfo = getBlockInfo({ + posBeforeNode: pos, + node, + }); // Checks if this block is the start of a new ordered list, i.e. if it's the first block in the document, the // first block in its nesting level, or the previous block is not an ordered list item. - if (!isFirstBlockInDoc) { - const prevBlockInfo = getBlockInfoFromPos(tr.doc, pos - 2)!; - if (prevBlockInfo === undefined) { - return; - } - const isFirstBlockInNestingLevel = - blockInfo.depth !== prevBlockInfo.depth; + const prevBlock = tr.doc.resolve( + blockInfo.blockContainer.beforePos + ).nodeBefore; - if (!isFirstBlockInNestingLevel) { - const prevBlockContentNode = prevBlockInfo.contentNode; - const prevBlockContentType = prevBlockInfo.contentType; + if (prevBlock) { + const prevBlockInfo = getBlockInfo({ + posBeforeNode: + blockInfo.blockContainer.beforePos - prevBlock.nodeSize, + node: prevBlock, + }); - const isPrevBlockOrderedListItem = - prevBlockContentType.name === "numberedListItem"; + const isPrevBlockOrderedListItem = + prevBlockInfo.blockContent.node.type.name === "numberedListItem"; - if (isPrevBlockOrderedListItem) { - const prevBlockIndex = prevBlockContentNode.attrs["index"]; + if (isPrevBlockOrderedListItem) { + const prevBlockIndex = + prevBlockInfo.blockContent.node.attrs["index"]; - newIndex = (parseInt(prevBlockIndex) + 1).toString(); - } + newIndex = (parseInt(prevBlockIndex) + 1).toString(); } } - const contentNode = blockInfo.contentNode; + const contentNode = blockInfo.blockContent.node; const index = contentNode.attrs["index"]; if (index !== newIndex) { modified = true; - tr.setNodeMarkup(pos + 1, undefined, { + tr.setNodeMarkup(blockInfo.blockContent.beforePos, undefined, { index: newIndex, }); } diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts index d5e6080125..8c4dc5b16b 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -1,5 +1,6 @@ import { InputRule } from "@tiptap/core"; -import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; +import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -39,15 +40,22 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`^1\\.\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() - .BNUpdateBlock(state.selection.from, { - type: "numberedListItem", - props: {}, - }) + .command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "numberedListItem", + props: {}, + } + ) + ) // Removes the "1." characters used to set the list. .deleteRange({ from: range.from, to: range.to }); }, @@ -57,18 +65,22 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { - Enter: () => handleEnter(this.editor), + Enter: () => handleEnter(this.options.editor), "Mod-Shift-7": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } - return this.editor.commands.BNUpdateBlock( - this.editor.state.selection.anchor, - { - type: "numberedListItem", - props: {}, - } + return this.editor.commands.command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "numberedListItem", + props: {}, + } + ) ); }, }; diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts index 21fc496ba8..a089cb3258 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -1,4 +1,5 @@ -import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; +import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; import { createBlockSpecFromStronglyTypedTiptapNode, createStronglyTypedTiptapNode, @@ -18,16 +19,20 @@ export const ParagraphBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { "Mod-Alt-0": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } - return this.editor.commands.BNUpdateBlock( - this.editor.state.selection.anchor, - { - type: "paragraph", - props: {}, - } + return this.editor.commands.command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "paragraph", + props: {}, + } + ) ); }, }; diff --git a/packages/core/src/blocks/defaultBlockHelpers.ts b/packages/core/src/blocks/defaultBlockHelpers.ts index 1c5cbcae74..4e758bd16b 100644 --- a/packages/core/src/blocks/defaultBlockHelpers.ts +++ b/packages/core/src/blocks/defaultBlockHelpers.ts @@ -1,4 +1,4 @@ -import { blockToNode } from "../api/nodeConversions/nodeConversions.js"; +import { blockToNode } from "../api/nodeConversions/blockToNode.js"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; import type { BlockNoDefaults, diff --git a/packages/core/src/editor/BlockNoteEditor.test.ts b/packages/core/src/editor/BlockNoteEditor.test.ts index 2fb878335f..f48abd9b4b 100644 --- a/packages/core/src/editor/BlockNoteEditor.test.ts +++ b/packages/core/src/editor/BlockNoteEditor.test.ts @@ -1,5 +1,8 @@ import { expect, it } from "vitest"; -import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; +import { + getBlockInfo, + getNearestBlockContainerPos, +} from "../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "./BlockNoteEditor.js"; /** @@ -7,8 +10,12 @@ import { BlockNoteEditor } from "./BlockNoteEditor.js"; */ it("creates an editor", () => { const editor = BlockNoteEditor.create(); - const blockInfo = getBlockInfoFromPos(editor._tiptapEditor.state.doc, 2); - expect(blockInfo?.contentNode.type.name).toEqual("paragraph"); + const posInfo = getNearestBlockContainerPos( + editor._tiptapEditor.state.doc, + 2 + ); + const { blockContent } = getBlockInfo(posInfo); + expect(blockContent.node.type.name).toEqual("paragraph"); }); it("immediately replaces doc", async () => { @@ -66,7 +73,7 @@ it("adds id attribute when requested", async () => { "This is a normal text\n\n# And this is a large heading" ); editor.replaceBlocks(editor.document, blocks); - expect( - await editor.blocksToFullHTML(editor.document) - ).toMatchInlineSnapshot(`"

This is a normal text

And this is a large heading

"`); + expect(await editor.blocksToFullHTML(editor.document)).toMatchInlineSnapshot( + `"

This is a normal text

And this is a large heading

"` + ); }); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 1c84cd639f..0b16a2cb49 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -2,25 +2,22 @@ import { EditorOptions, Extension, getSchema } from "@tiptap/core"; import { Node, Schema } from "prosemirror-model"; // import "./blocknote.css"; import * as Y from "yjs"; -import { - insertBlocks, - insertContentAt, - removeBlocks, - replaceBlocks, - updateBlock, -} from "../api/blockManipulation/blockManipulation.js"; +import { insertBlocks } from "../api/blockManipulation/commands/insertBlocks/insertBlocks.js"; import { moveBlockDown, moveBlockUp, -} from "../api/blockManipulation/moveBlock.js"; +} from "../api/blockManipulation/commands/moveBlock/moveBlock.js"; +import { removeBlocks } from "../api/blockManipulation/commands/removeBlocks/removeBlocks.js"; +import { replaceBlocks } from "../api/blockManipulation/commands/replaceBlocks/replaceBlocks.js"; +import { updateBlock } from "../api/blockManipulation/commands/updateBlock/updateBlock.js"; +import { insertContentAt } from "../api/blockManipulation/insertContentAt.js"; +import { + getTextCursorPosition, + setTextCursorPosition, +} from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js"; import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; -import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; -import { - inlineContentToNodes, - nodeToBlock, -} from "../api/nodeConversions/nodeConversions.js"; -import { getNodeById } from "../api/nodeUtil.js"; +import { getBlockInfoFromSelection } from "../api/getBlockInfoFromPos.js"; import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js"; import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown.js"; import { @@ -71,6 +68,9 @@ import { en } from "../i18n/locales/index.js"; import { Transaction } from "@tiptap/pm/state"; import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js"; +import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js"; +import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js"; +import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js"; import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js"; import "../style.css"; import { initializeESMDependencies } from "../util/esmDependencies.js"; @@ -382,6 +382,7 @@ export class BlockNoteEditor< ...(this.filePanel ? [this.filePanel.plugin] : []), ...(this.tableHandles ? [this.tableHandles.plugin] : []), PlaceholderPlugin(this, newOptions.placeholders), + NodeSelectionKeyboardPlugin(), ...(this.options.animations ?? true ? [PreviousBlockTypePlugin()] : []), @@ -663,81 +664,7 @@ export class BlockNoteEditor< ISchema, SSchema > { - const { node, depth, startPos, endPos } = getBlockInfoFromPos( - this._tiptapEditor.state.doc, - this._tiptapEditor.state.selection.from - )!; - - // Index of the current blockContainer node relative to its parent blockGroup. - const nodeIndex = this._tiptapEditor.state.doc - .resolve(endPos) - .index(depth - 1); - // Number of the parent blockGroup's child blockContainer nodes. - const numNodes = this._tiptapEditor.state.doc - .resolve(endPos + 1) - .node().childCount; - // Depth of the blockContainer node. - const nodeDepth = this._tiptapEditor.state.doc.resolve(startPos).depth; - - // Gets previous blockContainer node at the same nesting level, if the current node isn't the first child. - let prevNode: Node | undefined = undefined; - if (nodeIndex > 0) { - prevNode = this._tiptapEditor.state.doc.resolve(startPos - 2).node(); - } - - // Gets next blockContainer node at the same nesting level, if the current node isn't the last child. - let nextNode: Node | undefined = undefined; - if (nodeIndex < numNodes - 1) { - nextNode = this._tiptapEditor.state.doc.resolve(endPos + 2).node(); - } - - // Gets parent blockContainer node, if the current node is nested. - let parentNode: Node | undefined = undefined; - if (nodeDepth > 2) { - parentNode = this._tiptapEditor.state.doc - .resolve(startPos - 1) - .node(nodeDepth - 2); - } - - return { - block: nodeToBlock( - node, - this.schema.blockSchema, - this.schema.inlineContentSchema, - this.schema.styleSchema, - this.blockCache - ), - prevBlock: - prevNode === undefined - ? undefined - : nodeToBlock( - prevNode, - this.schema.blockSchema, - this.schema.inlineContentSchema, - this.schema.styleSchema, - this.blockCache - ), - nextBlock: - nextNode === undefined - ? undefined - : nodeToBlock( - nextNode, - this.schema.blockSchema, - this.schema.inlineContentSchema, - this.schema.styleSchema, - this.blockCache - ), - parentBlock: - parentNode === undefined - ? undefined - : nodeToBlock( - parentNode, - this.schema.blockSchema, - this.schema.inlineContentSchema, - this.schema.styleSchema, - this.blockCache - ), - }; + return getTextCursorPosition(this); } /** @@ -750,44 +677,7 @@ export class BlockNoteEditor< targetBlock: BlockIdentifier, placement: "start" | "end" = "start" ) { - const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id; - - const { posBeforeNode } = getNodeById(id, this._tiptapEditor.state.doc); - const { startPos, contentNode } = getBlockInfoFromPos( - this._tiptapEditor.state.doc, - posBeforeNode + 2 - )!; - - const contentType: "none" | "inline" | "table" = - this.schema.blockSchema[contentNode.type.name]!.content; - - if (contentType === "none") { - this._tiptapEditor.commands.setNodeSelection(startPos); - return; - } - - if (contentType === "inline") { - if (placement === "start") { - this._tiptapEditor.commands.setTextSelection(startPos + 1); - } else { - this._tiptapEditor.commands.setTextSelection( - startPos + contentNode.nodeSize - 1 - ); - } - } else if (contentType === "table") { - if (placement === "start") { - // Need to offset the position as we have to get through the `tableRow` - // and `tableCell` nodes to get to the `tableParagraph` node we want to - // set the selection in. - this._tiptapEditor.commands.setTextSelection(startPos + 4); - } else { - this._tiptapEditor.commands.setTextSelection( - startPos + contentNode.nodeSize - 4 - ); - } - } else { - throw new UnreachableCaseError(contentType); - } + setTextCursorPosition(this, targetBlock, placement); } /** @@ -882,14 +772,14 @@ export class BlockNoteEditor< * @param blocksToInsert An array of partial blocks that should be inserted. * @param referenceBlock An identifier for an existing block, at which the new blocks should be inserted. * @param placement Whether the blocks should be inserted just before, just after, or nested inside the - * `referenceBlock`. Inserts the blocks at the start of the existing block's children if "nested" is used. + * `referenceBlock`. */ public insertBlocks( blocksToInsert: PartialBlock[], referenceBlock: BlockIdentifier, - placement: "before" | "after" | "nested" = "before" + placement: "before" | "after" = "before" ) { - return insertBlocks(blocksToInsert, referenceBlock, placement, this); + return insertBlocks(this, blocksToInsert, referenceBlock, placement); } /** @@ -903,7 +793,7 @@ export class BlockNoteEditor< blockToUpdate: BlockIdentifier, update: PartialBlock ) { - return updateBlock(blockToUpdate, update, this); + return updateBlock(this, blockToUpdate, update); } /** @@ -911,7 +801,7 @@ export class BlockNoteEditor< * @param blocksToRemove An array of identifiers for existing blocks that should be removed. */ public removeBlocks(blocksToRemove: BlockIdentifier[]) { - return removeBlocks(blocksToRemove, this); + return removeBlocks(this, blocksToRemove); } /** @@ -925,7 +815,7 @@ export class BlockNoteEditor< blocksToRemove: BlockIdentifier[], blocksToInsert: PartialBlock[] ) { - return replaceBlocks(blocksToRemove, blocksToInsert, this); + return replaceBlocks(this, blocksToRemove, blocksToInsert); } /** @@ -1073,12 +963,14 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor can be nested. */ public canNestBlock() { - const { startPos, depth } = getBlockInfoFromPos( - this._tiptapEditor.state.doc, - this._tiptapEditor.state.selection.from - )!; + const { blockContainer } = getBlockInfoFromSelection( + this._tiptapEditor.state + ); - return this._tiptapEditor.state.doc.resolve(startPos).index(depth - 1) > 0; + return ( + this._tiptapEditor.state.doc.resolve(blockContainer.beforePos) + .nodeBefore !== null + ); } /** @@ -1092,12 +984,13 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor is nested. */ public canUnnestBlock() { - const { depth } = getBlockInfoFromPos( - this._tiptapEditor.state.doc, - this._tiptapEditor.state.selection.from - )!; + const { blockContainer } = getBlockInfoFromSelection( + this._tiptapEditor.state + ); - return depth > 2; + return ( + this._tiptapEditor.state.doc.resolve(blockContainer.beforePos).depth > 1 + ); } /** diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index c8683e2acc..333fc8fd30 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -15,6 +15,7 @@ import { createDropFileExtension } from "../api/clipboard/fromClipboard/fileDrop import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboard/pasteExtension.js"; import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js"; import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js"; +import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js"; import { TextAlignmentExtension } from "../extensions/TextAlignment/TextAlignmentExtension.js"; import { TextColorExtension } from "../extensions/TextColor/TextColorExtension.js"; import { TrailingNode } from "../extensions/TrailingNode/TrailingNodeExtension.js"; @@ -120,6 +121,9 @@ export const getBlockNoteExtensions = < editor: opts.editor, domAttributes: opts.domAttributes, }), + KeyboardShortcutsExtension.configure({ + editor: opts.editor, + }), BlockGroup.configure({ domAttributes: opts.domAttributes, }), diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index 22aa626f1b..fab4469e18 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -7,7 +7,7 @@ import { Node } from "@tiptap/pm/model"; import { EditorView } from "@tiptap/pm/view"; import { EditorState, Transaction } from "@tiptap/pm/state"; -import { blockToNode } from "../api/nodeConversions/nodeConversions.js"; +import { blockToNode } from "../api/nodeConversions/blockToNode.js"; import { PartialBlock } from "../blocks/defaultBlocks.js"; import { StyleSchema } from "../schema/index.js"; diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts new file mode 100644 index 0000000000..b64782d767 --- /dev/null +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -0,0 +1,333 @@ +import { Extension } from "@tiptap/core"; + +import { TextSelection } from "prosemirror-state"; +import { + getPrevBlockPos, + mergeBlocksCommand, +} from "../../api/blockManipulation/commands/mergeBlocks/mergeBlocks.js"; +import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock/splitBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; +import { + getBlockInfoFromResolvedPos, + getBlockInfoFromSelection, +} from "../../api/getBlockInfoFromPos.js"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; + +export const KeyboardShortcutsExtension = Extension.create<{ + editor: BlockNoteEditor; +}>({ + priority: 50, + + // TODO: The shortcuts need a refactor. Do we want to use a command priority + // design as there is now, or clump the logic into a single function? + addKeyboardShortcuts() { + // handleBackspace is partially adapted from https://github.com/ueberdosis/tiptap/blob/ed56337470efb4fd277128ab7ef792b37cfae992/packages/core/src/extensions/keymap.ts + const handleBackspace = () => + this.editor.commands.first(({ chain, commands }) => [ + // Deletes the selection if it's not empty. + () => commands.deleteSelection(), + // Undoes an input rule if one was triggered in the last editor state change. + () => commands.undoInputRule(), + // Reverts block content type to a paragraph if the selection is at the start of the block. + () => + commands.command(({ state }) => { + const blockInfo = getBlockInfoFromSelection(state); + + const selectionAtBlockStart = + state.selection.from === blockInfo.blockContent.beforePos + 1; + const isParagraph = + blockInfo.blockContent.node.type.name === "paragraph"; + + if (selectionAtBlockStart && !isParagraph) { + return commands.command( + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "paragraph", + props: {}, + } + ) + ); + } + + return false; + }), + // Removes a level of nesting if the block is indented if the selection is at the start of the block. + () => + commands.command(({ state }) => { + const { blockContent } = getBlockInfoFromSelection(state); + + const selectionAtBlockStart = + state.selection.from === blockContent.beforePos + 1; + + if (selectionAtBlockStart) { + return commands.liftListItem("blockContainer"); + } + + return false; + }), + // Merges block with the previous one if it isn't indented, isn't the + // first block in the doc, and the selection is at the start of the + // block. The target block for merging must contain inline content. + () => + commands.command(({ state }) => { + const { blockContainer, blockContent } = + getBlockInfoFromSelection(state); + + const { depth } = state.doc.resolve(blockContainer.beforePos); + + const selectionAtBlockStart = + state.selection.from === blockContent.beforePos + 1; + const selectionEmpty = state.selection.empty; + const blockAtDocStart = blockContainer.beforePos === 1; + + const posBetweenBlocks = blockContainer.beforePos; + + if ( + !blockAtDocStart && + selectionAtBlockStart && + selectionEmpty && + depth === 1 + ) { + return chain() + .command(mergeBlocksCommand(posBetweenBlocks)) + .scrollIntoView() + .run(); + } + + return false; + }), + // Deletes previous block if it contains no content and isn't a table, + // when the selection is empty and at the start of the block. Moves the + // current block into the deleted block's place. + () => + commands.command(({ state }) => { + const blockInfo = getBlockInfoFromSelection(state); + + const { depth } = state.doc.resolve( + blockInfo.blockContainer.beforePos + ); + + const selectionAtBlockStart = + state.selection.from === blockInfo.blockContent.beforePos + 1; + const selectionEmpty = state.selection.empty; + const blockAtDocStart = blockInfo.blockContainer.beforePos === 1; + + const prevBlockPos = getPrevBlockPos( + state.doc, + state.doc.resolve(blockInfo.blockContainer.beforePos) + ); + const prevBlockInfo = getBlockInfoFromResolvedPos( + state.doc.resolve(prevBlockPos.pos) + ); + + const prevBlockNotTableAndNoContent = + prevBlockInfo.blockContent.node.type.spec.content === "" || + (prevBlockInfo.blockContent.node.type.spec.content === + "inline*" && + prevBlockInfo.blockContent.node.childCount === 0); + + if ( + !blockAtDocStart && + selectionAtBlockStart && + selectionEmpty && + depth === 1 && + prevBlockNotTableAndNoContent + ) { + return chain() + .cut( + { + from: blockInfo.blockContainer.beforePos, + to: blockInfo.blockContainer.afterPos, + }, + prevBlockInfo.blockContainer.afterPos + ) + .deleteRange({ + from: prevBlockInfo.blockContainer.beforePos, + to: prevBlockInfo.blockContainer.afterPos, + }) + .run(); + } + + return false; + }), + ]); + + const handleDelete = () => + this.editor.commands.first(({ commands }) => [ + // Deletes the selection if it's not empty. + () => commands.deleteSelection(), + // Merges block with the next one (at the same nesting level or lower), + // if one exists, the block has no children, and the selection is at the + // end of the block. + () => + commands.command(({ state }) => { + // TODO: Change this to not rely on offsets & schema assumptions + const { blockContainer, blockContent, blockGroup } = + getBlockInfoFromSelection(state); + + const { depth } = state.doc.resolve(blockContainer.beforePos); + const blockAtDocEnd = + blockContainer.afterPos === state.doc.nodeSize - 3; + const selectionAtBlockEnd = + state.selection.from === blockContent.afterPos - 1; + const selectionEmpty = state.selection.empty; + const hasChildBlocks = blockGroup !== undefined; + + if ( + !blockAtDocEnd && + selectionAtBlockEnd && + selectionEmpty && + !hasChildBlocks + ) { + let oldDepth = depth; + let newPos = blockContainer.afterPos + 1; + let newDepth = state.doc.resolve(newPos).depth; + + while (newDepth < oldDepth) { + oldDepth = newDepth; + newPos += 2; + newDepth = state.doc.resolve(newPos).depth; + } + + return commands.command(mergeBlocksCommand(newPos - 1)); + } + + return false; + }), + ]); + + const handleEnter = () => + this.editor.commands.first(({ commands }) => [ + // Removes a level of nesting if the block is empty & indented, while the selection is also empty & at the start + // of the block. + () => + commands.command(({ state }) => { + const { blockContent, blockContainer } = + getBlockInfoFromSelection(state); + + const { depth } = state.doc.resolve(blockContainer.beforePos); + + const selectionAtBlockStart = + state.selection.$anchor.parentOffset === 0; + const selectionEmpty = + state.selection.anchor === state.selection.head; + const blockEmpty = blockContent.node.childCount === 0; + const blockIndented = depth > 1; + + if ( + selectionAtBlockStart && + selectionEmpty && + blockEmpty && + blockIndented + ) { + return commands.liftListItem("blockContainer"); + } + + return false; + }), + // 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 }) => { + const { blockContainer, blockContent } = + getBlockInfoFromSelection(state); + + const selectionAtBlockStart = + state.selection.$anchor.parentOffset === 0; + const selectionEmpty = + state.selection.anchor === state.selection.head; + const blockEmpty = blockContent.node.childCount === 0; + + if (selectionAtBlockStart && selectionEmpty && blockEmpty) { + const newBlockInsertionPos = blockContainer.afterPos; + const newBlockContentPos = newBlockInsertionPos + 2; + + if (dispatch) { + const newBlock = + state.schema.nodes["blockContainer"].createAndFill()!; + + state.tr + .insert(newBlockInsertionPos, newBlock) + .scrollIntoView(); + state.tr.setSelection( + new TextSelection(state.doc.resolve(newBlockContentPos)) + ); + } + + return true; + } + + return false; + }), + // Splits the current block, moving content inside that's after the cursor to a new text block below. Also + // deletes the selection beforehand, if it's not empty. + () => + commands.command(({ state, chain }) => { + const { blockContent } = getBlockInfoFromSelection(state); + + const selectionAtBlockStart = + state.selection.$anchor.parentOffset === 0; + const blockEmpty = blockContent.node.childCount === 0; + + if (!blockEmpty) { + chain() + .deleteSelection() + .command( + splitBlockCommand( + state.selection.from, + selectionAtBlockStart, + selectionAtBlockStart + ) + ) + .run(); + + return true; + } + + return false; + }), + ]); + + return { + Backspace: handleBackspace, + Delete: handleDelete, + Enter: handleEnter, + // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the + // editor since the browser will try to use tab for keyboard navigation. + Tab: () => { + if ( + this.options.editor.formattingToolbar?.shown || + this.options.editor.linkToolbar?.shown || + this.options.editor.filePanel?.shown + ) { + // don't handle tabs if a toolbar is shown, so we can tab into / out of it + return false; + } + this.editor.commands.sinkListItem("blockContainer"); + return true; + }, + "Shift-Tab": () => { + if ( + this.options.editor.formattingToolbar?.shown || + this.options.editor.linkToolbar?.shown || + this.options.editor.filePanel?.shown + ) { + // don't handle tabs if a toolbar is shown, so we can tab into / out of it + return false; + } + this.editor.commands.liftListItem("blockContainer"); + return true; + }, + "Shift-Mod-ArrowUp": () => { + this.options.editor.moveBlockUp(); + return true; + }, + "Shift-Mod-ArrowDown": () => { + this.options.editor.moveBlockDown(); + return true; + }, + }; + }, +}); diff --git a/packages/core/src/extensions/NonEditableBlocks/NonEditableBlockPlugin.ts b/packages/core/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts similarity index 95% rename from packages/core/src/extensions/NonEditableBlocks/NonEditableBlockPlugin.ts rename to packages/core/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts index 2247d26a88..f50d265607 100644 --- a/packages/core/src/extensions/NonEditableBlocks/NonEditableBlockPlugin.ts +++ b/packages/core/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts @@ -1,6 +1,6 @@ import { Plugin, PluginKey, TextSelection } from "prosemirror-state"; -const PLUGIN_KEY = new PluginKey("non-editable-block"); +const PLUGIN_KEY = new PluginKey("node-selection-keyboard"); // By default, typing with a node selection active will cause ProseMirror to // replace the node with one that contains editable content. This plugin blocks // this behaviour without also blocking things like keyboard shortcuts: @@ -15,7 +15,7 @@ const PLUGIN_KEY = new PluginKey("non-editable-block"); // While a more elegant solution would probably process transactions instead of // keystrokes, this brings us most of the way to Notion's UX without much added // complexity. -export const NonEditableBlockPlugin = () => { +export const NodeSelectionKeyboardPlugin = () => { return new Plugin({ key: PLUGIN_KEY, props: { diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index d931f3c23d..8a2bec0eaa 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -51,6 +51,8 @@ export function insertOrUpdateBlock< throw new Error("Slash Menu open in a block that doesn't contain content."); } + let newBlock: Block; + if ( Array.isArray(currentBlock.content) && ((currentBlock.content.length === 1 && @@ -59,19 +61,21 @@ export function insertOrUpdateBlock< currentBlock.content[0].text === "/") || currentBlock.content.length === 0) ) { - editor.updateBlock(currentBlock, block); + newBlock = editor.updateBlock(currentBlock, block); + + // Edge case for updating block content as `updateBlock` causes the + // selection to move into the next block, so we have to set it back. + if (block.content) { + editor.setTextCursorPosition(newBlock); + } } else { - editor.insertBlocks([block], currentBlock, "after"); - editor.setTextCursorPosition( - editor.getTextCursorPosition().nextBlock!, - "end" - ); + newBlock = editor.insertBlocks([block], currentBlock, "after")[0]; + editor.setTextCursorPosition(editor.getTextCursorPosition().nextBlock!); } - const insertedBlock = editor.getTextCursorPosition().block; setSelectionToNextContentEditableBlock(editor); - return insertedBlock; + return newBlock; } export function getDefaultSlashMenuItems< @@ -266,7 +270,7 @@ export function getDefaultSlashMenuItems< }) ); }, - key: "image", + key: "file", ...editor.dictionary.slash_menu.file, }); } diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 9b7b650629..b72c76b932 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -1,6 +1,6 @@ import { Plugin, PluginKey, PluginView } from "prosemirror-state"; import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; -import { nodeToBlock } from "../../api/nodeConversions/nodeConversions.js"; +import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; import { checkBlockIsDefaultType } from "../../blocks/defaultBlockTypeGuards.js"; import { Block, DefaultBlockSchema } from "../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; @@ -387,6 +387,10 @@ export class TableHandlesView< rows: rows, }, }); + + // Have to reset text cursor position to the block as `updateBlock` moves + // the existing selection out of the block. + this.editor.setTextCursorPosition(this.state.block.id); }; scrollHandler = () => { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 03a26587ed..ae98dff892 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,7 @@ import * as locales from "./i18n/locales/index.js"; export * from "./api/exporters/html/externalHTMLExporter.js"; export * from "./api/exporters/html/internalHTMLSerializer.js"; -export * from "./api/getCurrentBlockContentType.js"; +export * from "./api/getBlockInfoFromPos.js"; export * from "./api/testUtil/index.js"; export * from "./blocks/AudioBlockContent/AudioBlockContent.js"; export * from "./blocks/FileBlockContent/FileBlockContent.js"; @@ -39,7 +39,8 @@ export { UnreachableCaseError, assertEmpty } from "./util/typescript.js"; export { locales }; // for testing from react (TODO: move): -export * from "./api/nodeConversions/nodeConversions.js"; +export * from "./api/nodeConversions/blockToNode.js"; +export * from "./api/nodeConversions/nodeToBlock.js"; export * from "./api/testUtil/partialBlockTestUtil.js"; export * from "./extensions/UniqueID/UniqueID.js"; diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 209650ada3..81e8b46b14 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -1,24 +1,8 @@ import { Node } from "@tiptap/core"; -import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; -import { NodeSelection, TextSelection } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; -import { - blockToNode, - inlineContentToNodes, - tableContentToNodes, -} from "../api/nodeConversions/nodeConversions.js"; -import { PartialBlock } from "../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; -import { NonEditableBlockPlugin } from "../extensions/NonEditableBlocks/NonEditableBlockPlugin.js"; -import { - BlockNoteDOMAttributes, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../schema/index.js"; +import { BlockNoteDOMAttributes } from "../schema/index.js"; import { mergeCSSClasses } from "../util/browser.js"; -import { UnreachableCaseError } from "../util/typescript.js"; // Object containing all possible block attributes. const BlockAttributes: Record = { @@ -29,37 +13,6 @@ const BlockAttributes: Record = { depthChange: "data-depth-change", }; -declare module "@tiptap/core" { - interface Commands { - block: { - BNCreateBlock: (pos: number) => ReturnType; - BNDeleteBlock: (posInBlock: number) => ReturnType; - BNMergeBlocks: (posBetweenBlocks: number) => ReturnType; - BNSplitBlock: ( - posInBlock: number, - keepType?: boolean, - keepProps?: boolean - ) => ReturnType; - BNUpdateBlock: < - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema - >( - posInBlock: number, - block: PartialBlock - ) => ReturnType; - BNCreateOrUpdateBlock: < - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema - >( - posInBlock: number, - block: PartialBlock - ) => ReturnType; - }; - } -} - /** * The main "Block node" documents consist of */ @@ -131,609 +84,4 @@ export const BlockContainer = Node.create<{ contentDOM: block, }; }, - - addCommands() { - return { - // Creates a new text block at a given position. - BNCreateBlock: - (pos) => - ({ state, dispatch }) => { - const newBlock = - state.schema.nodes["blockContainer"].createAndFill()!; - - if (dispatch) { - state.tr.insert(pos, newBlock).scrollIntoView(); - } - - return true; - }, - // Deletes a block at a given position. - BNDeleteBlock: - (posInBlock) => - ({ state, dispatch }) => { - const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) { - return false; - } - - const { startPos, endPos } = blockInfo; - - if (dispatch) { - state.tr.deleteRange(startPos, endPos); - } - - return true; - }, - // Updates a block at a given position. - BNUpdateBlock: - (posInBlock, block) => - ({ state, dispatch }) => { - const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) { - return false; - } - - const { startPos, endPos, node, contentNode } = blockInfo; - - if (dispatch) { - // Adds blockGroup node with child blocks if necessary. - if (block.children !== undefined) { - const childNodes = []; - - // Creates ProseMirror nodes for each child block, including their descendants. - for (const child of block.children) { - childNodes.push( - blockToNode( - child, - state.schema, - this.options.editor.schema.styleSchema - ) - ); - } - - // Checks if a blockGroup node already exists. - if (node.childCount === 2) { - // Replaces all child nodes in the existing blockGroup with the ones created earlier. - state.tr.replace( - startPos + contentNode.nodeSize + 1, - endPos - 1, - new Slice(Fragment.from(childNodes), 0, 0) - ); - } else { - // Inserts a new blockGroup containing the child nodes created earlier. - state.tr.insert( - startPos + contentNode.nodeSize, - state.schema.nodes["blockGroup"].create({}, childNodes) - ); - } - } - - const oldType = contentNode.type.name; - const newType = block.type || oldType; - - // The code below determines the new content of the block. - // or "keep" to keep as-is - let content: PMNode[] | "keep" = "keep"; - - // Has there been any custom content provided? - if (block.content) { - if (typeof block.content === "string") { - // Adds a single text node with no marks to the content. - content = inlineContentToNodes( - [block.content], - state.schema, - this.options.editor.schema.styleSchema - ); - } else if (Array.isArray(block.content)) { - // Adds a text node with the provided styles converted into marks to the content, - // for each InlineContent object. - content = inlineContentToNodes( - block.content, - state.schema, - this.options.editor.schema.styleSchema - ); - } else if (block.content.type === "tableContent") { - content = tableContentToNodes( - block.content, - state.schema, - this.options.editor.schema.styleSchema - ); - } else { - throw new UnreachableCaseError(block.content.type); - } - } else { - // no custom content has been provided, use existing content IF possible - - // Since some block types contain inline content and others don't, - // we either need to call setNodeMarkup to just update type & - // attributes, or replaceWith to replace the whole blockContent. - const oldContentType = state.schema.nodes[oldType].spec.content; - const newContentType = state.schema.nodes[newType].spec.content; - - if (oldContentType === "") { - // keep old content, because it's empty anyway and should be compatible with - // any newContentType - } else if (newContentType !== oldContentType) { - // the content type changed, replace the previous content - content = []; - } else { - // keep old content, because the content type is the same and should be compatible - } - } - - // Now, changes the blockContent node type and adds the provided props - // as attributes. Also preserves all existing attributes that are - // compatible with the new type. - // - // Use either setNodeMarkup or replaceWith depending on whether the - // content is being replaced or not. - if (content === "keep") { - // use setNodeMarkup to only update the type and attributes - state.tr.setNodeMarkup( - startPos, - block.type === undefined - ? undefined - : state.schema.nodes[block.type], - { - ...contentNode.attrs, - ...block.props, - } - ); - } else { - // use replaceWith to replace the content and the block itself - // also reset the selection since replacing the block content - // sets it to the next block. - state.tr - .replaceWith( - startPos, - startPos + contentNode.nodeSize, - state.schema.nodes[newType].create( - { - ...contentNode.attrs, - ...block.props, - }, - content - ) - ) - // If the node doesn't contain editable content, we want to - // select the whole node. But if it does have editable content, - // we want to set the selection to the start of it. - .setSelection( - state.schema.nodes[newType].spec.content === "" - ? new NodeSelection(state.tr.doc.resolve(startPos)) - : state.schema.nodes[newType].spec.content === "inline*" - ? new TextSelection(state.tr.doc.resolve(startPos)) - : // Need to offset the position as we have to get through the - // `tableRow` and `tableCell` nodes to get to the - // `tableParagraph` node we want to set the selection in. - new TextSelection(state.tr.doc.resolve(startPos + 4)) - ); - } - - // Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing - // attributes. - state.tr.setNodeMarkup(startPos - 1, undefined, { - ...node.attrs, - ...block.props, - }); - } - - return true; - }, - // Appends the text contents of a block to the nearest previous block, given a position between them. Children of - // the merged block are moved out of it first, rather than also being merged. - // - // In the example below, the position passed into the function is between Block1 and Block2. - // - // Block1 - // Block2 - // Block3 - // Block4 - // Block5 - // - // Becomes: - // - // Block1 - // Block2Block3 - // Block4 - // Block5 - BNMergeBlocks: - (posBetweenBlocks) => - ({ state, dispatch }) => { - const nextNodeIsBlock = - state.doc.resolve(posBetweenBlocks + 1).node().type.name === - "blockContainer"; - const prevNodeIsBlock = - state.doc.resolve(posBetweenBlocks - 1).node().type.name === - "blockContainer"; - - if (!nextNodeIsBlock || !prevNodeIsBlock) { - return false; - } - - const nextBlockInfo = getBlockInfoFromPos( - state.doc, - posBetweenBlocks + 1 - ); - - const { node, contentNode, startPos, endPos, depth } = nextBlockInfo!; - - // Removes a level of nesting all children of the next block by 1 level, if it contains both content and block - // group nodes. - if (node.childCount === 2) { - const childBlocksStart = state.doc.resolve( - startPos + contentNode.nodeSize + 1 - ); - const childBlocksEnd = state.doc.resolve(endPos - 1); - const childBlocksRange = - childBlocksStart.blockRange(childBlocksEnd); - - // Moves the block group node inside the block into the block group node that the current block is in. - if (dispatch) { - state.tr.lift(childBlocksRange!, depth - 1); - } - } - - let prevBlockEndPos = posBetweenBlocks - 1; - let prevBlockInfo = getBlockInfoFromPos(state.doc, prevBlockEndPos); - - // Finds the nearest previous block, regardless of nesting level. - while (prevBlockInfo!.numChildBlocks > 0) { - prevBlockEndPos--; - prevBlockInfo = getBlockInfoFromPos(state.doc, prevBlockEndPos); - if (prevBlockInfo === undefined) { - return false; - } - } - - // Deletes next block and adds its text content to the nearest previous block. - - if (dispatch) { - dispatch( - state.tr - .deleteRange(startPos, startPos + contentNode.nodeSize) - .replace( - prevBlockEndPos - 1, - startPos, - new Slice(contentNode.content, 0, 0) - ) - .scrollIntoView() - ); - - state.tr.setSelection( - new TextSelection(state.doc.resolve(prevBlockEndPos - 1)) - ); - } - - return true; - }, - // Splits a block at a given position. Content after the position is moved to a new block below, at the same - // nesting level. - // - `keepType` is usually false, unless the selection is at the start of - // a block. - // - `keepProps` is usually true when `keepType` is true, except for when - // creating new list item blocks with Enter. - BNSplitBlock: - (posInBlock, keepType, keepProps) => - ({ state, dispatch }) => { - const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) { - return false; - } - - const { contentNode, contentType, startPos, endPos, depth } = - blockInfo; - - 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); - - // 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, - newBlockContentPos, - state.schema.node(contentType).type, - keepProps ? contentNode.attrs : undefined - ); - } - - // Sets the selection to the start of the new block's content node. - state.tr.setSelection( - new TextSelection(state.doc.resolve(newBlockContentPos)) - ); - - // 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 + 1, - endPos - 1, - originalBlockContent.content.size > 0 - ? new Slice( - Fragment.from(originalBlockContent), - depth + 2, - depth + 2 - ) - : undefined - ); - - state.tr.scrollIntoView(); - } - - return true; - }, - }; - }, - - addProseMirrorPlugins() { - return [NonEditableBlockPlugin()]; - }, - - addKeyboardShortcuts() { - // handleBackspace is partially adapted from https://github.com/ueberdosis/tiptap/blob/ed56337470efb4fd277128ab7ef792b37cfae992/packages/core/src/extensions/keymap.ts - const handleBackspace = () => - this.editor.commands.first(({ commands }) => [ - // Deletes the selection if it's not empty. - () => commands.deleteSelection(), - // Undoes an input rule if one was triggered in the last editor state change. - () => commands.undoInputRule(), - // Reverts block content type to a paragraph if the selection is at the start of the block. - () => - commands.command(({ state }) => { - const { contentType, startPos } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; - - const selectionAtBlockStart = state.selection.from === startPos + 1; - const isParagraph = contentType.name === "paragraph"; - - if (selectionAtBlockStart && !isParagraph) { - return commands.BNUpdateBlock(state.selection.from, { - type: "paragraph", - props: {}, - }); - } - - return false; - }), - // Removes a level of nesting if the block is indented if the selection is at the start of the block. - () => - commands.command(({ state }) => { - const { startPos } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; - - const selectionAtBlockStart = state.selection.from === startPos + 1; - - if (selectionAtBlockStart) { - return commands.liftListItem("blockContainer"); - } - - return false; - }), - // Merges block with the previous one if it isn't indented, isn't the first block in the doc, and the selection - // is at the start of the block. - () => - commands.command(({ state }) => { - const { depth, startPos } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; - - const selectionAtBlockStart = state.selection.from === startPos + 1; - const selectionEmpty = state.selection.empty; - const blockAtDocStart = startPos === 2; - - const posBetweenBlocks = startPos - 1; - - if ( - !blockAtDocStart && - selectionAtBlockStart && - selectionEmpty && - depth === 2 - ) { - return commands.BNMergeBlocks(posBetweenBlocks); - } - - return false; - }), - ]); - - const handleDelete = () => - this.editor.commands.first(({ commands }) => [ - // Deletes the selection if it's not empty. - () => commands.deleteSelection(), - // Merges block with the next one (at the same nesting level or lower), - // if one exists, the block has no children, and the selection is at the - // end of the block. - () => - commands.command(({ state }) => { - const { node, depth, endPos } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; - - const blockAtDocEnd = endPos === state.doc.nodeSize - 4; - const selectionAtBlockEnd = state.selection.from === endPos - 1; - const selectionEmpty = state.selection.empty; - const hasChildBlocks = node.childCount === 2; - - if ( - !blockAtDocEnd && - selectionAtBlockEnd && - selectionEmpty && - !hasChildBlocks - ) { - let oldDepth = depth; - let newPos = endPos + 2; - let newDepth = state.doc.resolve(newPos).depth; - - while (newDepth < oldDepth) { - oldDepth = newDepth; - newPos += 2; - newDepth = state.doc.resolve(newPos).depth; - } - - return commands.BNMergeBlocks(newPos - 1); - } - - return false; - }), - ]); - - const handleEnter = () => - this.editor.commands.first(({ commands }) => [ - // Removes a level of nesting if the block is empty & indented, while the selection is also empty & at the start - // of the block. - () => - commands.command(({ state }) => { - const { contentNode, depth } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; - - const selectionAtBlockStart = - state.selection.$anchor.parentOffset === 0; - const selectionEmpty = - state.selection.anchor === state.selection.head; - const blockEmpty = contentNode.childCount === 0; - const blockIndented = depth > 2; - - if ( - selectionAtBlockStart && - selectionEmpty && - blockEmpty && - blockIndented - ) { - return commands.liftListItem("blockContainer"); - } - - return false; - }), - // 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, chain }) => { - const { contentNode, endPos } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; - - const selectionAtBlockStart = - state.selection.$anchor.parentOffset === 0; - const selectionEmpty = - state.selection.anchor === state.selection.head; - const blockEmpty = contentNode.childCount === 0; - - if (selectionAtBlockStart && selectionEmpty && blockEmpty) { - const newBlockInsertionPos = endPos + 1; - const newBlockContentPos = newBlockInsertionPos + 2; - - chain() - .BNCreateBlock(newBlockInsertionPos) - .setTextSelection(newBlockContentPos) - .run(); - - return true; - } - - return false; - }), - // Splits the current block, moving content inside that's after the cursor to a new text block below. Also - // deletes the selection beforehand, if it's not empty. - () => - commands.command(({ state, chain }) => { - const { contentNode } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; - - const selectionAtBlockStart = - state.selection.$anchor.parentOffset === 0; - const blockEmpty = contentNode.childCount === 0; - - if (!blockEmpty) { - chain() - .deleteSelection() - .BNSplitBlock( - state.selection.from, - selectionAtBlockStart, - selectionAtBlockStart - ) - .run(); - - return true; - } - - return false; - }), - ]); - - return { - Backspace: handleBackspace, - Delete: handleDelete, - Enter: handleEnter, - // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the - // editor since the browser will try to use tab for keyboard navigation. - Tab: () => { - if ( - this.options.editor.formattingToolbar?.shown || - this.options.editor.linkToolbar?.shown || - this.options.editor.filePanel?.shown - ) { - // don't handle tabs if a toolbar is shown, so we can tab into / out of it - return false; - } - this.editor.commands.sinkListItem("blockContainer"); - return true; - }, - "Shift-Tab": () => { - if ( - this.options.editor.formattingToolbar?.shown || - this.options.editor.linkToolbar?.shown || - this.options.editor.filePanel?.shown - ) { - // don't handle tabs if a toolbar is shown, so we can tab into / out of it - return false; - } - this.editor.commands.liftListItem("blockContainer"); - return true; - }, - "Shift-Mod-ArrowUp": () => { - this.options.editor.moveBlockUp(); - return true; - }, - "Shift-Mod-ArrowDown": () => { - this.options.editor.moveBlockDown(); - return true; - }, - }; - }, }); diff --git a/packages/core/src/schema/inlineContent/createSpec.ts b/packages/core/src/schema/inlineContent/createSpec.ts index e8f13540b1..ba37d89049 100644 --- a/packages/core/src/schema/inlineContent/createSpec.ts +++ b/packages/core/src/schema/inlineContent/createSpec.ts @@ -1,10 +1,8 @@ import { Node } from "@tiptap/core"; import { TagParseRule } from "@tiptap/pm/model"; -import { - inlineContentToNodes, - nodeToCustomInlineContent, -} from "../../api/nodeConversions/nodeConversions.js"; +import { inlineContentToNodes } from "../../api/nodeConversions/blockToNode.js"; +import { nodeToCustomInlineContent } from "../../api/nodeConversions/nodeToBlock.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { propsToAttributes } from "../blocks/internal.js"; import { Props } from "../propTypes.js"; diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx index 51887e9039..5f86a68390 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx @@ -45,6 +45,10 @@ export const AddRowButton = < rows, }, }); + + // Have to reset text cursor position to the block as `updateBlock` + // moves the existing selection out of the block. + editor.setTextCursorPosition(props.block); }}> {dict.table_handle[`add_${props.side}_menuitem`]} @@ -82,6 +86,10 @@ export const AddColumnButton = < type: "table", content: content, }); + + // Have to reset text cursor position to the block as `updateBlock` + // moves the existing selection out of the block. + editor.setTextCursorPosition(props.block); }}> {dict.table_handle[`add_${props.side}_menuitem`]} diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx index fc8ebec1cc..f5bd9ae7c0 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx +++ b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/DeleteButton.tsx @@ -40,6 +40,10 @@ export const DeleteRowButton = < type: "table", content, }); + + // Have to reset text cursor position to the block as `updateBlock` + // moves the existing selection out of the block. + editor.setTextCursorPosition(props.block); }}> {dict.table_handle.delete_row_menuitem} @@ -75,6 +79,10 @@ export const DeleteColumnButton = < type: "table", content, }); + + // Have to reset text cursor position to the block as `updateBlock` + // moves the existing selection out of the block. + editor.setTextCursorPosition(props.block); }}> {dict.table_handle.delete_column_menuitem}