From 51f421677ae0318503926c3fa8f1b01d3cb695b9 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 8 Oct 2024 11:52:10 +0200 Subject: [PATCH 01/34] extract updateBlockCommand --- .../blockManipulation/blockManipulation.ts | 6 +- .../src/api/blockManipulation/updateBlock.ts | 179 ++++++++++++++ .../HeadingBlockContent.ts | 76 +++--- .../BulletListItemBlockContent.ts | 30 ++- .../CheckListItemBlockContent.ts | 60 +++-- .../ListItemKeyboardShortcuts.ts | 24 +- .../NumberedListItemBlockContent.ts | 28 ++- .../ParagraphBlockContent.ts | 16 +- packages/core/src/pm-nodes/BlockContainer.ts | 218 +----------------- 9 files changed, 333 insertions(+), 304 deletions(-) create mode 100644 packages/core/src/api/blockManipulation/updateBlock.ts diff --git a/packages/core/src/api/blockManipulation/blockManipulation.ts b/packages/core/src/api/blockManipulation/blockManipulation.ts index 1a5dd6dc6..185be35f9 100644 --- a/packages/core/src/api/blockManipulation/blockManipulation.ts +++ b/packages/core/src/api/blockManipulation/blockManipulation.ts @@ -15,6 +15,7 @@ import { nodeToBlock, } from "../nodeConversions/nodeConversions.js"; import { getNodeById } from "../nodeUtil.js"; +import { updateBlockCommand } from "./updateBlock.js"; export function insertBlocks< BSchema extends BlockSchema, @@ -105,7 +106,10 @@ export function updateBlock< typeof blockToUpdate === "string" ? blockToUpdate : blockToUpdate.id; const { posBeforeNode } = getNodeById(id, ttEditor.state.doc); - ttEditor.commands.BNUpdateBlock(posBeforeNode + 1, update); + ttEditor.commands.command(({ state, dispatch }) => { + updateBlockCommand(editor, posBeforeNode + 1, update)({ state, dispatch }); + return true; + }); const blockContainerNode = ttEditor.state.doc .resolve(posBeforeNode + 1) diff --git a/packages/core/src/api/blockManipulation/updateBlock.ts b/packages/core/src/api/blockManipulation/updateBlock.ts new file mode 100644 index 000000000..e836dbfb8 --- /dev/null +++ b/packages/core/src/api/blockManipulation/updateBlock.ts @@ -0,0 +1,179 @@ +import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; +import { EditorState, 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 { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import { 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"; + +export const updateBlockCommand = + < + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema + >( + editor: BlockNoteEditor, + posInBlock: number, + block: PartialBlock + ) => + ({ + state, + dispatch, + }: { + state: EditorState; + dispatch: ((args?: any) => any) | undefined; + }) => { + 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, 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, + 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( + 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; + }; diff --git a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index 1c61bcf40..88cf41741 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -1,4 +1,5 @@ import { InputRule } from "@tiptap/core"; +import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; import { PropSchema, @@ -51,14 +52,17 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ } chain() - .BNUpdateBlock(state.selection.from, { - type: "heading", - props: { - level: level as any, - }, - }) + .command( + updateBlockCommand(this.options.editor, state.selection.from, { + 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(); }, }); }), @@ -72,14 +76,18 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ 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, + this.editor.state.selection.anchor, + { + type: "heading", + props: { + level: 1 as any, + }, + } + ) ); }, "Mod-Alt-2": () => { @@ -87,14 +95,17 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ 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, + this.editor.state.selection.anchor, + { + type: "heading", + props: { + level: 2 as any, + }, + } + ) ); }, "Mod-Alt-3": () => { @@ -102,14 +113,17 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ 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, + this.editor.state.selection.anchor, + { + 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 4d427b3a7..73bc81fac 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -1,4 +1,5 @@ import { InputRule } from "@tiptap/core"; +import { updateBlockCommand } from "../../../api/blockManipulation/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, @@ -31,10 +32,12 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ } chain() - .BNUpdateBlock(state.selection.from, { - type: "bulletListItem", - props: {}, - }) + .command( + updateBlockCommand(this.options.editor, state.selection.from, { + type: "bulletListItem", + props: {}, + }) + ) // Removes the "-", "+", or "*" character used to set the list. .deleteRange({ from: range.from, to: range.to }); }, @@ -44,18 +47,21 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { - Enter: () => handleEnter(this.editor), + Enter: () => handleEnter(this.options.editor), "Mod-Shift-8": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if (getCurrentBlockContentType(this.options.editor) !== "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, + this.options.editor.state.selection.anchor, + { + type: "bulletListItem", + props: {}, + } + ) ); }, }; diff --git a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts index da69ffb52..e6b693894 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -1,4 +1,5 @@ import { InputRule } from "@tiptap/core"; +import { updateBlockCommand } from "../../../api/blockManipulation/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, @@ -49,12 +50,14 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ } chain() - .BNUpdateBlock(state.selection.from, { - type: "checkListItem", - props: { - checked: false as any, - }, - }) + .command( + updateBlockCommand(this.options.editor, state.selection.from, { + type: "checkListItem", + props: { + checked: false as any, + }, + }) + ) // Removes the characters used to set the list. .deleteRange({ from: range.from, to: range.to }); }, @@ -67,12 +70,14 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ } chain() - .BNUpdateBlock(state.selection.from, { - type: "checkListItem", - props: { - checked: true as any, - }, - }) + .command( + updateBlockCommand(this.options.editor, state.selection.from, { + type: "checkListItem", + props: { + checked: true as any, + }, + }) + ) // Removes the characters used to set the list. .deleteRange({ from: range.from, to: range.to }); }, @@ -82,18 +87,21 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { - Enter: () => handleEnter(this.editor), + Enter: () => handleEnter(this.options.editor), "Mod-Shift-9": () => { if (getCurrentBlockContentType(this.editor) !== "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, + this.editor.state.selection.anchor, + { + type: "checkListItem", + props: {}, + } + ) ); }, }; @@ -212,12 +220,14 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ } if (typeof getPos !== "boolean") { - this.editor.commands.BNUpdateBlock(getPos(), { - type: "checkListItem", - props: { - checked: checkbox.checked as any, - }, - }); + this.editor.commands.command( + updateBlockCommand(this.options.editor, getPos(), { + 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 3a740b021..dc5e58f16 100644 --- a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +++ b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts @@ -1,14 +1,16 @@ -import { Editor } from "@tiptap/core"; +import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -export const handleEnter = (editor: Editor) => { +export const handleEnter = (editor: BlockNoteEditor) => { + const ttEditor = editor._tiptapEditor; const { contentNode, contentType } = getBlockInfoFromPos( - editor.state.doc, - editor.state.selection.from + ttEditor.state.doc, + ttEditor.state.selection.from )!; const selectionEmpty = - editor.state.selection.anchor === editor.state.selection.head; + ttEditor.state.selection.anchor === ttEditor.state.selection.head; if ( !( @@ -21,15 +23,17 @@ export const handleEnter = (editor: Editor) => { 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: {}, - }); + return commands.command( + updateBlockCommand(editor, state.selection.from, { + type: "paragraph", + props: {}, + }) + ); } return false; diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts index d5e608012..cd94682e8 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -1,4 +1,5 @@ import { InputRule } from "@tiptap/core"; +import { updateBlockCommand } from "../../../api/blockManipulation/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, @@ -44,10 +45,12 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ } chain() - .BNUpdateBlock(state.selection.from, { - type: "numberedListItem", - props: {}, - }) + .command( + updateBlockCommand(this.options.editor, state.selection.from, { + type: "numberedListItem", + props: {}, + }) + ) // Removes the "1." characters used to set the list. .deleteRange({ from: range.from, to: range.to }); }, @@ -57,18 +60,21 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { - Enter: () => handleEnter(this.editor), + Enter: () => handleEnter(this.options.editor), "Mod-Shift-7": () => { if (getCurrentBlockContentType(this.editor) !== "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, + this.editor.state.selection.anchor, + { + type: "numberedListItem", + props: {}, + } + ) ); }, }; diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts index 21fc496ba..364493ddd 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -1,3 +1,4 @@ +import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; import { createBlockSpecFromStronglyTypedTiptapNode, @@ -22,12 +23,15 @@ export const ParagraphBlockContent = createStronglyTypedTiptapNode({ return true; } - return this.editor.commands.BNUpdateBlock( - this.editor.state.selection.anchor, - { - type: "paragraph", - props: {}, - } + return this.editor.commands.command( + updateBlockCommand( + this.options.editor, + this.editor.state.selection.anchor, + { + type: "paragraph", + props: {}, + } + ) ); }, }; diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 209650ada..85909297f 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -1,24 +1,13 @@ import { Node } from "@tiptap/core"; -import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; -import { NodeSelection, TextSelection } from "prosemirror-state"; +import { Fragment, Slice } from "prosemirror-model"; +import { TextSelection } from "prosemirror-state"; +import { updateBlockCommand } from "../api/blockManipulation/updateBlock.js"; 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 = { @@ -33,29 +22,12 @@ 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; }; } } @@ -147,179 +119,7 @@ export const BlockContainer = Node.create<{ 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. // @@ -514,10 +314,12 @@ export const BlockContainer = Node.create<{ const isParagraph = contentType.name === "paragraph"; if (selectionAtBlockStart && !isParagraph) { - return commands.BNUpdateBlock(state.selection.from, { - type: "paragraph", - props: {}, - }); + return commands.command( + updateBlockCommand(this.options.editor, state.selection.from, { + type: "paragraph", + props: {}, + }) + ); } return false; From 18ff213201e0a51a9991255c6acbf368b8f61195 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 8 Oct 2024 12:09:55 +0200 Subject: [PATCH 02/34] Extracted remaining commands --- .../src/api/blockManipulation/createBlock.ts | 19 ++ .../src/api/blockManipulation/mergeBlocks.ts | 77 ++++++ .../src/api/blockManipulation/splitBlock.ts | 75 ++++++ .../ListItemKeyboardShortcuts.ts | 3 +- packages/core/src/pm-nodes/BlockContainer.ts | 221 +----------------- 5 files changed, 185 insertions(+), 210 deletions(-) create mode 100644 packages/core/src/api/blockManipulation/createBlock.ts create mode 100644 packages/core/src/api/blockManipulation/mergeBlocks.ts create mode 100644 packages/core/src/api/blockManipulation/splitBlock.ts diff --git a/packages/core/src/api/blockManipulation/createBlock.ts b/packages/core/src/api/blockManipulation/createBlock.ts new file mode 100644 index 000000000..90697d7b1 --- /dev/null +++ b/packages/core/src/api/blockManipulation/createBlock.ts @@ -0,0 +1,19 @@ +import { EditorState } from "prosemirror-state"; + +export const createBlockCommand = + (pos: number) => + ({ + state, + dispatch, + }: { + state: EditorState; + dispatch: ((args?: any) => any) | undefined; + }) => { + const newBlock = state.schema.nodes["blockContainer"].createAndFill()!; + + if (dispatch) { + state.tr.insert(pos, newBlock).scrollIntoView(); + } + + return true; + }; diff --git a/packages/core/src/api/blockManipulation/mergeBlocks.ts b/packages/core/src/api/blockManipulation/mergeBlocks.ts new file mode 100644 index 000000000..941b226c3 --- /dev/null +++ b/packages/core/src/api/blockManipulation/mergeBlocks.ts @@ -0,0 +1,77 @@ +import { Slice } from "prosemirror-model"; +import { EditorState, TextSelection } from "prosemirror-state"; + +import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; + +export const mergeBlocksCommand = + (posBetweenBlocks: number) => + ({ + state, + dispatch, + }: { + state: EditorState; + dispatch: ((args?: any) => any) | undefined; + }) => { + 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; + }; diff --git a/packages/core/src/api/blockManipulation/splitBlock.ts b/packages/core/src/api/blockManipulation/splitBlock.ts new file mode 100644 index 000000000..2e33baa25 --- /dev/null +++ b/packages/core/src/api/blockManipulation/splitBlock.ts @@ -0,0 +1,75 @@ +import { Fragment, Slice } from "prosemirror-model"; +import { EditorState, TextSelection } from "prosemirror-state"; + +import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; + +export const splitBlockCommand = + (posInBlock: number, keepType?: boolean, keepProps?: boolean) => + ({ + state, + dispatch, + }: { + state: EditorState; + dispatch: ((args?: any) => any) | undefined; + }) => { + 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; + }; diff --git a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts index dc5e58f16..1ca7796dc 100644 --- a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +++ b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts @@ -1,4 +1,5 @@ import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; +import { splitBlockCommand } from "../../api/blockManipulation/splitBlock.js"; import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; @@ -46,7 +47,7 @@ export const handleEnter = (editor: BlockNoteEditor) => { if (contentNode.childCount > 0) { chain() .deleteSelection() - .BNSplitBlock(state.selection.from, true) + .command(splitBlockCommand(state.selection.from, true)) .run(); return true; diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 85909297f..8d4a637bd 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -1,7 +1,8 @@ import { Node } from "@tiptap/core"; -import { Fragment, Slice } from "prosemirror-model"; -import { TextSelection } from "prosemirror-state"; +import { createBlockCommand } from "../api/blockManipulation/createBlock.js"; +import { mergeBlocksCommand } from "../api/blockManipulation/mergeBlocks.js"; +import { splitBlockCommand } from "../api/blockManipulation/splitBlock.js"; import { updateBlockCommand } from "../api/blockManipulation/updateBlock.js"; import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; @@ -18,20 +19,6 @@ const BlockAttributes: Record = { depthChange: "data-depth-change", }; -declare module "@tiptap/core" { - interface Commands { - block: { - BNCreateBlock: (pos: number) => ReturnType; - BNMergeBlocks: (posBetweenBlocks: number) => ReturnType; - BNSplitBlock: ( - posInBlock: number, - keepType?: boolean, - keepProps?: boolean - ) => ReturnType; - }; - } -} - /** * The main "Block node" documents consist of */ @@ -104,192 +91,6 @@ export const BlockContainer = Node.create<{ }; }, - 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; - }, - - // 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()]; }, @@ -361,7 +162,7 @@ export const BlockContainer = Node.create<{ selectionEmpty && depth === 2 ) { - return commands.BNMergeBlocks(posBetweenBlocks); + return commands.command(mergeBlocksCommand(posBetweenBlocks)); } return false; @@ -403,7 +204,7 @@ export const BlockContainer = Node.create<{ newDepth = state.doc.resolve(newPos).depth; } - return commands.BNMergeBlocks(newPos - 1); + return commands.command(mergeBlocksCommand(newPos - 1)); } return false; @@ -459,7 +260,7 @@ export const BlockContainer = Node.create<{ const newBlockContentPos = newBlockInsertionPos + 2; chain() - .BNCreateBlock(newBlockInsertionPos) + .command(createBlockCommand(newBlockInsertionPos)) .setTextSelection(newBlockContentPos) .run(); @@ -484,10 +285,12 @@ export const BlockContainer = Node.create<{ if (!blockEmpty) { chain() .deleteSelection() - .BNSplitBlock( - state.selection.from, - selectionAtBlockStart, - selectionAtBlockStart + .command( + splitBlockCommand( + state.selection.from, + selectionAtBlockStart, + selectionAtBlockStart + ) ) .run(); From de6f1ecb35d7f94c76c372218f570fca344dad64 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 8 Oct 2024 12:34:52 +0200 Subject: [PATCH 03/34] extract keyboard shortcuts --- packages/core/src/editor/BlockNoteEditor.ts | 2 + .../core/src/editor/BlockNoteExtensions.ts | 4 + .../KeyboardShortcutsExtension.ts | 261 ++++++++++++++++++ .../NodeSelectionKeyboardPlugin.ts} | 4 +- packages/core/src/pm-nodes/BlockContainer.ts | 257 ----------------- 5 files changed, 269 insertions(+), 259 deletions(-) create mode 100644 packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts rename packages/core/src/extensions/{NonEditableBlocks/NonEditableBlockPlugin.ts => NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts} (95%) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 1c84cd639..e50a033f6 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -71,6 +71,7 @@ import { en } from "../i18n/locales/index.js"; import { Transaction } from "@tiptap/pm/state"; import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.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 +383,7 @@ export class BlockNoteEditor< ...(this.filePanel ? [this.filePanel.plugin] : []), ...(this.tableHandles ? [this.tableHandles.plugin] : []), PlaceholderPlugin(this, newOptions.placeholders), + NodeSelectionKeyboardPlugin(), ...(this.options.animations ?? true ? [PreviousBlockTypePlugin()] : []), diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index c8683e2ac..333fc8fd3 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/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts new file mode 100644 index 000000000..51769583c --- /dev/null +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -0,0 +1,261 @@ +import { Extension } from "@tiptap/core"; + +import { createBlockCommand } from "../../api/blockManipulation/createBlock.js"; +import { mergeBlocksCommand } from "../../api/blockManipulation/mergeBlocks.js"; +import { splitBlockCommand } from "../../api/blockManipulation/splitBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; +import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; + +export const KeyboardShortcutsExtension = Extension.create<{ + editor: BlockNoteEditor; +}>({ + priority: 50, + + 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.command( + updateBlockCommand(this.options.editor, 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.command(mergeBlocksCommand(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.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 { 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() + .command(createBlockCommand(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() + .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 2247d26a8..f50d26560 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/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 8d4a637bd..81e8b46b1 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -1,12 +1,6 @@ import { Node } from "@tiptap/core"; -import { createBlockCommand } from "../api/blockManipulation/createBlock.js"; -import { mergeBlocksCommand } from "../api/blockManipulation/mergeBlocks.js"; -import { splitBlockCommand } from "../api/blockManipulation/splitBlock.js"; -import { updateBlockCommand } from "../api/blockManipulation/updateBlock.js"; -import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; -import { NonEditableBlockPlugin } from "../extensions/NonEditableBlocks/NonEditableBlockPlugin.js"; import { BlockNoteDOMAttributes } from "../schema/index.js"; import { mergeCSSClasses } from "../util/browser.js"; @@ -90,255 +84,4 @@ export const BlockContainer = Node.create<{ contentDOM: block, }; }, - - 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.command( - updateBlockCommand(this.options.editor, 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.command(mergeBlocksCommand(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.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 { 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() - .command(createBlockCommand(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() - .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; - }, - }; - }, }); From c3cea795e69a33cbb854b6623616a03ee84cdc0a Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 8 Oct 2024 12:36:22 +0200 Subject: [PATCH 04/34] move directory --- .../api/blockManipulation/blockManipulation.ts | 2 +- .../{ => commands}/createBlock.ts | 0 .../{ => commands}/mergeBlocks.ts | 2 +- .../{ => commands}/splitBlock.ts | 2 +- .../{ => commands}/updateBlock.ts | 16 ++++++++-------- .../HeadingBlockContent/HeadingBlockContent.ts | 2 +- .../BulletListItemBlockContent.ts | 2 +- .../CheckListItemBlockContent.ts | 2 +- .../ListItemKeyboardShortcuts.ts | 4 ++-- .../NumberedListItemBlockContent.ts | 2 +- .../ParagraphBlockContent.ts | 2 +- .../KeyboardShortcutsExtension.ts | 8 ++++---- 12 files changed, 22 insertions(+), 22 deletions(-) rename packages/core/src/api/blockManipulation/{ => commands}/createBlock.ts (100%) rename packages/core/src/api/blockManipulation/{ => commands}/mergeBlocks.ts (96%) rename packages/core/src/api/blockManipulation/{ => commands}/splitBlock.ts (97%) rename packages/core/src/api/blockManipulation/{ => commands}/updateBlock.ts (92%) diff --git a/packages/core/src/api/blockManipulation/blockManipulation.ts b/packages/core/src/api/blockManipulation/blockManipulation.ts index 185be35f9..b2b1b9e3f 100644 --- a/packages/core/src/api/blockManipulation/blockManipulation.ts +++ b/packages/core/src/api/blockManipulation/blockManipulation.ts @@ -15,7 +15,7 @@ import { nodeToBlock, } from "../nodeConversions/nodeConversions.js"; import { getNodeById } from "../nodeUtil.js"; -import { updateBlockCommand } from "./updateBlock.js"; +import { updateBlockCommand } from "./commands/updateBlock.js"; export function insertBlocks< BSchema extends BlockSchema, diff --git a/packages/core/src/api/blockManipulation/createBlock.ts b/packages/core/src/api/blockManipulation/commands/createBlock.ts similarity index 100% rename from packages/core/src/api/blockManipulation/createBlock.ts rename to packages/core/src/api/blockManipulation/commands/createBlock.ts diff --git a/packages/core/src/api/blockManipulation/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks.ts similarity index 96% rename from packages/core/src/api/blockManipulation/mergeBlocks.ts rename to packages/core/src/api/blockManipulation/commands/mergeBlocks.ts index 941b226c3..438e89aa9 100644 --- a/packages/core/src/api/blockManipulation/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks.ts @@ -1,7 +1,7 @@ import { Slice } from "prosemirror-model"; import { EditorState, TextSelection } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; export const mergeBlocksCommand = (posBetweenBlocks: number) => diff --git a/packages/core/src/api/blockManipulation/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock.ts similarity index 97% rename from packages/core/src/api/blockManipulation/splitBlock.ts rename to packages/core/src/api/blockManipulation/commands/splitBlock.ts index 2e33baa25..9d9fe02ea 100644 --- a/packages/core/src/api/blockManipulation/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock.ts @@ -1,7 +1,7 @@ import { Fragment, Slice } from "prosemirror-model"; import { EditorState, TextSelection } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; export const splitBlockCommand = (posInBlock: number, keepType?: boolean, keepProps?: boolean) => diff --git a/packages/core/src/api/blockManipulation/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock.ts similarity index 92% rename from packages/core/src/api/blockManipulation/updateBlock.ts rename to packages/core/src/api/blockManipulation/commands/updateBlock.ts index e836dbfb8..a02348a7c 100644 --- a/packages/core/src/api/blockManipulation/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock.ts @@ -1,18 +1,18 @@ import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { PartialBlock } from "../../../blocks/defaultBlocks.js"; +import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; +import { 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 { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; import { blockToNode, inlineContentToNodes, tableContentToNodes, -} from "../../api/nodeConversions/nodeConversions.js"; -import { PartialBlock } from "../../blocks/defaultBlocks.js"; -import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import { 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"; +} from "../../nodeConversions/nodeConversions.js"; export const updateBlockCommand = < diff --git a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index 88cf41741..660a66be6 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -1,5 +1,5 @@ import { InputRule } from "@tiptap/core"; -import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; import { PropSchema, diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts index 73bc81fac..7f5b87454 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -1,5 +1,5 @@ import { InputRule } from "@tiptap/core"; -import { updateBlockCommand } from "../../../api/blockManipulation/updateBlock.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, diff --git a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts index e6b693894..6755fd7b3 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -1,5 +1,5 @@ import { InputRule } from "@tiptap/core"; -import { updateBlockCommand } from "../../../api/blockManipulation/updateBlock.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, diff --git a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts index 1ca7796dc..9e5749f26 100644 --- a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +++ b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts @@ -1,5 +1,5 @@ -import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; -import { splitBlockCommand } from "../../api/blockManipulation/splitBlock.js"; +import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts index cd94682e8..269c48b98 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -1,5 +1,5 @@ import { InputRule } from "@tiptap/core"; -import { updateBlockCommand } from "../../../api/blockManipulation/updateBlock.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts index 364493ddd..eeefcbb41 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -1,4 +1,4 @@ -import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; import { createBlockSpecFromStronglyTypedTiptapNode, diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 51769583c..512acdf15 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -1,9 +1,9 @@ import { Extension } from "@tiptap/core"; -import { createBlockCommand } from "../../api/blockManipulation/createBlock.js"; -import { mergeBlocksCommand } from "../../api/blockManipulation/mergeBlocks.js"; -import { splitBlockCommand } from "../../api/blockManipulation/splitBlock.js"; -import { updateBlockCommand } from "../../api/blockManipulation/updateBlock.js"; +import { createBlockCommand } from "../../api/blockManipulation/commands/createBlock.js"; +import { mergeBlocksCommand } from "../../api/blockManipulation/commands/mergeBlocks.js"; +import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; From 717301eafc98fe5c93bbe5061131296abe3fab3e Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 8 Oct 2024 14:43:11 +0200 Subject: [PATCH 05/34] remove createblockcommand --- .../blockManipulation/commands/createBlock.ts | 19 ------------------- .../KeyboardShortcutsExtension.ts | 19 +++++++++++++------ 2 files changed, 13 insertions(+), 25 deletions(-) delete mode 100644 packages/core/src/api/blockManipulation/commands/createBlock.ts diff --git a/packages/core/src/api/blockManipulation/commands/createBlock.ts b/packages/core/src/api/blockManipulation/commands/createBlock.ts deleted file mode 100644 index 90697d7b1..000000000 --- a/packages/core/src/api/blockManipulation/commands/createBlock.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { EditorState } from "prosemirror-state"; - -export const createBlockCommand = - (pos: number) => - ({ - state, - dispatch, - }: { - state: EditorState; - dispatch: ((args?: any) => any) | undefined; - }) => { - const newBlock = state.schema.nodes["blockContainer"].createAndFill()!; - - if (dispatch) { - state.tr.insert(pos, newBlock).scrollIntoView(); - } - - return true; - }; diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 512acdf15..ef5ad21c7 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -1,6 +1,6 @@ import { Extension } from "@tiptap/core"; -import { createBlockCommand } from "../../api/blockManipulation/commands/createBlock.js"; +import { TextSelection } from "prosemirror-state"; import { mergeBlocksCommand } from "../../api/blockManipulation/commands/mergeBlocks.js"; import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock.js"; import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; @@ -160,7 +160,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // Creates a new block and moves the selection to it if the current one is empty, while the selection is also // empty & at the start of the block. () => - commands.command(({ state, chain }) => { + commands.command(({ state, dispatch }) => { const { contentNode, endPos } = getBlockInfoFromPos( state.doc, state.selection.from @@ -176,10 +176,17 @@ export const KeyboardShortcutsExtension = Extension.create<{ const newBlockInsertionPos = endPos + 1; const newBlockContentPos = newBlockInsertionPos + 2; - chain() - .command(createBlockCommand(newBlockInsertionPos)) - .setTextSelection(newBlockContentPos) - .run(); + 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; } From 9c32c926b51230206e9d7538c9a8098ea5673e9a Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 8 Oct 2024 20:25:02 +0200 Subject: [PATCH 06/34] Added merge/split tests --- .../__snapshots__/moveBlock.test.ts.snap | 2008 +++++++++-- .../__snapshots__/mergeBlocks.test.ts.snap | 2993 +++++++++++++++++ .../__snapshots__/splitBlocks.test.ts.snap | 2245 +++++++++++++ .../commands/mergeBlocks.test.ts | 101 + .../commands/splitBlocks.test.ts | 84 + .../api/blockManipulation/moveBlock.test.ts | 81 +- .../src/api/blockManipulation/testDocument.ts | 114 + 7 files changed, 7224 insertions(+), 402 deletions(-) create mode 100644 packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap create mode 100644 packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap create mode 100644 packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts create mode 100644 packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts create mode 100644 packages/core/src/api/blockManipulation/testDocument.ts diff --git a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap b/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap index 4da0fdcee..f82f813a7 100644 --- a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap @@ -2,6 +2,23 @@ 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": [ @@ -22,7 +39,25 @@ exports[`Test moveBlockDown > 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": {}, @@ -42,11 +77,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 +94,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 +111,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", + "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 +171,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,11 +193,11 @@ 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", "textAlignment": "left", @@ -282,11 +210,11 @@ exports[`Test moveBlockDown > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 5", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-5", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -297,7 +225,7 @@ exports[`Test moveBlockDown > Into children 1`] = ` { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -309,6 +237,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,13 +336,30 @@ 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": [], @@ -412,7 +374,7 @@ exports[`Test moveBlockDown > Into children 1`] = ` ] `; -exports[`Test moveBlockDown > Last block 1`] = ` +exports[`Test moveBlockDown > Into children 1`] = ` [ { "children": [], @@ -435,6 +397,41 @@ exports[`Test moveBlockDown > Last block 1`] = ` "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 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": [ { "styles": {}, @@ -454,11 +451,11 @@ exports[`Test moveBlockDown > Last block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph with children", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -488,11 +485,110 @@ exports[`Test moveBlockDown > Last block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "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", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -503,7 +599,7 @@ exports[`Test moveBlockDown > Last block 1`] = ` { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -515,6 +611,23 @@ exports[`Test moveBlockDown > Last 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": { @@ -597,13 +710,30 @@ exports[`Test moveBlockDown > Last 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": [], @@ -618,7 +748,7 @@ exports[`Test moveBlockDown > Last block 1`] = ` ] `; -exports[`Test moveBlockDown > Out of children 1`] = ` +exports[`Test moveBlockDown > Last block 1`] = ` [ { "children": [], @@ -655,15 +785,51 @@ exports[`Test moveBlockDown > Out of children 1`] = ` "type": "paragraph", }, { - "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": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": [ { "styles": {}, - "text": "Nested Paragraph 1", + "text": "Paragraph with children", "type": "text", }, ], - "id": "nested-paragraph-1", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -688,6 +854,23 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "red", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -705,10 +888,92 @@ exports[`Test moveBlockDown > 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", + "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-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -720,6 +985,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,7 +1084,7 @@ exports[`Test moveBlockDown > Out of children 1`] = ` ], "type": "tableContent", }, - "id": "table-1", + "id": "table-0", "props": { "backgroundColor": "default", "textColor": "default", @@ -811,8 +1093,14 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, { "children": [], - "content": [], - "id": "trailing-paragraph", + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -820,14 +1108,772 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlockUp > Basic 1`] = ` -[ { "children": [], - "content": [ + "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": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "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": {}, + "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": "left", + "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", + "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": "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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlockUp > First block 1`] = ` +[ + { + "children": [], + "content": [ { "styles": {}, "text": "Paragraph 0", @@ -842,10 +1888,45 @@ exports[`Test moveBlockUp > Basic 1`] = ` }, "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": [ + { + "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": {}, @@ -865,11 +1946,28 @@ exports[`Test moveBlockUp > 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", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -877,6 +1975,23 @@ exports[`Test moveBlockUp > Basic 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "red", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -897,13 +2012,78 @@ exports[`Test moveBlockUp > Basic 1`] = ` { "children": [], "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, { "styles": {}, - "text": "Paragraph 2", + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", "type": "text", }, ], - "id": "paragraph-2", + "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", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -914,7 +2094,7 @@ exports[`Test moveBlockUp > Basic 1`] = ` { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -926,6 +2106,23 @@ exports[`Test moveBlockUp > Basic 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": { @@ -1008,13 +2205,30 @@ exports[`Test moveBlockUp > Basic 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": [], @@ -1029,7 +2243,7 @@ exports[`Test moveBlockUp > Basic 1`] = ` ] `; -exports[`Test moveBlockUp > First block 1`] = ` +exports[`Test moveBlockUp > Into children 1`] = ` [ { "children": [], @@ -1048,10 +2262,45 @@ exports[`Test moveBlockUp > First block 1`] = ` }, "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": [ + { + "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": {}, @@ -1065,17 +2314,116 @@ exports[`Test moveBlockUp > First block 1`] = ` "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "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": "left", + "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 1", + "text": "Paragraph 4", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-4", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1088,11 +2436,11 @@ 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", "textAlignment": "left", @@ -1105,11 +2453,11 @@ exports[`Test moveBlockUp > First block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 5", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-5", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1120,7 +2468,7 @@ exports[`Test moveBlockUp > First block 1`] = ` { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -1132,6 +2480,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,13 +2579,30 @@ 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": [], @@ -1235,7 +2617,7 @@ exports[`Test moveBlockUp > First block 1`] = ` ] `; -exports[`Test moveBlockUp > Into children 1`] = ` +exports[`Test moveBlockUp > Out of children 1`] = ` [ { "children": [], @@ -1255,34 +2637,34 @@ exports[`Test moveBlockUp > Into children 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", - }, - "type": "paragraph", + "styles": {}, + "text": "Paragraph 1", + "type": "text", }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1294,11 +2676,11 @@ exports[`Test moveBlockUp > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "paragraph-1", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1311,11 +2693,11 @@ exports[`Test moveBlockUp > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph with children", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1325,134 +2707,48 @@ exports[`Test moveBlockUp > Into children 1`] = ` }, { "children": [], - "content": undefined, - "id": "image-1", + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", "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", - }, - ], - [ - { - "styles": {}, - "text": "Cell 9", - "type": "text", - }, - ], - ], - }, - ], - "type": "tableContent", - }, - "id": "table-1", - "props": { - "backgroundColor": "default", "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", + "textColor": "red", }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlockUp > Out of children 1`] = ` -[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph 3", "type": "text", }, ], - "id": "paragraph-0", + "id": "paragraph-3", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1463,13 +2759,27 @@ exports[`Test moveBlockUp > 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", @@ -1482,11 +2792,11 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 4", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-4", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1499,11 +2809,11 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Heading 1", "type": "text", }, ], - "id": "paragraph-2", + "id": "heading-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1516,11 +2826,11 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 3", + "text": "Paragraph 5", "type": "text", }, ], - "id": "paragraph-3", + "id": "paragraph-5", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1531,7 +2841,7 @@ exports[`Test moveBlockUp > Out of children 1`] = ` { "children": [], "content": undefined, - "id": "image-1", + "id": "image-0", "props": { "backgroundColor": "default", "caption": "", @@ -1543,6 +2853,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 +2952,30 @@ 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": [], diff --git a/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap new file mode 100644 index 000000000..a52c7320d --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap @@ -0,0 +1,2993 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test mergeBlocks > Basic 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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test mergeBlocks > Blocks have different types 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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test mergeBlocks > First 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": [ + { + "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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test mergeBlocks > Inline content & 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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test mergeBlocks > Inline content & 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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test mergeBlocks > No content & 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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test mergeBlocks > Second 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": [ + { + "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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test mergeBlocks > Table content & 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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap new file mode 100644 index 000000000..0ca517c5f --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap @@ -0,0 +1,2245 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test splitBlocks > Basic 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 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": "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": "left", + "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", + "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": "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": [ + { + "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": "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": "left", + "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", + "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": "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 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": "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": "left", + "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", + "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": "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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test splitBlocks > 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 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": "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": "left", + "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", + "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": "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 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": "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": "left", + "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", + "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": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts new file mode 100644 index 000000000..4bd764bee --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts @@ -0,0 +1,101 @@ +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; +import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; +import { testDocument } from "../testDocument.js"; + +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); +}); + +function mergeBlocks(_posBetweenBlocks: number) { + // TODO: Replace with imported function after converting from TipTap command +} + +function getPosAfterSelectedBlock() { + const { startPos } = getBlockInfoFromPos( + editor._tiptapEditor.state.doc, + editor._tiptapEditor.state.selection.from + ); + return editor._tiptapEditor.state.doc.resolve(startPos).after(); +} + +describe("Test mergeBlocks", () => { + it("Basic", () => { + editor.setTextCursorPosition("paragraph-0"); + + mergeBlocks(getPosAfterSelectedBlock()); + + expect(editor.document).toMatchSnapshot(); + }); + + it("First block has children", () => { + editor.setTextCursorPosition("paragraph-with-children"); + + mergeBlocks(getPosAfterSelectedBlock()); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Second block has children", () => { + editor.setTextCursorPosition("paragraph-1"); + + mergeBlocks(getPosAfterSelectedBlock()); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Blocks have different types", () => { + editor.setTextCursorPosition("heading-0"); + + mergeBlocks(getPosAfterSelectedBlock()); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Inline content & no content", () => { + editor.setTextCursorPosition("paragraph-5"); + + mergeBlocks(getPosAfterSelectedBlock()); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Inline content & table content", () => { + editor.setTextCursorPosition("paragraph-6"); + + mergeBlocks(getPosAfterSelectedBlock()); + + expect(editor.document).toMatchSnapshot(); + }); + + it("No content & inline content", () => { + editor.setTextCursorPosition("image-0"); + + mergeBlocks(getPosAfterSelectedBlock()); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Table content & inline content", () => { + editor.setTextCursorPosition("table-0"); + + mergeBlocks(getPosAfterSelectedBlock()); + + expect(editor.document).toMatchSnapshot(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts new file mode 100644 index 000000000..f8597b3cb --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts @@ -0,0 +1,84 @@ +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; +import { testDocument } from "../testDocument.js"; + +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); +}); + +function splitBlocks( + _posInBlock: number, + _keepType?: boolean, + _keepProps?: boolean +) { + // TODO: Replace with imported function after converting from TipTap command +} + +function getSelectionAnchorPosWithOffset(offset: number) { + return editor._tiptapEditor.state.selection.anchor + offset; +} + +describe("Test splitBlocks", () => { + it("Basic", () => { + editor.setTextCursorPosition("paragraph-0"); + + splitBlocks(getSelectionAnchorPosWithOffset(4)); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Block has children", () => { + editor.setTextCursorPosition("paragraph-1"); + + splitBlocks(getSelectionAnchorPosWithOffset(4)); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Keep type", () => { + editor.setTextCursorPosition("heading-0"); + + splitBlocks(getSelectionAnchorPosWithOffset(4), true); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Don't keep type", () => { + editor.setTextCursorPosition("heading-0"); + + splitBlocks(getSelectionAnchorPosWithOffset(4)); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Keep props", () => { + editor.setTextCursorPosition("paragraph-with-props"); + + splitBlocks(getSelectionAnchorPosWithOffset(4), false, true); + + expect(editor.document).toMatchSnapshot(); + }); + + it("Don't keep props", () => { + editor.setTextCursorPosition("paragraph-with-props"); + + splitBlocks(getSelectionAnchorPosWithOffset(4)); + + expect(editor.document).toMatchSnapshot(); + }); +}); diff --git a/packages/core/src/api/blockManipulation/moveBlock.test.ts b/packages/core/src/api/blockManipulation/moveBlock.test.ts index 35fc4d59e..8d6bf938a 100644 --- a/packages/core/src/api/blockManipulation/moveBlock.test.ts +++ b/packages/core/src/api/blockManipulation/moveBlock.test.ts @@ -1,7 +1,6 @@ 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 { @@ -9,65 +8,7 @@ import { 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", - }, -]; +import { testDocument } from "./testDocument.js"; let editor: BlockNoteEditor; const div = document.createElement("div"); @@ -145,44 +86,44 @@ afterAll(() => { }); beforeEach(() => { - editor.replaceBlocks(editor.document, blocks); + editor.replaceBlocks(editor.document, testDocument); }); describe("Test moveSelectedBlockAndSelection", () => { it("Text selection", () => { - editor.setTextCursorPosition("paragraph-2"); + editor.setTextCursorPosition("paragraph-1"); makeSelectionSpanContent("text"); moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("paragraph-2"); + editor.setTextCursorPosition("paragraph-1"); makeSelectionSpanContent("text"); expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); }); it("Node selection", () => { - editor.setTextCursorPosition("image-1"); + editor.setTextCursorPosition("image-0"); makeSelectionSpanContent("node"); moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("image-1"); + editor.setTextCursorPosition("image-0"); makeSelectionSpanContent("node"); expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); }); it("Cell selection", () => { - editor.setTextCursorPosition("table-1"); + editor.setTextCursorPosition("table-0"); makeSelectionSpanContent("cell"); moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("table-1"); + editor.setTextCursorPosition("table-0"); makeSelectionSpanContent("cell"); expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); @@ -191,7 +132,7 @@ describe("Test moveSelectedBlockAndSelection", () => { describe("Test moveBlockUp", () => { it("Basic", () => { - editor.setTextCursorPosition("paragraph-3"); + editor.setTextCursorPosition("paragraph-1"); moveBlockUp(editor); @@ -225,7 +166,7 @@ describe("Test moveBlockUp", () => { describe("Test moveBlockDown", () => { it("Basic", () => { - editor.setTextCursorPosition("paragraph-2"); + editor.setTextCursorPosition("paragraph-0"); moveBlockDown(editor); @@ -233,7 +174,7 @@ describe("Test moveBlockDown", () => { }); it("Into children", () => { - editor.setTextCursorPosition("paragraph-0"); + editor.setTextCursorPosition("paragraph-1"); moveBlockDown(editor); diff --git a/packages/core/src/api/blockManipulation/testDocument.ts b/packages/core/src/api/blockManipulation/testDocument.ts new file mode 100644 index 000000000..e19f7ac89 --- /dev/null +++ b/packages/core/src/api/blockManipulation/testDocument.ts @@ -0,0 +1,114 @@ +import { PartialBlock } from "../../blocks/defaultBlocks.js"; + +export 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-1", + type: "paragraph", + content: "Nested Paragraph 1", + children: [ + { + id: "double-nested-paragraph-1", + type: "paragraph", + content: "Double Nested Paragraph 1", + }, + ], + }, + ], + }, + { + id: "paragraph-2", + type: "paragraph", + content: "Paragraph 2", + }, + { + id: "paragraph-with-props", + type: "paragraph", + props: { + 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: "paragraph", + 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: "trailing-paragraph", + type: "paragraph", + }, +]; From 3fe50d55a8c489ebcf5e1fb41e57e005a3081888 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 9 Oct 2024 10:52:10 +0200 Subject: [PATCH 07/34] Updated snapshots --- .../__snapshots__/moveBlock.test.ts.snap | 40 +- .../__snapshots__/mergeBlocks.test.ts.snap | 901 +----------------- .../__snapshots__/splitBlocks.test.ts.snap | 467 ++------- .../commands/mergeBlocks.test.ts | 8 +- .../blockManipulation/commands/mergeBlocks.ts | 2 +- .../blockManipulation/commands/splitBlock.ts | 2 +- .../commands/splitBlocks.test.ts | 16 +- .../src/api/blockManipulation/testDocument.ts | 3 +- 8 files changed, 165 insertions(+), 1274 deletions(-) diff --git a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap b/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap index f82f813a7..be3660399 100644 --- a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap @@ -118,7 +118,7 @@ exports[`Test moveBlockDown > Basic 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -200,10 +200,11 @@ exports[`Test moveBlockDown > Basic 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -492,7 +493,7 @@ exports[`Test moveBlockDown > Into children 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -574,10 +575,11 @@ exports[`Test moveBlockDown > Into children 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -866,7 +868,7 @@ exports[`Test moveBlockDown > Last block 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -948,10 +950,11 @@ exports[`Test moveBlockDown > Last block 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -1239,7 +1242,7 @@ exports[`Test moveBlockDown > Out of children 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -1321,10 +1324,11 @@ exports[`Test moveBlockDown > Out of children 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -1613,7 +1617,7 @@ exports[`Test moveBlockUp > Basic 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -1695,10 +1699,11 @@ exports[`Test moveBlockUp > Basic 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -1987,7 +1992,7 @@ exports[`Test moveBlockUp > First block 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -2069,10 +2074,11 @@ exports[`Test moveBlockUp > First block 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -2361,7 +2367,7 @@ exports[`Test moveBlockUp > Into children 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -2443,10 +2449,11 @@ exports[`Test moveBlockUp > Into children 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -2734,7 +2741,7 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -2816,10 +2823,11 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], diff --git a/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap index a52c7320d..e8e9e1d12 100644 --- a/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap @@ -7,7 +7,7 @@ exports[`Test mergeBlocks > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph 0Paragraph 1", "type": "text", }, ], @@ -19,23 +19,6 @@ exports[`Test mergeBlocks > Basic 1`] = ` }, "type": "paragraph", }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 1", - "type": "text", - }, - ], - "id": "paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, { "children": [ { @@ -118,7 +101,7 @@ exports[`Test mergeBlocks > Basic 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -200,10 +183,11 @@ exports[`Test mergeBlocks > Basic 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -492,7 +476,7 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -567,34 +551,18 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` "content": [ { "styles": {}, - "text": "Heading 1", + "text": "Heading 1Paragraph 5", "type": "text", }, ], "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "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", + "type": "heading", }, { "children": [], @@ -793,7 +761,7 @@ exports[`Test mergeBlocks > First block has children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 1Paragraph 2", "type": "text", }, ], @@ -837,23 +805,6 @@ exports[`Test mergeBlocks > First block has children 1`] = ` }, "type": "paragraph", }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 2", - "type": "text", - }, - ], - "id": "paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, { "children": [], "content": [ @@ -866,7 +817,7 @@ exports[`Test mergeBlocks > First block has children 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -948,10 +899,11 @@ exports[`Test mergeBlocks > First block has children 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -1240,7 +1192,7 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -1322,10 +1274,11 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -1344,21 +1297,6 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` }, "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": [ @@ -1496,7 +1434,7 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` ] `; -exports[`Test mergeBlocks > Inline content & table content 1`] = ` +exports[`Test mergeBlocks > No content & inline content 1`] = ` [ { "children": [], @@ -1614,7 +1552,7 @@ exports[`Test mergeBlocks > Inline content & table content 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -1696,10 +1634,11 @@ exports[`Test mergeBlocks > Inline content & table content 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -1718,21 +1657,6 @@ exports[`Test mergeBlocks > Inline content & table content 1`] = ` }, "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": [ @@ -1742,7 +1666,7 @@ exports[`Test mergeBlocks > Inline content & table content 1`] = ` "type": "text", }, ], - "id": "paragraph-6", + "id": "image-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1870,7 +1794,7 @@ exports[`Test mergeBlocks > Inline content & table content 1`] = ` ] `; -exports[`Test mergeBlocks > No content & inline content 1`] = ` +exports[`Test mergeBlocks > Second block has children 1`] = ` [ { "children": [], @@ -1894,7 +1818,7 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 1Paragraph with children", "type": "text", }, ], @@ -1909,33 +1833,15 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` { "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", - }, - ], + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 1", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-1", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1947,11 +1853,11 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "paragraph-with-children", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1988,7 +1894,7 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -2070,758 +1976,11 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "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": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Test mergeBlocks > Second 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": [ - { - "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": "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": "left", - "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", - "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": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Test mergeBlocks > Table content & 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 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": "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": "left", - "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", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", + "type": "heading", }, { "children": [], diff --git a/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap index 0ca517c5f..b8b9d174f 100644 --- a/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap @@ -7,7 +7,7 @@ exports[`Test splitBlocks > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Para", "type": "text", }, ], @@ -19,6 +19,23 @@ exports[`Test splitBlocks > Basic 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "graph 0", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -118,7 +135,7 @@ exports[`Test splitBlocks > Basic 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -200,10 +217,11 @@ exports[`Test splitBlocks > Basic 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -410,6 +428,23 @@ exports[`Test splitBlocks > Block has children 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Para", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [ { @@ -451,11 +486,11 @@ exports[`Test splitBlocks > Block has children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "graph with children", "type": "text", }, ], - "id": "paragraph-with-children", + "id": "0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -492,7 +527,7 @@ exports[`Test splitBlocks > Block has children 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -574,10 +609,11 @@ exports[`Test splitBlocks > Block has children 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -859,14 +895,14 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with props", + "text": "Para", "type": "text", }, ], "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -876,264 +912,11 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` "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", - "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": "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", + "text": "graph with props", "type": "text", }, ], - "id": "paragraph-0", + "id": "0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1141,110 +924,6 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` }, "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 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": "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": "left", - "textColor": "red", - }, - "type": "paragraph", - }, { "children": [], "content": [ @@ -1322,10 +1001,11 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", }, { "children": [], @@ -1496,7 +1176,7 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` ] `; -exports[`Test splitBlocks > Keep props 1`] = ` +exports[`Test splitBlocks > Don't keep type 1`] = ` [ { "children": [], @@ -1614,7 +1294,7 @@ exports[`Test splitBlocks > Keep props 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -1689,11 +1369,29 @@ exports[`Test splitBlocks > Keep props 1`] = ` "content": [ { "styles": {}, - "text": "Heading 1", + "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", @@ -1988,7 +1686,7 @@ exports[`Test splitBlocks > Keep type 1`] = ` "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", + "textAlignment": "center", "textColor": "red", }, "type": "paragraph", @@ -2063,17 +1761,36 @@ exports[`Test splitBlocks > Keep type 1`] = ` "content": [ { "styles": {}, - "text": "Heading 1", + "text": "Head", "type": "text", }, ], "id": "heading-0", "props": { "backgroundColor": "default", + "level": 1, "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "ing 1", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", }, { "children": [], diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts index 4bd764bee..9c1e0f421 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts @@ -3,6 +3,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; import { testDocument } from "../testDocument.js"; +import { mergeBlocksCommand } from "./mergeBlocks.js"; let editor: BlockNoteEditor; const div = document.createElement("div"); @@ -22,8 +23,9 @@ beforeEach(() => { editor.replaceBlocks(editor.document, testDocument); }); -function mergeBlocks(_posBetweenBlocks: number) { +function mergeBlocks(posBetweenBlocks: number) { // TODO: Replace with imported function after converting from TipTap command + editor._tiptapEditor.commands.command(mergeBlocksCommand(posBetweenBlocks)); } function getPosAfterSelectedBlock() { @@ -75,7 +77,7 @@ describe("Test mergeBlocks", () => { expect(editor.document).toMatchSnapshot(); }); - it("Inline content & table content", () => { + it.skip("Inline content & table content", () => { editor.setTextCursorPosition("paragraph-6"); mergeBlocks(getPosAfterSelectedBlock()); @@ -91,7 +93,7 @@ describe("Test mergeBlocks", () => { expect(editor.document).toMatchSnapshot(); }); - it("Table content & inline content", () => { + it.skip("Table content & inline content", () => { editor.setTextCursorPosition("table-0"); mergeBlocks(getPosAfterSelectedBlock()); diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks.ts index 438e89aa9..6671546b4 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks.ts @@ -65,7 +65,7 @@ export const mergeBlocksCommand = startPos, new Slice(contentNode.content, 0, 0) ) - .scrollIntoView() + // .scrollIntoView() ); state.tr.setSelection( diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock.ts index 9d9fe02ea..43f4cdee4 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock.ts @@ -68,7 +68,7 @@ export const splitBlockCommand = : undefined ); - state.tr.scrollIntoView(); + // state.tr.scrollIntoView(); } return true; diff --git a/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts index f8597b3cb..31d6fe616 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts @@ -2,6 +2,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { testDocument } from "../testDocument.js"; +import { splitBlockCommand } from "./splitBlock.js"; let editor: BlockNoteEditor; const div = document.createElement("div"); @@ -22,11 +23,14 @@ beforeEach(() => { }); function splitBlocks( - _posInBlock: number, - _keepType?: boolean, - _keepProps?: boolean + posInBlock: number, + keepType?: boolean, + keepProps?: boolean ) { // TODO: Replace with imported function after converting from TipTap command + editor._tiptapEditor.commands.command( + splitBlockCommand(posInBlock, keepType, keepProps) + ); } function getSelectionAnchorPosWithOffset(offset: number) { @@ -43,7 +47,7 @@ describe("Test splitBlocks", () => { }); it("Block has children", () => { - editor.setTextCursorPosition("paragraph-1"); + editor.setTextCursorPosition("paragraph-with-children"); splitBlocks(getSelectionAnchorPosWithOffset(4)); @@ -66,10 +70,10 @@ describe("Test splitBlocks", () => { expect(editor.document).toMatchSnapshot(); }); - it("Keep props", () => { + it.skip("Keep props", () => { editor.setTextCursorPosition("paragraph-with-props"); - splitBlocks(getSelectionAnchorPosWithOffset(4), false, true); + splitBlocks(getSelectionAnchorPosWithOffset(4), true, true); expect(editor.document).toMatchSnapshot(); }); diff --git a/packages/core/src/api/blockManipulation/testDocument.ts b/packages/core/src/api/blockManipulation/testDocument.ts index e19f7ac89..aa66ed6e0 100644 --- a/packages/core/src/api/blockManipulation/testDocument.ts +++ b/packages/core/src/api/blockManipulation/testDocument.ts @@ -39,6 +39,7 @@ export const testDocument: PartialBlock[] = [ id: "paragraph-with-props", type: "paragraph", props: { + textAlignment: "center", textColor: "red", }, content: "Paragraph with props", @@ -64,7 +65,7 @@ export const testDocument: PartialBlock[] = [ }, { id: "heading-0", - type: "paragraph", + type: "heading", content: "Heading 1", }, { From 6a0fda35e231f7687a8473a5ad93155da4d512bd Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 9 Oct 2024 14:47:40 +0200 Subject: [PATCH 08/34] Added update block tests and unified test setup --- .../__snapshots__/moveBlock.test.ts.snap | 544 ++ .../blockManipulation.test.ts | 79 +- .../blockManipulation/blockManipulation.ts | 34 - .../__snapshots__/mergeBlocks.test.ts.snap | 408 ++ .../__snapshots__/splitBlocks.test.ts.snap | 340 ++ .../__snapshots__/updateBlock.test.ts.snap | 5138 +++++++++++++++++ .../commands/mergeBlocks.test.ts | 65 +- .../commands/splitBlocks.test.ts | 53 +- .../commands/updateBlock.test.ts | 169 + .../blockManipulation/commands/updateBlock.ts | 39 +- .../api/blockManipulation/moveBlock.test.ts | 158 +- .../{testDocument.ts => setupTestEnv.ts} | 56 +- packages/core/src/editor/BlockNoteEditor.ts | 4 +- 13 files changed, 6813 insertions(+), 274 deletions(-) create mode 100644 packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap create mode 100644 packages/core/src/api/blockManipulation/commands/updateBlock.test.ts rename packages/core/src/api/blockManipulation/{testDocument.ts => setupTestEnv.ts} (62%) diff --git a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap b/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap index be3660399..81b090c0e 100644 --- a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap @@ -361,6 +361,74 @@ exports[`Test moveBlockDown > Basic 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -736,6 +804,74 @@ exports[`Test moveBlockDown > Into children 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1111,6 +1247,74 @@ exports[`Test moveBlockDown > Last block 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1485,6 +1689,74 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1860,6 +2132,74 @@ exports[`Test moveBlockUp > Basic 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -2235,6 +2575,74 @@ exports[`Test moveBlockUp > First block 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -2610,6 +3018,74 @@ exports[`Test moveBlockUp > Into children 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -2984,6 +3460,74 @@ exports[`Test moveBlockUp > Out of children 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], diff --git a/packages/core/src/api/blockManipulation/blockManipulation.test.ts b/packages/core/src/api/blockManipulation/blockManipulation.test.ts index 1a3edcdad..cee2bcc1a 100644 --- a/packages/core/src/api/blockManipulation/blockManipulation.test.ts +++ b/packages/core/src/api/blockManipulation/blockManipulation.test.ts @@ -45,12 +45,6 @@ let singleBlock: PartialBlock< DefaultStyleSchema >; -let singleBlockWithChildren: PartialBlock< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->; - let multipleBlocks: PartialBlock< typeof schema.blockSchema, DefaultInlineContentSchema, @@ -83,17 +77,6 @@ beforeEach(() => { content: "Paragraph", }; - singleBlockWithChildren = { - type: "paragraph", - content: "Paragraph", - children: [ - { - type: "paragraph", - content: "Nested Paragraph", - }, - ], - }; - multipleBlocks = [ { type: "heading", @@ -281,67 +264,7 @@ describe("Insert, Update, & Delete Blocks", () => { }); }); -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(); - }); -}); - +// TODO: This seems like it really tests converting strings to inline content? describe("Update Line Breaks", () => { it("Update paragraph with line break", () => { const existingBlock = editor.document[0]; diff --git a/packages/core/src/api/blockManipulation/blockManipulation.ts b/packages/core/src/api/blockManipulation/blockManipulation.ts index b2b1b9e3f..d0ebdd296 100644 --- a/packages/core/src/api/blockManipulation/blockManipulation.ts +++ b/packages/core/src/api/blockManipulation/blockManipulation.ts @@ -15,7 +15,6 @@ import { nodeToBlock, } from "../nodeConversions/nodeConversions.js"; import { getNodeById } from "../nodeUtil.js"; -import { updateBlockCommand } from "./commands/updateBlock.js"; export function insertBlocks< BSchema extends BlockSchema, @@ -91,39 +90,6 @@ export function insertBlocks< 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.command(({ state, dispatch }) => { - updateBlockCommand(editor, posBeforeNode + 1, update)({ state, dispatch }); - return true; - }); - - 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, diff --git a/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap index e8e9e1d12..4d21e8598 100644 --- a/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap @@ -344,6 +344,74 @@ exports[`Test mergeBlocks > Basic 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -702,6 +770,74 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1060,6 +1196,74 @@ exports[`Test mergeBlocks > First block has children 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1420,6 +1624,74 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1780,6 +2052,74 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -2137,6 +2477,74 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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/__snapshots__/splitBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap index b8b9d174f..40b0062be 100644 --- a/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap @@ -378,6 +378,74 @@ exports[`Test splitBlocks > Basic 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -770,6 +838,74 @@ exports[`Test splitBlocks > Block has children 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1162,6 +1298,74 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1554,6 +1758,74 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "heading-with-everything", + "props": { + "backgroundColor": "red", + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, { "children": [], "content": [], @@ -1947,6 +2219,74 @@ exports[`Test splitBlocks > Keep type 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap new file mode 100644 index 000000000..86a2aee72 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap @@ -0,0 +1,5138 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "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 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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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": [], + "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "nested-paragraph-2", + "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "nested-paragraph-2", + "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/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts index 9c1e0f421..07fc30886 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts @@ -1,103 +1,88 @@ -import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; -import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; -import { testDocument } from "../testDocument.js"; +import { setupTestEnv } from "../setupTestEnv.js"; import { mergeBlocksCommand } from "./mergeBlocks.js"; -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); -}); +const getEditor = setupTestEnv(); function mergeBlocks(posBetweenBlocks: number) { // TODO: Replace with imported function after converting from TipTap command - editor._tiptapEditor.commands.command(mergeBlocksCommand(posBetweenBlocks)); + getEditor()._tiptapEditor.commands.command( + mergeBlocksCommand(posBetweenBlocks) + ); } function getPosAfterSelectedBlock() { const { startPos } = getBlockInfoFromPos( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.selection.from + getEditor()._tiptapEditor.state.doc, + getEditor()._tiptapEditor.state.selection.from ); - return editor._tiptapEditor.state.doc.resolve(startPos).after(); + return getEditor()._tiptapEditor.state.doc.resolve(startPos).after(); } describe("Test mergeBlocks", () => { it("Basic", () => { - editor.setTextCursorPosition("paragraph-0"); + getEditor().setTextCursorPosition("paragraph-0"); mergeBlocks(getPosAfterSelectedBlock()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("First block has children", () => { - editor.setTextCursorPosition("paragraph-with-children"); + getEditor().setTextCursorPosition("paragraph-with-children"); mergeBlocks(getPosAfterSelectedBlock()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Second block has children", () => { - editor.setTextCursorPosition("paragraph-1"); + getEditor().setTextCursorPosition("paragraph-1"); mergeBlocks(getPosAfterSelectedBlock()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Blocks have different types", () => { - editor.setTextCursorPosition("heading-0"); + getEditor().setTextCursorPosition("heading-0"); mergeBlocks(getPosAfterSelectedBlock()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Inline content & no content", () => { - editor.setTextCursorPosition("paragraph-5"); + getEditor().setTextCursorPosition("paragraph-5"); mergeBlocks(getPosAfterSelectedBlock()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it.skip("Inline content & table content", () => { - editor.setTextCursorPosition("paragraph-6"); + getEditor().setTextCursorPosition("paragraph-6"); mergeBlocks(getPosAfterSelectedBlock()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("No content & inline content", () => { - editor.setTextCursorPosition("image-0"); + getEditor().setTextCursorPosition("image-0"); mergeBlocks(getPosAfterSelectedBlock()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it.skip("Table content & inline content", () => { - editor.setTextCursorPosition("table-0"); + getEditor().setTextCursorPosition("table-0"); mergeBlocks(getPosAfterSelectedBlock()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); }); diff --git a/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts index 31d6fe616..84dac4d60 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts @@ -1,26 +1,9 @@ -import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; -import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; -import { testDocument } from "../testDocument.js"; +import { setupTestEnv } from "../setupTestEnv.js"; import { splitBlockCommand } from "./splitBlock.js"; -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); -}); +const getEditor = setupTestEnv(); function splitBlocks( posInBlock: number, @@ -28,61 +11,61 @@ function splitBlocks( keepProps?: boolean ) { // TODO: Replace with imported function after converting from TipTap command - editor._tiptapEditor.commands.command( + getEditor()._tiptapEditor.commands.command( splitBlockCommand(posInBlock, keepType, keepProps) ); } function getSelectionAnchorPosWithOffset(offset: number) { - return editor._tiptapEditor.state.selection.anchor + offset; + return getEditor()._tiptapEditor.state.selection.anchor + offset; } describe("Test splitBlocks", () => { it("Basic", () => { - editor.setTextCursorPosition("paragraph-0"); + getEditor().setTextCursorPosition("paragraph-0"); splitBlocks(getSelectionAnchorPosWithOffset(4)); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Block has children", () => { - editor.setTextCursorPosition("paragraph-with-children"); + getEditor().setTextCursorPosition("paragraph-with-children"); splitBlocks(getSelectionAnchorPosWithOffset(4)); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Keep type", () => { - editor.setTextCursorPosition("heading-0"); + getEditor().setTextCursorPosition("heading-0"); splitBlocks(getSelectionAnchorPosWithOffset(4), true); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Don't keep type", () => { - editor.setTextCursorPosition("heading-0"); + getEditor().setTextCursorPosition("heading-0"); splitBlocks(getSelectionAnchorPosWithOffset(4)); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it.skip("Keep props", () => { - editor.setTextCursorPosition("paragraph-with-props"); + getEditor().setTextCursorPosition("paragraph-with-props"); - splitBlocks(getSelectionAnchorPosWithOffset(4), true, true); + splitBlocks(getSelectionAnchorPosWithOffset(4), false, true); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Don't keep props", () => { - editor.setTextCursorPosition("paragraph-with-props"); + getEditor().setTextCursorPosition("paragraph-with-props"); splitBlocks(getSelectionAnchorPosWithOffset(4)); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); }); diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts new file mode 100644 index 000000000..3fd0044c2 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts @@ -0,0 +1,169 @@ +import { describe, expect, it } from "vitest"; + +import { setupTestEnv } from "../setupTestEnv.js"; +import { updateBlock } from "./updateBlock.js"; + +const getEditor = setupTestEnv(); + +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("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 table content", () => { + updateBlock(getEditor(), "paragraph-0", { + type: "table", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update table content to inline content", () => { + updateBlock(getEditor(), "table-0", { + type: "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 inline content", () => { + updateBlock(getEditor(), "image-0", { + type: "paragraph", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update no content to table content", () => { + updateBlock(getEditor(), "image-0", { + type: "table", + }); + + 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.ts b/packages/core/src/api/blockManipulation/commands/updateBlock.ts index a02348a7c..84aa5f58e 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock.ts @@ -1,9 +1,9 @@ import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; -import { PartialBlock } from "../../../blocks/defaultBlocks.js"; +import { Block, PartialBlock } from "../../../blocks/defaultBlocks.js"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; -import { BlockSchema } from "../../../schema/blocks/types.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"; @@ -11,8 +11,10 @@ import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; import { blockToNode, inlineContentToNodes, + nodeToBlock, tableContentToNodes, } from "../../nodeConversions/nodeConversions.js"; +import { getNodeById } from "../../nodeUtil.js"; export const updateBlockCommand = < @@ -177,3 +179,36 @@ export const updateBlockCommand = 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 + 1, update)({ state, dispatch }); + return true; + }); + + const blockContainerNode = ttEditor.state.doc + .resolve(posBeforeNode + 1) + .node(); + + return nodeToBlock( + blockContainerNode, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ); +} diff --git a/packages/core/src/api/blockManipulation/moveBlock.test.ts b/packages/core/src/api/blockManipulation/moveBlock.test.ts index 8d6bf938a..639aafbe0 100644 --- a/packages/core/src/api/blockManipulation/moveBlock.test.ts +++ b/packages/core/src/api/blockManipulation/moveBlock.test.ts @@ -1,46 +1,49 @@ import { NodeSelection, TextSelection } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; -import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import { describe, expect, it } from "vitest"; + import { getBlockInfoFromPos } from "../getBlockInfoFromPos.js"; import { moveBlockDown, moveBlockUp, moveSelectedBlockAndSelection, } from "./moveBlock.js"; -import { testDocument } from "./testDocument.js"; +import { setupTestEnv } from "./setupTestEnv.js"; -let editor: BlockNoteEditor; -const div = document.createElement("div"); +const getEditor = setupTestEnv(); function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { const { startPos, contentNode } = getBlockInfoFromPos( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.selection.from + getEditor()._tiptapEditor.state.doc, + getEditor()._tiptapEditor.state.selection.from ); if (selectionType === "cell") { - editor._tiptapEditor.view.dispatch( - editor._tiptapEditor.state.tr.setSelection( + getEditor()._tiptapEditor.view.dispatch( + getEditor()._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) + getEditor()._tiptapEditor.state.doc, + getEditor() + ._tiptapEditor.state.doc.resolve(startPos + 3) + .before(), + getEditor() + ._tiptapEditor.state.doc.resolve( + startPos + contentNode.nodeSize - 3 + ) .before() ) ) ); } else if (selectionType === "node") { const resolvedContentStartPos = - editor._tiptapEditor.state.doc.resolve(startPos); + getEditor()._tiptapEditor.state.doc.resolve(startPos); - editor._tiptapEditor.view.dispatch( - editor._tiptapEditor.state.tr.setSelection( + getEditor()._tiptapEditor.view.dispatch( + getEditor()._tiptapEditor.state.tr.setSelection( NodeSelection.create( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.doc - .resolve( + getEditor()._tiptapEditor.state.doc, + getEditor() + ._tiptapEditor.state.doc.resolve( resolvedContentStartPos.after(resolvedContentStartPos.depth + 1) ) .start() @@ -49,22 +52,22 @@ function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { ); } else { const resolvedContentStartPos = - editor._tiptapEditor.state.doc.resolve(startPos); - const resolvedContentEndPos = editor._tiptapEditor.state.doc.resolve( + getEditor()._tiptapEditor.state.doc.resolve(startPos); + const resolvedContentEndPos = getEditor()._tiptapEditor.state.doc.resolve( startPos + contentNode.nodeSize ); - editor._tiptapEditor.view.dispatch( - editor._tiptapEditor.state.tr.setSelection( + getEditor()._tiptapEditor.view.dispatch( + getEditor()._tiptapEditor.state.tr.setSelection( TextSelection.create( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.doc - .resolve( + getEditor()._tiptapEditor.state.doc, + getEditor() + ._tiptapEditor.state.doc.resolve( resolvedContentStartPos.after(resolvedContentStartPos.depth + 1) ) .start(), - editor._tiptapEditor.state.doc - .resolve( + getEditor() + ._tiptapEditor.state.doc.resolve( resolvedContentEndPos.before(resolvedContentEndPos.depth + 1) ) .end() @@ -74,126 +77,117 @@ function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { } } -beforeAll(() => { - editor = BlockNoteEditor.create(); - editor.mount(div); -}); - -afterAll(() => { - editor.mount(undefined); - editor._tiptapEditor.destroy(); - editor = undefined as any; -}); - -beforeEach(() => { - editor.replaceBlocks(editor.document, testDocument); -}); - describe("Test moveSelectedBlockAndSelection", () => { it("Text selection", () => { - editor.setTextCursorPosition("paragraph-1"); + getEditor().setTextCursorPosition("paragraph-1"); makeSelectionSpanContent("text"); - moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); + moveSelectedBlockAndSelection(getEditor(), "paragraph-0", "before"); - const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("paragraph-1"); + const selection = getEditor()._tiptapEditor.state.selection; + getEditor().setTextCursorPosition("paragraph-1"); makeSelectionSpanContent("text"); - expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); + expect( + selection.eq(getEditor()._tiptapEditor.state.selection) + ).toBeTruthy(); }); it("Node selection", () => { - editor.setTextCursorPosition("image-0"); + getEditor().setTextCursorPosition("image-0"); makeSelectionSpanContent("node"); - moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); + moveSelectedBlockAndSelection(getEditor(), "paragraph-0", "before"); - const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("image-0"); + const selection = getEditor()._tiptapEditor.state.selection; + getEditor().setTextCursorPosition("image-0"); makeSelectionSpanContent("node"); - expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); + expect( + selection.eq(getEditor()._tiptapEditor.state.selection) + ).toBeTruthy(); }); it("Cell selection", () => { - editor.setTextCursorPosition("table-0"); + getEditor().setTextCursorPosition("table-0"); makeSelectionSpanContent("cell"); - moveSelectedBlockAndSelection(editor, "paragraph-0", "before"); + moveSelectedBlockAndSelection(getEditor(), "paragraph-0", "before"); - const selection = editor._tiptapEditor.state.selection; - editor.setTextCursorPosition("table-0"); + const selection = getEditor()._tiptapEditor.state.selection; + getEditor().setTextCursorPosition("table-0"); makeSelectionSpanContent("cell"); - expect(selection.eq(editor._tiptapEditor.state.selection)).toBeTruthy(); + expect( + selection.eq(getEditor()._tiptapEditor.state.selection) + ).toBeTruthy(); }); }); describe("Test moveBlockUp", () => { it("Basic", () => { - editor.setTextCursorPosition("paragraph-1"); + getEditor().setTextCursorPosition("paragraph-1"); - moveBlockUp(editor); + moveBlockUp(getEditor()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Into children", () => { - editor.setTextCursorPosition("paragraph-2"); + getEditor().setTextCursorPosition("paragraph-2"); - moveBlockUp(editor); + moveBlockUp(getEditor()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Out of children", () => { - editor.setTextCursorPosition("nested-paragraph-1"); + getEditor().setTextCursorPosition("nested-paragraph-1"); - moveBlockUp(editor); + moveBlockUp(getEditor()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("First block", () => { - editor.setTextCursorPosition("paragraph-0"); + getEditor().setTextCursorPosition("paragraph-0"); - moveBlockUp(editor); + moveBlockUp(getEditor()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); }); describe("Test moveBlockDown", () => { it("Basic", () => { - editor.setTextCursorPosition("paragraph-0"); + getEditor().setTextCursorPosition("paragraph-0"); - moveBlockDown(editor); + moveBlockDown(getEditor()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Into children", () => { - editor.setTextCursorPosition("paragraph-1"); + getEditor().setTextCursorPosition("paragraph-1"); - moveBlockDown(editor); + moveBlockDown(getEditor()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Out of children", () => { - editor.setTextCursorPosition("nested-paragraph-1"); + getEditor().setTextCursorPosition("nested-paragraph-1"); - moveBlockDown(editor); + moveBlockDown(getEditor()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); it("Last block", () => { - editor.setTextCursorPosition("trailing-paragraph"); + getEditor().setTextCursorPosition("trailing-paragraph"); - moveBlockDown(editor); + moveBlockDown(getEditor()); - expect(editor.document).toMatchSnapshot(); + expect(getEditor().document).toMatchSnapshot(); }); }); diff --git a/packages/core/src/api/blockManipulation/testDocument.ts b/packages/core/src/api/blockManipulation/setupTestEnv.ts similarity index 62% rename from packages/core/src/api/blockManipulation/testDocument.ts rename to packages/core/src/api/blockManipulation/setupTestEnv.ts index aa66ed6e0..5b95d5e2c 100644 --- a/packages/core/src/api/blockManipulation/testDocument.ts +++ b/packages/core/src/api/blockManipulation/setupTestEnv.ts @@ -1,6 +1,31 @@ +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; + }); -export const testDocument: PartialBlock[] = [ + beforeEach(() => { + editor.replaceBlocks(editor.document, testDocument); + }); + + return () => editor; +} + +const testDocument: PartialBlock[] = [ { id: "paragraph-0", type: "paragraph", @@ -108,6 +133,35 @@ export const testDocument: PartialBlock[] = [ type: "paragraph", content: "Paragraph 7", }, + { + 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-2", + type: "paragraph", + content: "Nested Paragraph 2", + children: [ + { + id: "double-nested-paragraph-2", + type: "paragraph", + content: "Double Nested Paragraph 2", + }, + ], + }, + ], + }, { id: "trailing-paragraph", type: "paragraph", diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index e50a033f6..4808cc84d 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -7,12 +7,12 @@ import { insertContentAt, removeBlocks, replaceBlocks, - updateBlock, } from "../api/blockManipulation/blockManipulation.js"; import { moveBlockDown, moveBlockUp, } from "../api/blockManipulation/moveBlock.js"; +import { updateBlock } from "../api/blockManipulation/commands/updateBlock.js"; import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; @@ -905,7 +905,7 @@ export class BlockNoteEditor< blockToUpdate: BlockIdentifier, update: PartialBlock ) { - return updateBlock(blockToUpdate, update, this); + return updateBlock(this, blockToUpdate, update); } /** From df6dccc0e232e1ba0116ee6171baf7b7a069236b Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 9 Oct 2024 15:11:29 +0200 Subject: [PATCH 09/34] Added test cases for reverting props --- .../blockManipulation.test.ts.snap | 207 ---- .../__snapshots__/updateBlock.test.ts.snap | 886 ++++++++++++++++++ .../commands/updateBlock.test.ts | 23 + 3 files changed, 909 insertions(+), 207 deletions(-) diff --git a/packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap b/packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap index ecc3057ed..9a49952d7 100644 --- a/packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +++ b/packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap @@ -712,210 +712,3 @@ Line2", }, ] `; - -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/commands/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap index 86a2aee72..025fd96ec 100644 --- a/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap @@ -1,5 +1,891 @@ // 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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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`] = ` [ { diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts index 3fd0044c2..3f3630cb3 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts @@ -45,6 +45,29 @@ describe("Test updateBlock", () => { 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", From d4f206dec0061c9cb5f8d95a2c982aba310cab41 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 9 Oct 2024 15:18:40 +0200 Subject: [PATCH 10/34] Added additional test cases for changing content type --- .../__snapshots__/updateBlock.test.ts.snap | 1956 ++++++++++++++++- .../commands/updateBlock.test.ts | 62 + 2 files changed, 1964 insertions(+), 54 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap index 025fd96ec..6a4c8d02e 100644 --- a/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap @@ -1772,22 +1772,20 @@ exports[`Test updateBlock > Update children 1`] = ` ] `; -exports[`Test updateBlock > Update inline content to no content 1`] = ` +exports[`Test updateBlock > Update inline content to empty table content 1`] = ` [ { "children": [], - "content": undefined, + "content": { + "rows": [], + "type": "tableContent", + }, "id": "paragraph-0", "props": { "backgroundColor": "default", - "caption": "", - "name": "", - "previewWidth": 512, - "showPreview": true, - "textAlignment": "left", - "url": "", + "textColor": "default", }, - "type": "image", + "type": "table", }, { "children": [], @@ -2213,20 +2211,22 @@ exports[`Test updateBlock > Update inline content to no content 1`] = ` ] `; -exports[`Test updateBlock > Update inline content to table content 1`] = ` +exports[`Test updateBlock > Update inline content to no content 1`] = ` [ { "children": [], - "content": { - "rows": [], - "type": "tableContent", - }, + "content": undefined, "id": "paragraph-0", "props": { "backgroundColor": "default", - "textColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, + "textAlignment": "left", + "url": "", }, - "type": "table", + "type": "image", }, { "children": [], @@ -2652,24 +2652,96 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` ] `; -exports[`Test updateBlock > Update no content to inline content 1`] = ` +exports[`Test updateBlock > Update inline content to table content 1`] = ` [ { "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 0", - "type": "text", - }, - ], + "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", - "textAlignment": "left", "textColor": "default", }, - "type": "paragraph", + "type": "table", }, { "children": [], @@ -2877,14 +2949,18 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` }, { "children": [], - "content": [], + "content": undefined, "id": "image-0", "props": { "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": 512, + "showPreview": true, "textAlignment": "left", - "textColor": "default", + "url": "https://via.placeholder.com/150", }, - "type": "paragraph", + "type": "image", }, { "children": [], @@ -3091,7 +3167,7 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` ] `; -exports[`Test updateBlock > Update no content to table content 1`] = ` +exports[`Test updateBlock > Update no content to empty inline content 1`] = ` [ { "children": [], @@ -3316,16 +3392,14 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` }, { "children": [], - "content": { - "rows": [], - "type": "tableContent", - }, + "content": [], "id": "image-0", "props": { "backgroundColor": "default", + "textAlignment": "left", "textColor": "default", }, - "type": "table", + "type": "paragraph", }, { "children": [], @@ -3532,7 +3606,7 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` ] `; -exports[`Test updateBlock > Update single prop 1`] = ` +exports[`Test updateBlock > Update no content to empty table content 1`] = ` [ { "children": [], @@ -3757,18 +3831,16 @@ exports[`Test updateBlock > Update single prop 1`] = ` }, { "children": [], - "content": undefined, + "content": { + "rows": [], + "type": "tableContent", + }, "id": "image-0", "props": { "backgroundColor": "default", - "caption": "", - "name": "", - "previewWidth": 512, - "showPreview": true, - "textAlignment": "left", - "url": "https://via.placeholder.com/150", + "textColor": "default", }, - "type": "image", + "type": "table", }, { "children": [], @@ -3955,7 +4027,7 @@ exports[`Test updateBlock > Update single prop 1`] = ` "id": "heading-with-everything", "props": { "backgroundColor": "red", - "level": 3, + "level": 2, "textAlignment": "center", "textColor": "red", }, @@ -3975,7 +4047,7 @@ exports[`Test updateBlock > Update single prop 1`] = ` ] `; -exports[`Test updateBlock > Update table content to inline content 1`] = ` +exports[`Test updateBlock > Update no content to inline content 1`] = ` [ { "children": [], @@ -4200,18 +4272,20 @@ exports[`Test updateBlock > Update table content to inline content 1`] = ` }, { "children": [], - "content": undefined, + "content": [ + { + "styles": {}, + "text": "Paragraph", + "type": "text", + }, + ], "id": "image-0", "props": { "backgroundColor": "default", - "caption": "", - "name": "", - "previewWidth": 512, - "showPreview": true, "textAlignment": "left", - "url": "https://via.placeholder.com/150", + "textColor": "default", }, - "type": "image", + "type": "paragraph", }, { "children": [], @@ -4232,7 +4306,1781 @@ exports[`Test updateBlock > Update table content to inline content 1`] = ` }, { "children": [], - "content": [], + "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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", diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts index 3f3630cb3..7640c763a 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts @@ -142,9 +142,39 @@ describe("Test updateBlock", () => { 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(); @@ -153,6 +183,7 @@ describe("Test updateBlock", () => { it("Update table content to inline content", () => { updateBlock(getEditor(), "table-0", { type: "paragraph", + content: "Paragraph", }); expect(getEditor().document).toMatchSnapshot(); @@ -166,9 +197,26 @@ describe("Test updateBlock", () => { 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(); @@ -177,6 +225,20 @@ describe("Test updateBlock", () => { 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(); From 5ac23d9d6943e7744cf29ace7d935d8aadb84a4c Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 9 Oct 2024 15:23:33 +0200 Subject: [PATCH 11/34] remove "nested" insert option --- .../docs/editor-api/manipulating-blocks.mdx | 4 ++-- .../blockManipulation.test.ts | 8 +------- .../api/blockManipulation/blockManipulation.ts | 18 +----------------- packages/core/src/editor/BlockNoteEditor.ts | 6 +++--- 4 files changed, 7 insertions(+), 29 deletions(-) diff --git a/docs/pages/docs/editor-api/manipulating-blocks.mdx b/docs/pages/docs/editor-api/manipulating-blocks.mdx index 3e90d41a5..377403bd2 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/blockManipulation.test.ts b/packages/core/src/api/blockManipulation/blockManipulation.test.ts index cee2bcc1a..eb5c951ce 100644 --- a/packages/core/src/api/blockManipulation/blockManipulation.test.ts +++ b/packages/core/src/api/blockManipulation/blockManipulation.test.ts @@ -58,7 +58,7 @@ let blocksWithLineBreaks: PartialBlock< >[]; let insert: ( - placement: "before" | "nested" | "after" + placement: "before" | "after" ) => Block< typeof schema.blockSchema, DefaultInlineContentSchema, @@ -190,12 +190,6 @@ describe("Inserting Blocks with Different Placements", () => { 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"); diff --git a/packages/core/src/api/blockManipulation/blockManipulation.ts b/packages/core/src/api/blockManipulation/blockManipulation.ts index d0ebdd296..cbb5afde8 100644 --- a/packages/core/src/api/blockManipulation/blockManipulation.ts +++ b/packages/core/src/api/blockManipulation/blockManipulation.ts @@ -23,7 +23,7 @@ export function insertBlocks< >( blocksToInsert: PartialBlock[], referenceBlock: BlockIdentifier, - placement: "before" | "after" | "nested" = "before", + placement: "before" | "after" = "before", editor: BlockNoteEditor ): Block[] { const id = @@ -56,22 +56,6 @@ export function insertBlocks< ); } - 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[] = []; diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 4808cc84d..f0f68962d 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -8,11 +8,11 @@ import { removeBlocks, replaceBlocks, } from "../api/blockManipulation/blockManipulation.js"; +import { updateBlock } from "../api/blockManipulation/commands/updateBlock.js"; import { moveBlockDown, moveBlockUp, } from "../api/blockManipulation/moveBlock.js"; -import { updateBlock } from "../api/blockManipulation/commands/updateBlock.js"; import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; @@ -884,12 +884,12 @@ 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); } From fc0010bdd0dc971e535d957bca663321344f753b Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 9 Oct 2024 19:33:41 +0200 Subject: [PATCH 12/34] Split remaining commands & cleaned up --- .../blockManipulation.test.ts.snap | 714 --- .../blockManipulation.test.ts | 287 - .../blockManipulation/blockManipulation.ts | 307 -- .../__snapshots__/insertBlocks.test.ts.snap | 2919 +++++++++++ .../insertBlocks/insertBlocks.test.ts | 132 + .../commands/insertBlocks/insertBlocks.ts | 73 + .../__snapshots__/mergeBlocks.test.ts.snap | 0 .../{ => mergeBlocks}/mergeBlocks.test.ts | 4 +- .../commands/{ => mergeBlocks}/mergeBlocks.ts | 2 +- .../__snapshots__/moveBlock.test.ts.snap | 0 .../moveBlock}/moveBlock.test.ts | 4 +- .../{ => commands/moveBlock}/moveBlock.ts | 8 +- .../__snapshots__/removeBlocks.test.ts.snap | 1052 ++++ .../removeBlocks/removeBlocks.test.ts | 34 + .../commands/removeBlocks/removeBlocks.ts | 100 + .../__snapshots__/replaceBlocks.test.ts.snap | 4595 +++++++++++++++++ .../replaceBlocks/replaceBlocks.test.ts | 222 + .../commands/replaceBlocks/replaceBlocks.ts | 71 + .../__snapshots__/splitBlock.test.ts.snap} | 0 .../splitBlock.test.ts} | 16 +- .../commands/{ => splitBlock}/splitBlock.ts | 2 +- .../__snapshots__/updateBlock.test.ts.snap | 0 .../{ => updateBlock}/updateBlock.test.ts | 76 +- .../commands/{ => updateBlock}/updateBlock.ts | 21 +- .../api/blockManipulation/insertContentAt.ts | 96 + .../HeadingBlockContent.ts | 2 +- .../BulletListItemBlockContent.ts | 2 +- .../CheckListItemBlockContent.ts | 2 +- .../ListItemKeyboardShortcuts.ts | 4 +- .../NumberedListItemBlockContent.ts | 2 +- .../ParagraphBlockContent.ts | 2 +- packages/core/src/editor/BlockNoteEditor.ts | 20 +- .../KeyboardShortcutsExtension.ts | 6 +- 33 files changed, 9418 insertions(+), 1357 deletions(-) delete mode 100644 packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap delete mode 100644 packages/core/src/api/blockManipulation/blockManipulation.test.ts delete mode 100644 packages/core/src/api/blockManipulation/blockManipulation.ts create mode 100644 packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap create mode 100644 packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts create mode 100644 packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts rename packages/core/src/api/blockManipulation/commands/{ => mergeBlocks}/__snapshots__/mergeBlocks.test.ts.snap (100%) rename packages/core/src/api/blockManipulation/commands/{ => mergeBlocks}/mergeBlocks.test.ts (94%) rename packages/core/src/api/blockManipulation/commands/{ => mergeBlocks}/mergeBlocks.ts (97%) rename packages/core/src/api/blockManipulation/{ => commands/moveBlock}/__snapshots__/moveBlock.test.ts.snap (100%) rename packages/core/src/api/blockManipulation/{ => commands/moveBlock}/moveBlock.test.ts (97%) rename packages/core/src/api/blockManipulation/{ => commands/moveBlock}/moveBlock.ts (94%) create mode 100644 packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap create mode 100644 packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.ts create mode 100644 packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts create mode 100644 packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap create mode 100644 packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.test.ts create mode 100644 packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts rename packages/core/src/api/blockManipulation/commands/{__snapshots__/splitBlocks.test.ts.snap => splitBlock/__snapshots__/splitBlock.test.ts.snap} (100%) rename packages/core/src/api/blockManipulation/commands/{splitBlocks.test.ts => splitBlock/splitBlock.test.ts} (78%) rename packages/core/src/api/blockManipulation/commands/{ => splitBlock}/splitBlock.ts (97%) rename packages/core/src/api/blockManipulation/commands/{ => updateBlock}/__snapshots__/updateBlock.test.ts.snap (100%) rename packages/core/src/api/blockManipulation/commands/{ => updateBlock}/updateBlock.test.ts (75%) rename packages/core/src/api/blockManipulation/commands/{ => updateBlock}/updateBlock.ts (91%) create mode 100644 packages/core/src/api/blockManipulation/insertContentAt.ts 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 9a49952d7..000000000 --- a/packages/core/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +++ /dev/null @@ -1,714 +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", - }, -] -`; 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 eb5c951ce..000000000 --- a/packages/core/src/api/blockManipulation/blockManipulation.test.ts +++ /dev/null @@ -1,287 +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 multipleBlocks: PartialBlock< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->[]; - -let blocksWithLineBreaks: PartialBlock< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->[]; - -let insert: ( - placement: "before" | "after" -) => Block< - typeof schema.blockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema ->[]; - -beforeEach(() => { - editor = BlockNoteEditor.create({ - schema: schema, - }); - - editor.mount(div); - - singleBlock = { - type: "paragraph", - content: "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 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(); - }); -}); - -// TODO: This seems like it really tests converting strings to inline content? -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 cbb5afde8..000000000 --- a/packages/core/src/api/blockManipulation/blockManipulation.ts +++ /dev/null @@ -1,307 +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" = "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 - ) - ); - } - - // 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; -} - -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 000000000..39be75618 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap @@ -0,0 +1,2919 @@ +// 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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 000000000..c84a298db --- /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 000000000..b63a1439d --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -0,0 +1,73 @@ +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, + nodeToBlock, +} from "../../../nodeConversions/nodeConversions.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/commands/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap similarity index 100% rename from packages/core/src/api/blockManipulation/commands/__snapshots__/mergeBlocks.test.ts.snap rename to packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts similarity index 94% rename from packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts rename to packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 07fc30886..53f42a6f6 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; -import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; -import { setupTestEnv } from "../setupTestEnv.js"; +import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { setupTestEnv } from "../../setupTestEnv.js"; import { mergeBlocksCommand } from "./mergeBlocks.js"; const getEditor = setupTestEnv(); diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts similarity index 97% rename from packages/core/src/api/blockManipulation/commands/mergeBlocks.ts rename to packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index 6671546b4..dce074864 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -1,7 +1,7 @@ import { Slice } from "prosemirror-model"; import { EditorState, TextSelection } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; +import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; export const mergeBlocksCommand = (posBetweenBlocks: number) => diff --git a/packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap similarity index 100% rename from packages/core/src/api/blockManipulation/__snapshots__/moveBlock.test.ts.snap rename to packages/core/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap diff --git a/packages/core/src/api/blockManipulation/moveBlock.test.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts similarity index 97% rename from packages/core/src/api/blockManipulation/moveBlock.test.ts rename to packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts index 639aafbe0..fed2e9470 100644 --- a/packages/core/src/api/blockManipulation/moveBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts @@ -2,13 +2,13 @@ import { NodeSelection, TextSelection } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; import { describe, expect, it } from "vitest"; -import { getBlockInfoFromPos } from "../getBlockInfoFromPos.js"; +import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; import { moveBlockDown, moveBlockUp, moveSelectedBlockAndSelection, } from "./moveBlock.js"; -import { setupTestEnv } from "./setupTestEnv.js"; +import { setupTestEnv } from "../../setupTestEnv.js"; const getEditor = setupTestEnv(); diff --git a/packages/core/src/api/blockManipulation/moveBlock.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts similarity index 94% rename from packages/core/src/api/blockManipulation/moveBlock.ts rename to packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts index 598a6df8f..ace9541c6 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 { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { getNodeById } from "../../../nodeUtil.js"; type BlockSelectionData = ( | { 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 000000000..f2a83b655 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap @@ -0,0 +1,1052 @@ +// 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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 000000000..55625e11d --- /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 000000000..d38ea3622 --- /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 type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; +import { Block } from "../../../../blocks/defaultBlocks.js"; +import { + BlockIdentifier, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../../../schema/index.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeConversions.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 000000000..57d57c6ed --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap @@ -0,0 +1,4595 @@ +// 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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": "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 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": "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": "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 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": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 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": "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": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 2", + "type": "text", + }, + ], + "id": "double-nested-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 2", + "type": "text", + }, + ], + "id": "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": "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 000000000..09d4911a3 --- /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 000000000..bf81b6bce --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -0,0 +1,71 @@ +import { + BlockIdentifier, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../../../schema/index.js"; +import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; +import { Node } from "prosemirror-model"; +import { + blockToNode, + nodeToBlock, +} from "../../../nodeConversions/nodeConversions.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/__snapshots__/splitBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap similarity index 100% rename from packages/core/src/api/blockManipulation/commands/__snapshots__/splitBlocks.test.ts.snap rename to packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap diff --git a/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts similarity index 78% rename from packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts rename to packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts index 84dac4d60..659f7f6bf 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from "vitest"; -import { setupTestEnv } from "../setupTestEnv.js"; +import { setupTestEnv } from "../../setupTestEnv.js"; import { splitBlockCommand } from "./splitBlock.js"; const getEditor = setupTestEnv(); -function splitBlocks( +function splitBlock( posInBlock: number, keepType?: boolean, keepProps?: boolean @@ -24,7 +24,7 @@ describe("Test splitBlocks", () => { it("Basic", () => { getEditor().setTextCursorPosition("paragraph-0"); - splitBlocks(getSelectionAnchorPosWithOffset(4)); + splitBlock(getSelectionAnchorPosWithOffset(4)); expect(getEditor().document).toMatchSnapshot(); }); @@ -32,7 +32,7 @@ describe("Test splitBlocks", () => { it("Block has children", () => { getEditor().setTextCursorPosition("paragraph-with-children"); - splitBlocks(getSelectionAnchorPosWithOffset(4)); + splitBlock(getSelectionAnchorPosWithOffset(4)); expect(getEditor().document).toMatchSnapshot(); }); @@ -40,7 +40,7 @@ describe("Test splitBlocks", () => { it("Keep type", () => { getEditor().setTextCursorPosition("heading-0"); - splitBlocks(getSelectionAnchorPosWithOffset(4), true); + splitBlock(getSelectionAnchorPosWithOffset(4), true); expect(getEditor().document).toMatchSnapshot(); }); @@ -48,7 +48,7 @@ describe("Test splitBlocks", () => { it("Don't keep type", () => { getEditor().setTextCursorPosition("heading-0"); - splitBlocks(getSelectionAnchorPosWithOffset(4)); + splitBlock(getSelectionAnchorPosWithOffset(4)); expect(getEditor().document).toMatchSnapshot(); }); @@ -56,7 +56,7 @@ describe("Test splitBlocks", () => { it.skip("Keep props", () => { getEditor().setTextCursorPosition("paragraph-with-props"); - splitBlocks(getSelectionAnchorPosWithOffset(4), false, true); + splitBlock(getSelectionAnchorPosWithOffset(4), false, true); expect(getEditor().document).toMatchSnapshot(); }); @@ -64,7 +64,7 @@ describe("Test splitBlocks", () => { it("Don't keep props", () => { getEditor().setTextCursorPosition("paragraph-with-props"); - splitBlocks(getSelectionAnchorPosWithOffset(4)); + splitBlock(getSelectionAnchorPosWithOffset(4)); expect(getEditor().document).toMatchSnapshot(); }); diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts similarity index 97% rename from packages/core/src/api/blockManipulation/commands/splitBlock.ts rename to packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index 43f4cdee4..ff340dd60 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -1,7 +1,7 @@ import { Fragment, Slice } from "prosemirror-model"; import { EditorState, TextSelection } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; +import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; export const splitBlockCommand = (posInBlock: number, keepType?: boolean, keepProps?: boolean) => diff --git a/packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap similarity index 100% rename from packages/core/src/api/blockManipulation/commands/__snapshots__/updateBlock.test.ts.snap rename to packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts similarity index 75% rename from packages/core/src/api/blockManipulation/commands/updateBlock.test.ts rename to packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts index 7640c763a..d4f2ddcc4 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts @@ -1,10 +1,56 @@ import { describe, expect, it } from "vitest"; -import { setupTestEnv } from "../setupTestEnv.js"; +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", { @@ -252,3 +298,31 @@ describe("Test updateBlock", () => { expect(getEditor().document).toMatchSnapshot(); }); }); + +// TODO: This seems like it really tests converting strings to inline content? +// 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/commands/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts similarity index 91% rename from packages/core/src/api/blockManipulation/commands/updateBlock.ts rename to packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 84aa5f58e..71c95e7e9 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -1,20 +1,23 @@ import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; import { EditorState, NodeSelection, TextSelection } 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 { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; +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 { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; import { blockToNode, inlineContentToNodes, nodeToBlock, tableContentToNodes, -} from "../../nodeConversions/nodeConversions.js"; -import { getNodeById } from "../../nodeUtil.js"; +} from "../../../nodeConversions/nodeConversions.js"; +import { getNodeById } from "../../../nodeUtil.js"; export const updateBlockCommand = < diff --git a/packages/core/src/api/blockManipulation/insertContentAt.ts b/packages/core/src/api/blockManipulation/insertContentAt.ts new file mode 100644 index 000000000..64791083d --- /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/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index 660a66be6..1560cfe24 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -1,5 +1,5 @@ import { InputRule } from "@tiptap/core"; -import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; import { PropSchema, diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts index 7f5b87454..eb6d3e2c7 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -1,5 +1,5 @@ import { InputRule } from "@tiptap/core"; -import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, diff --git a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts index 6755fd7b3..b126dbd90 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -1,5 +1,5 @@ import { InputRule } from "@tiptap/core"; -import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, diff --git a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts index 9e5749f26..a3d5fd4af 100644 --- a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +++ b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts @@ -1,5 +1,5 @@ -import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock.js"; -import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; +import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock/splitBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts index 269c48b98..ebc29cefc 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -1,5 +1,5 @@ import { InputRule } from "@tiptap/core"; -import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock.js"; +import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; import { PropSchema, diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts index eeefcbb41..8616b1bda 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -1,4 +1,4 @@ -import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; +import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; import { createBlockSpecFromStronglyTypedTiptapNode, diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index f0f68962d..c972054b5 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -2,17 +2,15 @@ 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, -} from "../api/blockManipulation/blockManipulation.js"; -import { updateBlock } from "../api/blockManipulation/commands/updateBlock.js"; +import { insertContentAt } from "../api/blockManipulation/insertContentAt.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 { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; @@ -891,7 +889,7 @@ export class BlockNoteEditor< referenceBlock: BlockIdentifier, placement: "before" | "after" = "before" ) { - return insertBlocks(blocksToInsert, referenceBlock, placement, this); + return insertBlocks(this, blocksToInsert, referenceBlock, placement); } /** @@ -913,7 +911,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); } /** @@ -927,7 +925,7 @@ export class BlockNoteEditor< blocksToRemove: BlockIdentifier[], blocksToInsert: PartialBlock[] ) { - return replaceBlocks(blocksToRemove, blocksToInsert, this); + return replaceBlocks(this, blocksToRemove, blocksToInsert); } /** diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index ef5ad21c7..0bb43ed11 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -1,9 +1,9 @@ import { Extension } from "@tiptap/core"; import { TextSelection } from "prosemirror-state"; -import { mergeBlocksCommand } from "../../api/blockManipulation/commands/mergeBlocks.js"; -import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock.js"; -import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock.js"; +import { 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 { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; From e3926375088d9022183f64f70b2b1d8e67cac815 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 10 Oct 2024 13:42:40 +0200 Subject: [PATCH 13/34] Added `getNearestBlockContainerPos` --- packages/core/src/api/getBlockInfoFromPos.ts | 101 ++++++++++--------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index 91a5c8543..9fef17774 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -36,70 +36,71 @@ export function getBlockInfo(blockContainer: Node): BlockInfoWithoutPositions { } /** - * Retrieves information regarding the nearest blockContainer node in a - * ProseMirror doc, relative to a position. + * 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. - * @returns A BlockInfo object for the nearest blockContainer node. + * @returns The position just before 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; +export function getNearestBlockContainerPos(doc: Node, pos: number) { + const $pos = doc.resolve(pos); - 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 $pos.before(depth); } - } - // 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); - - const maxDepth = $pos.depth; - let node = $pos.node(maxDepth); - let depth = maxDepth; - - // 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." - ); - } - + // 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 as almost every position will be within a blockContainer, + // according to the schema. + const allBlockContainerPositions: number[] = []; + doc.descendants((node, pos) => { if (node.type.name === "blockContainer") { - break; + allBlockContainerPositions.push(pos); } + }); - depth -= 1; - node = $pos.node(depth); - } + return ( + allBlockContainerPositions.find((position) => position >= pos) || + allBlockContainerPositions[allBlockContainerPositions.length - 1] + ); +} + +/** + * 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 { + const $pos = doc.resolve(getNearestBlockContainerPos(doc, pos)); + const node = $pos.nodeAfter!; const { id, contentNode, contentType, numChildBlocks } = getBlockInfo(node); - const startPos = $pos.start(depth); - const endPos = $pos.end(depth); + const posInsideBlockContainer = $pos.pos + 1; + const $posInsideBlockContainer = doc.resolve(posInsideBlockContainer); + const depth = $posInsideBlockContainer.depth; + + const startPos = $posInsideBlockContainer.start(depth); + const endPos = $posInsideBlockContainer.end(depth); return { id, From 5dd4afdcb0234586d1d1f1e5f6f875b536204484 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 10 Oct 2024 17:44:59 +0200 Subject: [PATCH 14/34] Refactored `getBlockInfoFromPos` --- .../commands/mergeBlocks/mergeBlocks.test.ts | 5 +- .../commands/mergeBlocks/mergeBlocks.ts | 25 +-- .../commands/moveBlock/moveBlock.test.ts | 20 +-- .../commands/moveBlock/moveBlock.ts | 6 +- .../commands/splitBlock/splitBlock.ts | 26 ++-- .../commands/updateBlock/updateBlock.ts | 34 ++-- .../fromClipboard/handleFileInsertion.ts | 2 +- packages/core/src/api/getBlockInfoFromPos.ts | 147 ++++++++++++------ .../src/api/getCurrentBlockContentType.ts | 14 -- .../api/nodeConversions/nodeConversions.ts | 22 +-- .../HeadingBlockContent.ts | 30 +++- .../BulletListItemBlockContent.ts | 16 +- .../CheckListItemBlockContent.ts | 23 ++- .../ListItemKeyboardShortcuts.ts | 12 +- .../NumberedListIndexingPlugin.ts | 21 +-- .../NumberedListItemBlockContent.ts | 16 +- .../ParagraphBlockContent.ts | 9 +- .../core/src/editor/BlockNoteEditor.test.ts | 13 +- packages/core/src/editor/BlockNoteEditor.ts | 66 ++++---- .../KeyboardShortcutsExtension.ts | 58 +++---- packages/core/src/index.ts | 2 +- 21 files changed, 338 insertions(+), 229 deletions(-) delete mode 100644 packages/core/src/api/getCurrentBlockContentType.ts diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 53f42a6f6..8f1bb7445 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -14,11 +14,10 @@ function mergeBlocks(posBetweenBlocks: number) { } function getPosAfterSelectedBlock() { - const { startPos } = getBlockInfoFromPos( + return getBlockInfoFromPos( getEditor()._tiptapEditor.state.doc, getEditor()._tiptapEditor.state.selection.from - ); - return getEditor()._tiptapEditor.state.doc.resolve(startPos).after(); + ).blockContainer.afterPos; } describe("Test mergeBlocks", () => { diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index dce074864..c84b8f06b 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -23,22 +23,22 @@ export const mergeBlocksCommand = return false; } - const nextBlockInfo = getBlockInfoFromPos(state.doc, posBetweenBlocks + 1); - - const { node, contentNode, startPos, endPos, depth } = nextBlockInfo!; + const nextBlockInfo = getBlockInfoFromPos(state.doc, posBetweenBlocks); // 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) { + if (nextBlockInfo.blockGroup) { const childBlocksStart = state.doc.resolve( - startPos + contentNode.nodeSize + 1 + nextBlockInfo.blockGroup.beforePos + 1 + ); + const childBlocksEnd = state.doc.resolve( + nextBlockInfo.blockGroup.afterPos - 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); + state.tr.lift(childBlocksRange!, nextBlockInfo.depth); } } @@ -46,7 +46,7 @@ export const mergeBlocksCommand = let prevBlockInfo = getBlockInfoFromPos(state.doc, prevBlockEndPos); // Finds the nearest previous block, regardless of nesting level. - while (prevBlockInfo!.numChildBlocks > 0) { + while (prevBlockInfo.blockGroup) { prevBlockEndPos--; prevBlockInfo = getBlockInfoFromPos(state.doc, prevBlockEndPos); if (prevBlockInfo === undefined) { @@ -59,11 +59,14 @@ export const mergeBlocksCommand = if (dispatch) { dispatch( state.tr - .deleteRange(startPos, startPos + contentNode.nodeSize) + .deleteRange( + nextBlockInfo.blockContent.beforePos, + nextBlockInfo.blockContent.afterPos + ) .replace( prevBlockEndPos - 1, - startPos, - new Slice(contentNode.content, 0, 0) + nextBlockInfo.blockContent.beforePos, + new Slice(nextBlockInfo.blockContent.node.content, 0, 0) ) // .scrollIntoView() ); diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts index fed2e9470..92988bc2f 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts @@ -13,7 +13,7 @@ import { setupTestEnv } from "../../setupTestEnv.js"; const getEditor = setupTestEnv(); function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { - const { startPos, contentNode } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos( getEditor()._tiptapEditor.state.doc, getEditor()._tiptapEditor.state.selection.from ); @@ -24,19 +24,18 @@ function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { CellSelection.create( getEditor()._tiptapEditor.state.doc, getEditor() - ._tiptapEditor.state.doc.resolve(startPos + 3) + ._tiptapEditor.state.doc.resolve(blockContent.beforePos + 3) .before(), getEditor() - ._tiptapEditor.state.doc.resolve( - startPos + contentNode.nodeSize - 3 - ) + ._tiptapEditor.state.doc.resolve(blockContent.afterPos - 3) .before() ) ) ); } else if (selectionType === "node") { - const resolvedContentStartPos = - getEditor()._tiptapEditor.state.doc.resolve(startPos); + const resolvedContentStartPos = getEditor()._tiptapEditor.state.doc.resolve( + blockContent.beforePos + ); getEditor()._tiptapEditor.view.dispatch( getEditor()._tiptapEditor.state.tr.setSelection( @@ -51,10 +50,11 @@ function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { ) ); } else { - const resolvedContentStartPos = - getEditor()._tiptapEditor.state.doc.resolve(startPos); + const resolvedContentStartPos = getEditor()._tiptapEditor.state.doc.resolve( + blockContent.beforePos + ); const resolvedContentEndPos = getEditor()._tiptapEditor.state.doc.resolve( - startPos + contentNode.nodeSize + blockContent.afterPos ); getEditor()._tiptapEditor.view.dispatch( diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts index ace9541c6..c58eecb8a 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts @@ -31,14 +31,14 @@ type BlockSelectionData = ( function getBlockSelectionData( editor: BlockNoteEditor ): BlockSelectionData { - const { id, startPos } = getBlockInfoFromPos( + const { blockContainer } = getBlockInfoFromPos( editor._tiptapEditor.state.doc, editor._tiptapEditor.state.selection.from ); 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/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index ff340dd60..d05bdd8be 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -17,14 +17,20 @@ export const splitBlockCommand = return false; } - const { contentNode, contentType, startPos, endPos, depth } = blockInfo; + const { depth, blockContainer, blockContent } = blockInfo; - const originalBlockContent = state.doc.cut(startPos + 1, posInBlock); - const newBlockContent = state.doc.cut(posInBlock, endPos - 1); + const originalBlockContent = state.doc.cut( + blockContainer.beforePos + 2, + posInBlock + ); + const newBlockContent = state.doc.cut( + posInBlock, + blockContainer.afterPos - 2 + ); const newBlock = state.schema.nodes["blockContainer"].createAndFill()!; - const newBlockInsertionPos = endPos + 1; + const newBlockInsertionPos = blockContainer.afterPos; const newBlockContentPos = newBlockInsertionPos + 2; if (dispatch) { @@ -38,7 +44,7 @@ export const splitBlockCommand = newBlockContentPos, newBlockContentPos + 1, newBlockContent.content.size > 0 - ? new Slice(Fragment.from(newBlockContent), depth + 2, depth + 2) + ? new Slice(Fragment.from(newBlockContent), depth + 3, depth + 3) : undefined ); @@ -48,8 +54,8 @@ export const splitBlockCommand = state.tr.setBlockType( newBlockContentPos, newBlockContentPos, - state.schema.node(contentType).type, - keepProps ? contentNode.attrs : undefined + blockContent.node.type, + keepProps ? blockContent.node.attrs : undefined ); } @@ -61,10 +67,10 @@ export const splitBlockCommand = // 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, + blockContainer.beforePos + 2, + blockContainer.afterPos - 2, originalBlockContent.content.size > 0 - ? new Slice(Fragment.from(originalBlockContent), depth + 2, depth + 2) + ? new Slice(Fragment.from(originalBlockContent), depth + 3, depth + 3) : undefined ); diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 71c95e7e9..37888809b 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -41,7 +41,7 @@ export const updateBlockCommand = return false; } - const { startPos, endPos, node, contentNode } = blockInfo; + const { blockContainer, blockContent, blockGroup } = blockInfo; if (dispatch) { // Adds blockGroup node with child blocks if necessary. @@ -56,23 +56,23 @@ export const updateBlockCommand = } // Checks if a blockGroup node already exists. - if (node.childCount === 2) { + if (blockGroup) { // Replaces all child nodes in the existing blockGroup with the ones created earlier. state.tr.replace( - startPos + contentNode.nodeSize + 1, - endPos - 1, + 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( - startPos + contentNode.nodeSize, + blockContent.afterPos, state.schema.nodes["blockGroup"].create({}, childNodes) ); } } - const oldType = contentNode.type.name; + const oldType = blockContent.node.type.name; const newType = block.type || oldType; // The code below determines the new content of the block. @@ -134,10 +134,10 @@ export const updateBlockCommand = if (content === "keep") { // use setNodeMarkup to only update the type and attributes state.tr.setNodeMarkup( - startPos, + blockContent.beforePos, block.type === undefined ? undefined : state.schema.nodes[block.type], { - ...contentNode.attrs, + ...blockContent.node.attrs, ...block.props, } ); @@ -147,11 +147,11 @@ export const updateBlockCommand = // sets it to the next block. state.tr .replaceWith( - startPos, - startPos + contentNode.nodeSize, + blockContent.beforePos, + blockContent.afterPos, state.schema.nodes[newType].create( { - ...contentNode.attrs, + ...blockContent.node.attrs, ...block.props, }, content @@ -162,20 +162,22 @@ export const updateBlockCommand = // 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)) + ? new NodeSelection(state.tr.doc.resolve(blockContent.beforePos)) : state.schema.nodes[newType].spec.content === "inline*" - ? new TextSelection(state.tr.doc.resolve(startPos)) + ? new TextSelection(state.tr.doc.resolve(blockContent.beforePos)) : // 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)) + new TextSelection( + state.tr.doc.resolve(blockContent.beforePos + 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, + state.tr.setNodeMarkup(blockContainer.beforePos, undefined, { + ...blockContainer.node.attrs, ...block.props, }); } diff --git a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts index f27ac25b9..4fd86efe2 100644 --- a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts +++ b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts @@ -139,7 +139,7 @@ export async function handleFileInsertion< insertedBlockId = editor.insertBlocks( [fileBlock], - blockInfo.id, + blockInfo.blockContainer.node.attrs.id, "after" )[0].id; } else { diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index 9fef17774..72cfe03ef 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -1,40 +1,18 @@ -import { Node, NodeType } from "prosemirror-model"; +import { Node } from "prosemirror-model"; -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; +export type BlockInfo = { depth: number; + 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. - */ -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; - - return { - id, - node: blockContainer, - contentNode, - contentType, - numChildBlocks, - }; -} - /** * Retrieves the position just before the nearest blockContainer node in a * ProseMirror doc, relative to a position. If the position is within a @@ -50,6 +28,12 @@ export function getBlockInfo(blockContainer: Node): BlockInfoWithoutPositions { export function getNearestBlockContainerPos(doc: Node, pos: number) { const $pos = doc.resolve(pos); + // 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 $pos.pos; + } + // Checks the node containing the position and its ancestors until a // blockContainer node is found and returned. let depth = $pos.depth; @@ -63,12 +47,14 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { node = $pos.node(depth); } + // TODO: Make more specific & log warn // 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 as almost every position will be within a blockContainer, - // according to the schema. + // 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") { @@ -76,12 +62,85 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { } }); + // eslint-disable-next-line no-console + console.warn(`Position ${pos} is not within a blockContainer node.`); + return ( allBlockContainerPositions.find((position) => position >= pos) || allBlockContainerPositions[allBlockContainerPositions.length - 1] ); } +export function getBlockInfo( + node: Node, + beforePos?: number +): Omit { + const blockContainerNode = node; + const blockContainerBeforePos = beforePos || 0; + 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, + }; + } + }); + + if (!blockContent) { + throw new Error( + `blockContainer node does not contain a blockContent node in its children: ${blockContainerNode}` + ); + } + + // TODO: Remove + if ( + blockGroup && + (blockContent as SingleBlockInfo).afterPos !== + (blockGroup as SingleBlockInfo).beforePos + ) { + throw new Error( + `blockContent.afterPos (${ + (blockContent as SingleBlockInfo).afterPos + }) does not match blockGroup.beforePos (${ + (blockGroup as SingleBlockInfo | undefined)?.beforePos + })` + ); + } + + return { + blockContainer, + blockContent, + blockGroup, + }; +} + /** * Retrieves information regarding the nearest blockContainer node in a * ProseMirror doc, relative to a position. @@ -91,25 +150,19 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { */ export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo { const $pos = doc.resolve(getNearestBlockContainerPos(doc, pos)); - const node = $pos.nodeAfter!; - - const { id, contentNode, contentType, numChildBlocks } = getBlockInfo(node); + const depth = $pos.depth; - const posInsideBlockContainer = $pos.pos + 1; - const $posInsideBlockContainer = doc.resolve(posInsideBlockContainer); - const depth = $posInsideBlockContainer.depth; + const node = $pos.nodeAfter; + const beforePos = $pos.pos; - const startPos = $posInsideBlockContainer.start(depth); - const endPos = $posInsideBlockContainer.end(depth); + if (node === null || node.type.name !== "blockContainer") { + throw new Error( + `No blockContainer node found near position ${pos}. getNearestBlockContainerPos returned ${beforePos}.` + ); + } return { - id, - node, - contentNode, - contentType, - numChildBlocks, - startPos, - endPos, depth, + ...getBlockInfo(node, beforePos), }; } diff --git a/packages/core/src/api/getCurrentBlockContentType.ts b/packages/core/src/api/getCurrentBlockContentType.ts deleted file mode 100644 index b0345203f..000000000 --- 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/nodeConversions.ts b/packages/core/src/api/nodeConversions/nodeConversions.ts index 669cdac08..c3bd4bb57 100644 --- a/packages/core/src/api/nodeConversions/nodeConversions.ts +++ b/packages/core/src/api/nodeConversions/nodeConversions.ts @@ -566,9 +566,9 @@ export function nodeToBlock< return cachedBlock; } - const blockInfo = getBlockInfo(node); + const { blockContainer, blockContent, blockGroup } = getBlockInfo(node); - let id = blockInfo.id; + let id = blockContainer.node.attrs.id; // Only used for blocks converted from other formats. if (id === null) { @@ -578,13 +578,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 +595,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/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index 1560cfe24..a501c4647 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -1,6 +1,6 @@ import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; +import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -47,7 +47,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ return new InputRule({ find: new RegExp(`^(#{${level}})\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return; } @@ -72,7 +77,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { "Mod-Alt-1": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return true; } @@ -91,7 +101,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ ); }, "Mod-Alt-2": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return true; } @@ -109,7 +124,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ ); }, "Mod-Alt-3": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return true; } diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts index eb6d3e2c7..9cd518b28 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -1,6 +1,6 @@ import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; +import { getBlockInfoFromPos } from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -27,7 +27,12 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`^[-+*]\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return; } @@ -49,7 +54,12 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ return { Enter: () => handleEnter(this.options.editor), "Mod-Shift-8": () => { - if (getCurrentBlockContentType(this.options.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return true; } diff --git a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts index b126dbd90..f221574bc 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -1,6 +1,6 @@ import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; +import { getBlockInfoFromPos } from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -45,7 +45,12 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`\\[\\s*\\]\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return; } @@ -65,7 +70,12 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`\\[[Xx]\\]\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return; } @@ -89,7 +99,12 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ return { Enter: () => handleEnter(this.options.editor), "Mod-Shift-9": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return true; } diff --git a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts index a3d5fd4af..48201b68b 100644 --- a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +++ b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts @@ -5,7 +5,7 @@ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; export const handleEnter = (editor: BlockNoteEditor) => { const ttEditor = editor._tiptapEditor; - const { contentNode, contentType } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos( ttEditor.state.doc, ttEditor.state.selection.from )!; @@ -15,9 +15,9 @@ export const handleEnter = (editor: BlockNoteEditor) => { 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 ) { @@ -28,7 +28,7 @@ export const handleEnter = (editor: BlockNoteEditor) => { () => // Changes list item block to a paragraph block if the content is empty. commands.command(() => { - if (contentNode.childCount === 0) { + if (blockContent.node.childCount === 0) { return commands.command( updateBlockCommand(editor, state.selection.from, { type: "paragraph", @@ -44,7 +44,7 @@ export const handleEnter = (editor: BlockNoteEditor) => { // 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() .command(splitBlockCommand(state.selection.from, true)) diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts index 4f4b95b94..fec5350bf 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts @@ -23,15 +23,18 @@ export const NumberedListIndexingPlugin = () => { let newIndex = "1"; const isFirstBlockInDoc = pos === 1; - const blockInfo = getBlockInfoFromPos(tr.doc, pos + 1)!; - if (blockInfo === undefined) { - return; - } + const blockInfo = getBlockInfoFromPos(tr.doc, pos)!; // 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)!; + const prevBlock = tr.doc.resolve( + blockInfo.blockContainer.beforePos - 1 + ).nodeBefore!; + const prevBlockInfo = getBlockInfoFromPos( + tr.doc, + blockInfo.blockContainer.beforePos - prevBlock.nodeSize + )!; if (prevBlockInfo === undefined) { return; } @@ -40,8 +43,8 @@ export const NumberedListIndexingPlugin = () => { blockInfo.depth !== prevBlockInfo.depth; if (!isFirstBlockInNestingLevel) { - const prevBlockContentNode = prevBlockInfo.contentNode; - const prevBlockContentType = prevBlockInfo.contentType; + const prevBlockContentNode = prevBlockInfo.blockContent.node; + const prevBlockContentType = prevBlockContentNode.type; const isPrevBlockOrderedListItem = prevBlockContentType.name === "numberedListItem"; @@ -54,13 +57,13 @@ export const NumberedListIndexingPlugin = () => { } } - 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 ebc29cefc..65561da5f 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -1,6 +1,6 @@ import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType.js"; +import { getBlockInfoFromPos } from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -40,7 +40,12 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`^1\\.\\s$`), handler: ({ state, chain, range }) => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return; } @@ -62,7 +67,12 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ return { Enter: () => handleEnter(this.options.editor), "Mod-Shift-7": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return true; } diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts index 8616b1bda..5d496e30c 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -1,5 +1,5 @@ import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getCurrentBlockContentType } from "../../api/getCurrentBlockContentType.js"; +import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; import { createBlockSpecFromStronglyTypedTiptapNode, createStronglyTypedTiptapNode, @@ -19,7 +19,12 @@ export const ParagraphBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { "Mod-Alt-0": () => { - if (getCurrentBlockContentType(this.editor) !== "inline*") { + if ( + getBlockInfoFromPos( + this.editor.state.doc, + this.editor.state.selection.anchor + ).blockContent.node.type.spec.content !== "inline*" + ) { return true; } diff --git a/packages/core/src/editor/BlockNoteEditor.test.ts b/packages/core/src/editor/BlockNoteEditor.test.ts index 2fb878335..6e1e056af 100644 --- a/packages/core/src/editor/BlockNoteEditor.test.ts +++ b/packages/core/src/editor/BlockNoteEditor.test.ts @@ -7,8 +7,11 @@ 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 { blockContent } = getBlockInfoFromPos( + editor._tiptapEditor.state.doc, + 2 + ); + expect(blockContent.node.type.name).toEqual("paragraph"); }); it("immediately replaces doc", async () => { @@ -66,7 +69,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 c972054b5..937b0c18e 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -663,52 +663,39 @@ export class BlockNoteEditor< ISchema, SSchema > { - const { node, depth, startPos, endPos } = getBlockInfoFromPos( + const { depth, blockContainer } = 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(); - } + const prevNode = this._tiptapEditor.state.doc.resolve( + blockContainer.beforePos + ).nodeBefore; // 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(); - } + const nextNode = this._tiptapEditor.state.doc.resolve( + blockContainer.afterPos + ).nodeAfter; // Gets parent blockContainer node, if the current node is nested. let parentNode: Node | undefined = undefined; - if (nodeDepth > 2) { + if (depth > 1) { parentNode = this._tiptapEditor.state.doc - .resolve(startPos - 1) - .node(nodeDepth - 2); + .resolve(blockContainer.beforePos) + .node(depth - 1); } return { block: nodeToBlock( - node, + blockContainer.node, this.schema.blockSchema, this.schema.inlineContentSchema, this.schema.styleSchema, this.blockCache ), prevBlock: - prevNode === undefined + prevNode === null ? undefined : nodeToBlock( prevNode, @@ -718,7 +705,7 @@ export class BlockNoteEditor< this.blockCache ), nextBlock: - nextNode === undefined + nextNode === null ? undefined : nodeToBlock( nextNode, @@ -753,37 +740,37 @@ export class BlockNoteEditor< const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id; const { posBeforeNode } = getNodeById(id, this._tiptapEditor.state.doc); - const { startPos, contentNode } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos( this._tiptapEditor.state.doc, posBeforeNode + 2 )!; const contentType: "none" | "inline" | "table" = - this.schema.blockSchema[contentNode.type.name]!.content; + this.schema.blockSchema[blockContent.node.type.name]!.content; if (contentType === "none") { - this._tiptapEditor.commands.setNodeSelection(startPos); + this._tiptapEditor.commands.setNodeSelection(blockContent.beforePos); return; } if (contentType === "inline") { if (placement === "start") { - this._tiptapEditor.commands.setTextSelection(startPos + 1); - } else { this._tiptapEditor.commands.setTextSelection( - startPos + contentNode.nodeSize - 1 + blockContent.beforePos + 1 ); + } else { + this._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. - this._tiptapEditor.commands.setTextSelection(startPos + 4); - } else { this._tiptapEditor.commands.setTextSelection( - startPos + contentNode.nodeSize - 4 + blockContent.beforePos + 4 ); + } else { + this._tiptapEditor.commands.setTextSelection(blockContent.afterPos - 4); } } else { throw new UnreachableCaseError(contentType); @@ -1073,12 +1060,15 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor can be nested. */ public canNestBlock() { - const { startPos, depth } = getBlockInfoFromPos( + const { blockContainer } = getBlockInfoFromPos( this._tiptapEditor.state.doc, this._tiptapEditor.state.selection.from )!; - return this._tiptapEditor.state.doc.resolve(startPos).index(depth - 1) > 0; + return ( + this._tiptapEditor.state.doc.resolve(blockContainer.beforePos) + .nodeBefore !== null + ); } /** @@ -1097,7 +1087,7 @@ export class BlockNoteEditor< this._tiptapEditor.state.selection.from )!; - return depth > 2; + return depth > 1; } /** diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 0bb43ed11..823edc838 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -23,13 +23,14 @@ export const KeyboardShortcutsExtension = Extension.create<{ // Reverts block content type to a paragraph if the selection is at the start of the block. () => commands.command(({ state }) => { - const { contentType, startPos } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos( state.doc, state.selection.from )!; - const selectionAtBlockStart = state.selection.from === startPos + 1; - const isParagraph = contentType.name === "paragraph"; + const selectionAtBlockStart = + state.selection.from === blockContent.beforePos + 1; + const isParagraph = blockContent.node.type.name === "paragraph"; if (selectionAtBlockStart && !isParagraph) { return commands.command( @@ -45,12 +46,13 @@ export const KeyboardShortcutsExtension = Extension.create<{ // 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( + const { blockContent } = getBlockInfoFromPos( state.doc, state.selection.from )!; - const selectionAtBlockStart = state.selection.from === startPos + 1; + const selectionAtBlockStart = + state.selection.from === blockContent.beforePos + 1; if (selectionAtBlockStart) { return commands.liftListItem("blockContainer"); @@ -62,22 +64,23 @@ export const KeyboardShortcutsExtension = Extension.create<{ // is at the start of the block. () => commands.command(({ state }) => { - const { depth, startPos } = getBlockInfoFromPos( + const { depth, blockContainer, blockContent } = getBlockInfoFromPos( state.doc, state.selection.from )!; - const selectionAtBlockStart = state.selection.from === startPos + 1; + const selectionAtBlockStart = + state.selection.from === blockContent.beforePos + 1; const selectionEmpty = state.selection.empty; - const blockAtDocStart = startPos === 2; + const blockAtDocStart = blockContainer.beforePos === 1; - const posBetweenBlocks = startPos - 1; + const posBetweenBlocks = blockContainer.beforePos; if ( !blockAtDocStart && selectionAtBlockStart && selectionEmpty && - depth === 2 + depth === 1 ) { return commands.command(mergeBlocksCommand(posBetweenBlocks)); } @@ -95,15 +98,16 @@ export const KeyboardShortcutsExtension = Extension.create<{ // 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; + // TODO: Change this to not rely on offsets & schema assumptions + const { depth, blockContainer, blockContent, blockGroup } = + getBlockInfoFromPos(state.doc, state.selection.from)!; + + const blockAtDocEnd = + blockContainer.afterPos === state.doc.nodeSize - 3; + const selectionAtBlockEnd = + state.selection.from === blockContent.afterPos - 1; const selectionEmpty = state.selection.empty; - const hasChildBlocks = node.childCount === 2; + const hasChildBlocks = blockGroup !== undefined; if ( !blockAtDocEnd && @@ -112,7 +116,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ !hasChildBlocks ) { let oldDepth = depth; - let newPos = endPos + 2; + let newPos = blockContainer.afterPos + 1; let newDepth = state.doc.resolve(newPos).depth; while (newDepth < oldDepth) { @@ -134,7 +138,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // of the block. () => commands.command(({ state }) => { - const { contentNode, depth } = getBlockInfoFromPos( + const { depth, blockContent } = getBlockInfoFromPos( state.doc, state.selection.from )!; @@ -143,8 +147,8 @@ export const KeyboardShortcutsExtension = Extension.create<{ state.selection.$anchor.parentOffset === 0; const selectionEmpty = state.selection.anchor === state.selection.head; - const blockEmpty = contentNode.childCount === 0; - const blockIndented = depth > 2; + const blockEmpty = blockContent.node.childCount === 0; + const blockIndented = depth > 1; if ( selectionAtBlockStart && @@ -161,7 +165,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // empty & at the start of the block. () => commands.command(({ state, dispatch }) => { - const { contentNode, endPos } = getBlockInfoFromPos( + const { blockContainer, blockContent } = getBlockInfoFromPos( state.doc, state.selection.from )!; @@ -170,10 +174,10 @@ export const KeyboardShortcutsExtension = Extension.create<{ state.selection.$anchor.parentOffset === 0; const selectionEmpty = state.selection.anchor === state.selection.head; - const blockEmpty = contentNode.childCount === 0; + const blockEmpty = blockContent.node.childCount === 0; if (selectionAtBlockStart && selectionEmpty && blockEmpty) { - const newBlockInsertionPos = endPos + 1; + const newBlockInsertionPos = blockContainer.afterPos; const newBlockContentPos = newBlockInsertionPos + 2; if (dispatch) { @@ -197,14 +201,14 @@ export const KeyboardShortcutsExtension = Extension.create<{ // deletes the selection beforehand, if it's not empty. () => commands.command(({ state, chain }) => { - const { contentNode } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos( state.doc, state.selection.from )!; const selectionAtBlockStart = state.selection.$anchor.parentOffset === 0; - const blockEmpty = contentNode.childCount === 0; + const blockEmpty = blockContent.node.childCount === 0; if (!blockEmpty) { chain() diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 03a26587e..da6ddc9f0 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"; From 58016a96b0f0647ee4cfc5f75c44c311fc94e6f0 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Sat, 12 Oct 2024 15:51:08 +0200 Subject: [PATCH 15/34] Rewrote `splitBlockCommand` --- .../commands/splitBlock/splitBlock.ts | 89 ++++++------------- 1 file changed, 29 insertions(+), 60 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index d05bdd8be..fb00b4112 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -1,81 +1,50 @@ -import { Fragment, Slice } from "prosemirror-model"; -import { EditorState, TextSelection } from "prosemirror-state"; +import { EditorState } from "prosemirror-state"; import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; -export const splitBlockCommand = - (posInBlock: number, keepType?: boolean, keepProps?: boolean) => - ({ +export const splitBlockCommand = ( + posInBlock: number, + keepType?: boolean, + keepProps?: boolean +) => { + return ({ state, dispatch, }: { state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) { - return false; - } - - const { depth, blockContainer, blockContent } = blockInfo; - - const originalBlockContent = state.doc.cut( - blockContainer.beforePos + 2, + const { blockContainer, blockContent, blockGroup } = getBlockInfoFromPos( + state.doc, posInBlock ); - const newBlockContent = state.doc.cut( - posInBlock, - blockContainer.afterPos - 2 - ); - - const newBlock = state.schema.nodes["blockContainer"].createAndFill()!; - const newBlockInsertionPos = blockContainer.afterPos; - const newBlockContentPos = newBlockInsertionPos + 2; + const newBlock = state.schema.nodes["blockContainer"].createAndFill( + keepProps ? { ...blockContainer.node.attrs, id: undefined } : null, + [ + state.schema.nodes[ + keepType ? blockContent.node.type.name : "paragraph" + ].createAndFill( + keepProps ? blockContent.node.attrs : null, + blockContent.node.content.cut(posInBlock - blockContent.beforePos - 1) + )!, + ...(blockGroup ? [blockGroup.node] : []), + ] + )!; 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 + 3, depth + 3) - : 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, - blockContent.node.type, - keepProps ? blockContent.node.attrs : undefined - ); + // Insert new block + state.tr.insert(blockContainer.afterPos, newBlock); + // Delete original block's children, if they exist + if (blockGroup) { + state.tr.delete(blockGroup.beforePos, blockGroup.afterPos); } - - // 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( - blockContainer.beforePos + 2, - blockContainer.afterPos - 2, - originalBlockContent.content.size > 0 - ? new Slice(Fragment.from(originalBlockContent), depth + 3, depth + 3) - : undefined - ); + // Delete original block's content past the cursor + state.tr.delete(posInBlock, blockContent.afterPos - 1); // state.tr.scrollIntoView(); } return true; }; +}; From 6bcd81d14b30f1c8f18c36a884f0347588c7277f Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Sat, 12 Oct 2024 18:24:03 +0200 Subject: [PATCH 16/34] Added text cursor position tests --- .../__snapshots__/insertBlocks.test.ts.snap | 96 +++--- .../__snapshots__/mergeBlocks.test.ts.snap | 96 +++--- .../__snapshots__/moveBlock.test.ts.snap | 296 ++++++++-------- .../__snapshots__/removeBlocks.test.ts.snap | 32 +- .../__snapshots__/replaceBlocks.test.ts.snap | 128 +++---- .../__snapshots__/splitBlock.test.ts.snap | 80 ++--- .../__snapshots__/updateBlock.test.ts.snap | 280 ++++++++-------- .../textCursorPosition.test.ts.snap | 316 ++++++++++++++++++ .../textCursorPosition.test.ts | 53 +++ .../textCursorPosition/textCursorPosition.ts | 132 ++++++++ .../src/api/blockManipulation/setupTestEnv.ts | 16 +- packages/core/src/editor/BlockNoteEditor.ts | 107 +----- 12 files changed, 1019 insertions(+), 613 deletions(-) create mode 100644 packages/core/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap create mode 100644 packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts create mode 100644 packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts 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 index 39be75618..ca7ba1f56 100644 --- 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 @@ -96,11 +96,11 @@ exports[`Test insertBlocks > Insert multiple blocks after 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -112,11 +112,11 @@ exports[`Test insertBlocks > Insert multiple blocks after 1`] = ` "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", @@ -421,11 +421,11 @@ exports[`Test insertBlocks > Insert multiple blocks after 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -437,11 +437,11 @@ exports[`Test insertBlocks > Insert multiple blocks after 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -590,11 +590,11 @@ exports[`Test insertBlocks > Insert multiple blocks before 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -606,11 +606,11 @@ exports[`Test insertBlocks > Insert multiple blocks before 1`] = ` "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", @@ -915,11 +915,11 @@ exports[`Test insertBlocks > Insert multiple blocks before 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -931,11 +931,11 @@ exports[`Test insertBlocks > Insert multiple blocks before 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1044,11 +1044,11 @@ exports[`Test insertBlocks > Insert single basic block after 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1060,11 +1060,11 @@ exports[`Test insertBlocks > Insert single basic block after 1`] = ` "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", @@ -1369,11 +1369,11 @@ exports[`Test insertBlocks > Insert single basic block after 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1385,11 +1385,11 @@ exports[`Test insertBlocks > Insert single basic block after 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1498,11 +1498,11 @@ exports[`Test insertBlocks > Insert single basic block before 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1514,11 +1514,11 @@ exports[`Test insertBlocks > Insert single basic block before 1`] = ` "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", @@ -1823,11 +1823,11 @@ exports[`Test insertBlocks > Insert single basic block before 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1839,11 +1839,11 @@ exports[`Test insertBlocks > Insert single basic block before 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2009,11 +2009,11 @@ exports[`Test insertBlocks > Insert single complex block after 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2025,11 +2025,11 @@ exports[`Test insertBlocks > Insert single complex block after 1`] = ` "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", @@ -2334,11 +2334,11 @@ exports[`Test insertBlocks > Insert single complex block after 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2350,11 +2350,11 @@ exports[`Test insertBlocks > Insert single complex block after 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2520,11 +2520,11 @@ exports[`Test insertBlocks > Insert single complex block before 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2536,11 +2536,11 @@ exports[`Test insertBlocks > Insert single complex block before 1`] = ` "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", @@ -2845,11 +2845,11 @@ exports[`Test insertBlocks > Insert single complex block before 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2861,11 +2861,11 @@ exports[`Test insertBlocks > Insert single complex block before 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index 4d21e8598..781150a14 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -28,11 +28,11 @@ exports[`Test mergeBlocks > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -44,11 +44,11 @@ exports[`Test mergeBlocks > Basic 1`] = ` "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", @@ -353,11 +353,11 @@ exports[`Test mergeBlocks > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -369,11 +369,11 @@ exports[`Test mergeBlocks > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -471,11 +471,11 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -487,11 +487,11 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` "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", @@ -779,11 +779,11 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -795,11 +795,11 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -897,11 +897,11 @@ exports[`Test mergeBlocks > First block has children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1Paragraph 2", + "text": "Double Nested Paragraph 0Paragraph 2", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -913,11 +913,11 @@ exports[`Test mergeBlocks > First block has children 1`] = ` "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", @@ -1205,11 +1205,11 @@ exports[`Test mergeBlocks > First block has children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1221,11 +1221,11 @@ exports[`Test mergeBlocks > First block has children 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1323,11 +1323,11 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1339,11 +1339,11 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` "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", @@ -1633,11 +1633,11 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1649,11 +1649,11 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1751,11 +1751,11 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1767,11 +1767,11 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "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", @@ -2061,11 +2061,11 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2077,11 +2077,11 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2177,11 +2177,11 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2193,11 +2193,11 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` "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", @@ -2486,11 +2486,11 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2502,11 +2502,11 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", 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 index 81b090c0e..b83261ae3 100644 --- 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 @@ -45,11 +45,11 @@ exports[`Test moveBlockDown > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -61,11 +61,11 @@ exports[`Test moveBlockDown > Basic 1`] = ` "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", @@ -370,11 +370,11 @@ exports[`Test moveBlockDown > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -386,11 +386,11 @@ exports[`Test moveBlockDown > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -488,11 +488,11 @@ exports[`Test moveBlockDown > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -504,11 +504,11 @@ exports[`Test moveBlockDown > Into children 1`] = ` "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", @@ -813,11 +813,11 @@ exports[`Test moveBlockDown > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -829,11 +829,11 @@ exports[`Test moveBlockDown > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -931,11 +931,11 @@ exports[`Test moveBlockDown > Last block 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -947,11 +947,11 @@ exports[`Test moveBlockDown > Last block 1`] = ` "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", @@ -1256,11 +1256,11 @@ exports[`Test moveBlockDown > Last block 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1272,11 +1272,11 @@ exports[`Test moveBlockDown > Last block 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1365,35 +1365,36 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, "type": "paragraph", }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph with children", - "type": "text", - }, - ], - "id": "paragraph-with-children", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "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": "Double Nested Paragraph 1", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1405,11 +1406,11 @@ exports[`Test moveBlockDown > Out of children 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 1", + "text": "Paragraph with children", "type": "text", }, ], - "id": "nested-paragraph-1", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1690,43 +1691,7 @@ exports[`Test moveBlockDown > Out of children 1`] = ` "type": "paragraph", }, { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Double Nested Paragraph 2", - "type": "text", - }, - ], - "id": "double-nested-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph 2", - "type": "text", - }, - ], - "id": "nested-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], + "children": [], "content": [ { "styles": { @@ -1757,6 +1722,41 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, "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": [], @@ -1816,11 +1816,11 @@ exports[`Test moveBlockUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1832,11 +1832,11 @@ exports[`Test moveBlockUp > Basic 1`] = ` "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", @@ -2141,11 +2141,11 @@ exports[`Test moveBlockUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2157,11 +2157,11 @@ exports[`Test moveBlockUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2259,11 +2259,11 @@ exports[`Test moveBlockUp > First block 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2275,11 +2275,11 @@ exports[`Test moveBlockUp > First block 1`] = ` "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", @@ -2584,11 +2584,11 @@ exports[`Test moveBlockUp > First block 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2600,11 +2600,11 @@ exports[`Test moveBlockUp > First block 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2702,11 +2702,11 @@ exports[`Test moveBlockUp > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2718,11 +2718,11 @@ exports[`Test moveBlockUp > Into children 1`] = ` "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", @@ -3027,11 +3027,11 @@ exports[`Test moveBlockUp > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3043,11 +3043,11 @@ exports[`Test moveBlockUp > Into children 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3139,15 +3139,33 @@ exports[`Test moveBlockUp > Out of children 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": "Double Nested Paragraph 1", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3156,23 +3174,6 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "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": {}, @@ -3463,33 +3464,15 @@ exports[`Test moveBlockUp > Out of children 1`] = ` { "children": [ { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Double Nested Paragraph 2", - "type": "text", - }, - ], - "id": "double-nested-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3498,6 +3481,23 @@ exports[`Test moveBlockUp > Out of children 1`] = ` "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": { 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 index f2a83b655..0246521a4 100644 --- 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 @@ -283,11 +283,11 @@ exports[`Test removeBlocks > Remove multiple consecutive blocks 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -299,11 +299,11 @@ exports[`Test removeBlocks > Remove multiple consecutive blocks 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -384,11 +384,11 @@ exports[`Test removeBlocks > Remove multiple non-consecutive blocks 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -400,11 +400,11 @@ exports[`Test removeBlocks > Remove multiple non-consecutive blocks 1`] = ` "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", @@ -653,11 +653,11 @@ exports[`Test removeBlocks > Remove single block 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -669,11 +669,11 @@ exports[`Test removeBlocks > Remove single block 1`] = ` "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", @@ -978,11 +978,11 @@ exports[`Test removeBlocks > Remove single block 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -994,11 +994,11 @@ exports[`Test removeBlocks > Remove single block 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", 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 index 57d57c6ed..0026f59c2 100644 --- 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 @@ -283,11 +283,11 @@ exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -299,11 +299,11 @@ exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -384,11 +384,11 @@ exports[`Test replaceBlocks > Remove multiple non-consecutive blocks 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -400,11 +400,11 @@ exports[`Test replaceBlocks > Remove multiple non-consecutive blocks 1`] = ` "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", @@ -653,11 +653,11 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -669,11 +669,11 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` "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", @@ -978,11 +978,11 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -994,11 +994,11 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1385,11 +1385,11 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1401,11 +1401,11 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1752,11 +1752,11 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single ba "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1768,11 +1768,11 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single ba "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2176,11 +2176,11 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single co "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2192,11 +2192,11 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single co "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2328,11 +2328,11 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with multi "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2344,11 +2344,11 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with multi "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", @@ -2608,11 +2608,11 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2624,11 +2624,11 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl "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", @@ -2945,11 +2945,11 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2961,11 +2961,11 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl "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", @@ -3265,11 +3265,11 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3281,11 +3281,11 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` "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", @@ -3590,11 +3590,11 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3606,11 +3606,11 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3702,11 +3702,11 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3718,11 +3718,11 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` "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", @@ -4027,11 +4027,11 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4043,11 +4043,11 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4196,11 +4196,11 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4212,11 +4212,11 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` "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", @@ -4521,11 +4521,11 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4537,11 +4537,11 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", 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 index 40b0062be..eee27c58b 100644 --- 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 @@ -62,11 +62,11 @@ exports[`Test splitBlocks > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -78,11 +78,11 @@ exports[`Test splitBlocks > Basic 1`] = ` "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", @@ -387,11 +387,11 @@ exports[`Test splitBlocks > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -403,11 +403,11 @@ exports[`Test splitBlocks > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -522,11 +522,11 @@ exports[`Test splitBlocks > Block has children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -538,11 +538,11 @@ exports[`Test splitBlocks > Block has children 1`] = ` "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", @@ -847,11 +847,11 @@ exports[`Test splitBlocks > Block has children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -863,11 +863,11 @@ exports[`Test splitBlocks > Block has children 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -965,11 +965,11 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -981,11 +981,11 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` "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", @@ -1307,11 +1307,11 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1323,11 +1323,11 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1425,11 +1425,11 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1441,11 +1441,11 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` "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", @@ -1767,11 +1767,11 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1783,11 +1783,11 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1885,11 +1885,11 @@ exports[`Test splitBlocks > Keep type 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1901,11 +1901,11 @@ exports[`Test splitBlocks > Keep type 1`] = ` "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", @@ -2228,11 +2228,11 @@ exports[`Test splitBlocks > Keep type 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2244,11 +2244,11 @@ exports[`Test splitBlocks > Keep type 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", 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 index 6a4c8d02e..918fca62e 100644 --- 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 @@ -45,11 +45,11 @@ exports[`Test updateBlock > Revert all props 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -61,11 +61,11 @@ exports[`Test updateBlock > Revert all props 1`] = ` "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", @@ -370,11 +370,11 @@ exports[`Test updateBlock > Revert all props 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -386,11 +386,11 @@ exports[`Test updateBlock > Revert all props 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -488,11 +488,11 @@ exports[`Test updateBlock > Revert single prop 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -504,11 +504,11 @@ exports[`Test updateBlock > Revert single prop 1`] = ` "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", @@ -813,11 +813,11 @@ exports[`Test updateBlock > Revert single prop 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -829,11 +829,11 @@ exports[`Test updateBlock > Revert single prop 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -931,11 +931,11 @@ exports[`Test updateBlock > Update all props 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -947,11 +947,11 @@ exports[`Test updateBlock > Update all props 1`] = ` "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", @@ -1256,11 +1256,11 @@ exports[`Test updateBlock > Update all props 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1272,11 +1272,11 @@ exports[`Test updateBlock > Update all props 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1374,11 +1374,11 @@ exports[`Test updateBlock > Update children 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1390,11 +1390,11 @@ exports[`Test updateBlock > Update children 1`] = ` "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", @@ -1813,11 +1813,11 @@ exports[`Test updateBlock > Update inline content to empty table content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1829,11 +1829,11 @@ exports[`Test updateBlock > Update inline content to empty table content 1`] = ` "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", @@ -2138,11 +2138,11 @@ exports[`Test updateBlock > Update inline content to empty table content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2154,11 +2154,11 @@ exports[`Test updateBlock > Update inline content to empty table content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2254,11 +2254,11 @@ exports[`Test updateBlock > Update inline content to no content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2270,11 +2270,11 @@ exports[`Test updateBlock > Update inline content to no content 1`] = ` "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", @@ -2579,11 +2579,11 @@ exports[`Test updateBlock > Update inline content to no content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2595,11 +2595,11 @@ exports[`Test updateBlock > Update inline content to no content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2769,11 +2769,11 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2785,11 +2785,11 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` "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", @@ -3094,11 +3094,11 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3110,11 +3110,11 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3212,11 +3212,11 @@ exports[`Test updateBlock > Update no content to empty inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3228,11 +3228,11 @@ exports[`Test updateBlock > Update no content to empty inline content 1`] = ` "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", @@ -3533,11 +3533,11 @@ exports[`Test updateBlock > Update no content to empty inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3549,11 +3549,11 @@ exports[`Test updateBlock > Update no content to empty inline content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3651,11 +3651,11 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3667,11 +3667,11 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` "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", @@ -3974,11 +3974,11 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3990,11 +3990,11 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4092,11 +4092,11 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4108,11 +4108,11 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` "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", @@ -4419,11 +4419,11 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4435,11 +4435,11 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4537,11 +4537,11 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4553,11 +4553,11 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` "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", @@ -4936,11 +4936,11 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4952,11 +4952,11 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5054,11 +5054,11 @@ exports[`Test updateBlock > Update single prop 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5070,11 +5070,11 @@ exports[`Test updateBlock > Update single prop 1`] = ` "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", @@ -5379,11 +5379,11 @@ exports[`Test updateBlock > Update single prop 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5395,11 +5395,11 @@ exports[`Test updateBlock > Update single prop 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5497,11 +5497,11 @@ exports[`Test updateBlock > Update table content to empty inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5513,11 +5513,11 @@ exports[`Test updateBlock > Update table content to empty inline content 1`] = ` "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", @@ -5744,11 +5744,11 @@ exports[`Test updateBlock > Update table content to empty inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5760,11 +5760,11 @@ exports[`Test updateBlock > Update table content to empty inline content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5862,11 +5862,11 @@ exports[`Test updateBlock > Update table content to inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5878,11 +5878,11 @@ exports[`Test updateBlock > Update table content to inline content 1`] = ` "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", @@ -6115,11 +6115,11 @@ exports[`Test updateBlock > Update table content to inline content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6131,11 +6131,11 @@ exports[`Test updateBlock > Update table content to inline content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6233,11 +6233,11 @@ exports[`Test updateBlock > Update table content to no content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6249,11 +6249,11 @@ exports[`Test updateBlock > Update table content to no content 1`] = ` "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", @@ -6484,11 +6484,11 @@ exports[`Test updateBlock > Update table content to no content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6500,11 +6500,11 @@ exports[`Test updateBlock > Update table content to no content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6602,11 +6602,11 @@ exports[`Test updateBlock > Update type 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6618,11 +6618,11 @@ exports[`Test updateBlock > Update type 1`] = ` "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", @@ -6927,11 +6927,11 @@ exports[`Test updateBlock > Update type 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6943,11 +6943,11 @@ exports[`Test updateBlock > Update type 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7044,11 +7044,11 @@ exports[`Test updateBlock > Update with plain content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7060,11 +7060,11 @@ exports[`Test updateBlock > Update with plain content 1`] = ` "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", @@ -7369,11 +7369,11 @@ exports[`Test updateBlock > Update with plain content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7385,11 +7385,11 @@ exports[`Test updateBlock > Update with plain content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7473,11 +7473,11 @@ exports[`Test updateBlock > Update with styled content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 1", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-1", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7489,11 +7489,11 @@ exports[`Test updateBlock > Update with styled content 1`] = ` "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", @@ -7798,11 +7798,11 @@ exports[`Test updateBlock > Update with styled content 1`] = ` "content": [ { "styles": {}, - "text": "Double Nested Paragraph 2", + "text": "Double Nested Paragraph 1", "type": "text", }, ], - "id": "double-nested-paragraph-2", + "id": "double-nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7814,11 +7814,11 @@ exports[`Test updateBlock > Update with styled content 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 2", + "text": "Nested Paragraph 1", "type": "text", }, ], - "id": "nested-paragraph-2", + "id": "nested-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", 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 000000000..7e506a213 --- /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 000000000..f7ed69279 --- /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 000000000..f442beda3 --- /dev/null +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -0,0 +1,132 @@ +import { Node } from "prosemirror-model"; + +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; +import { TextCursorPosition } from "../../../../editor/cursorPositionTypes.js"; +import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeConversions.js"; +import { + BlockIdentifier, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../../../schema/index.js"; +import { getNodeById } from "../../../nodeUtil"; +import { UnreachableCaseError } from "../../../../util/typescript"; + +export function getTextCursorPosition< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>(editor: BlockNoteEditor): TextCursorPosition { + const { depth, blockContainer } = getBlockInfoFromPos( + editor._tiptapEditor.state.doc, + editor._tiptapEditor.state.selection.from + )!; + + // Gets previous blockContainer node at the same nesting level, if the current node isn't the first child. + const prevNode = editor._tiptapEditor.state.doc.resolve( + blockContainer.beforePos + ).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 (depth > 1) { + parentNode = editor._tiptapEditor.state.doc + .resolve(blockContainer.beforePos) + .node(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 { posBeforeNode } = getNodeById(id, editor._tiptapEditor.state.doc); + const { blockContent } = getBlockInfoFromPos( + editor._tiptapEditor.state.doc, + posBeforeNode + )!; + + 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 index 5b95d5e2c..a1c4ceca9 100644 --- a/packages/core/src/api/blockManipulation/setupTestEnv.ts +++ b/packages/core/src/api/blockManipulation/setupTestEnv.ts @@ -42,14 +42,14 @@ const testDocument: PartialBlock[] = [ content: "Paragraph with children", children: [ { - id: "nested-paragraph-1", + id: "nested-paragraph-0", type: "paragraph", - content: "Nested Paragraph 1", + content: "Nested Paragraph 0", children: [ { - id: "double-nested-paragraph-1", + id: "double-nested-paragraph-0", type: "paragraph", - content: "Double Nested Paragraph 1", + content: "Double Nested Paragraph 0", }, ], }, @@ -149,14 +149,14 @@ const testDocument: PartialBlock[] = [ ], children: [ { - id: "nested-paragraph-2", + id: "nested-paragraph-1", type: "paragraph", - content: "Nested Paragraph 2", + content: "Nested Paragraph 1", children: [ { - id: "double-nested-paragraph-2", + id: "double-nested-paragraph-1", type: "paragraph", - content: "Double Nested Paragraph 2", + content: "Double Nested Paragraph 1", }, ], }, diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 937b0c18e..d947f70db 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -11,6 +11,10 @@ import { 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 { + getTextCursorPosition, + setTextCursorPosition, +} from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition"; import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; @@ -18,7 +22,6 @@ import { inlineContentToNodes, nodeToBlock, } from "../api/nodeConversions/nodeConversions.js"; -import { getNodeById } from "../api/nodeUtil.js"; import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js"; import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown.js"; import { @@ -663,68 +666,7 @@ export class BlockNoteEditor< ISchema, SSchema > { - const { depth, blockContainer } = getBlockInfoFromPos( - this._tiptapEditor.state.doc, - this._tiptapEditor.state.selection.from - )!; - - // Gets previous blockContainer node at the same nesting level, if the current node isn't the first child. - const prevNode = this._tiptapEditor.state.doc.resolve( - blockContainer.beforePos - ).nodeBefore; - - // Gets next blockContainer node at the same nesting level, if the current node isn't the last child. - const nextNode = this._tiptapEditor.state.doc.resolve( - blockContainer.afterPos - ).nodeAfter; - - // Gets parent blockContainer node, if the current node is nested. - let parentNode: Node | undefined = undefined; - if (depth > 1) { - parentNode = this._tiptapEditor.state.doc - .resolve(blockContainer.beforePos) - .node(depth - 1); - } - - return { - block: nodeToBlock( - blockContainer.node, - this.schema.blockSchema, - this.schema.inlineContentSchema, - this.schema.styleSchema, - this.blockCache - ), - prevBlock: - prevNode === null - ? undefined - : nodeToBlock( - prevNode, - this.schema.blockSchema, - this.schema.inlineContentSchema, - this.schema.styleSchema, - this.blockCache - ), - nextBlock: - nextNode === null - ? 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); } /** @@ -737,44 +679,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 { blockContent } = getBlockInfoFromPos( - this._tiptapEditor.state.doc, - posBeforeNode + 2 - )!; - - const contentType: "none" | "inline" | "table" = - this.schema.blockSchema[blockContent.node.type.name]!.content; - - if (contentType === "none") { - this._tiptapEditor.commands.setNodeSelection(blockContent.beforePos); - return; - } - - if (contentType === "inline") { - if (placement === "start") { - this._tiptapEditor.commands.setTextSelection( - blockContent.beforePos + 1 - ); - } else { - this._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. - this._tiptapEditor.commands.setTextSelection( - blockContent.beforePos + 4 - ); - } else { - this._tiptapEditor.commands.setTextSelection(blockContent.afterPos - 4); - } - } else { - throw new UnreachableCaseError(contentType); - } + setTextCursorPosition(this, targetBlock, placement); } /** From ab18dd42d4ff8c4f1e320366a1c46284151fc75a Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Sat, 12 Oct 2024 18:32:21 +0200 Subject: [PATCH 17/34] Fixed lint issue --- .../selections/textCursorPosition/textCursorPosition.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts index f442beda3..ec6ec1c78 100644 --- a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -2,16 +2,16 @@ import { Node } from "prosemirror-model"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { TextCursorPosition } from "../../../../editor/cursorPositionTypes.js"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; -import { nodeToBlock } from "../../../nodeConversions/nodeConversions.js"; import { BlockIdentifier, BlockSchema, InlineContentSchema, StyleSchema, } from "../../../../schema/index.js"; -import { getNodeById } from "../../../nodeUtil"; -import { UnreachableCaseError } from "../../../../util/typescript"; +import { UnreachableCaseError } from "../../../../util/typescript.js"; +import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeConversions.js"; +import { getNodeById } from "../../../nodeUtil.js"; export function getTextCursorPosition< BSchema extends BlockSchema, From 968b6fc12a442d4c745ea1f2611c90bf7156c6d0 Mon Sep 17 00:00:00 2001 From: yousefed Date: Sun, 13 Oct 2024 20:53:11 +0200 Subject: [PATCH 18/34] fix lint --- packages/core/src/editor/BlockNoteEditor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index d947f70db..9d7e0683b 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -2,7 +2,6 @@ import { EditorOptions, Extension, getSchema } from "@tiptap/core"; import { Node, Schema } from "prosemirror-model"; // import "./blocknote.css"; import * as Y from "yjs"; -import { insertContentAt } from "../api/blockManipulation/insertContentAt.js"; import { insertBlocks } from "../api/blockManipulation/commands/insertBlocks/insertBlocks.js"; import { moveBlockDown, @@ -11,10 +10,11 @@ import { 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"; +} 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"; From 89a86fb2e6cbe78685ae3d09cf97c40eed8657e5 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Mon, 14 Oct 2024 10:20:59 +0200 Subject: [PATCH 19/34] Fixed `splitBlock` selection --- .../commands/splitBlock/splitBlock.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index fb00b4112..ed754519a 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -1,4 +1,4 @@ -import { EditorState } from "prosemirror-state"; +import { EditorState, TextSelection } from "prosemirror-state"; import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; @@ -35,6 +35,14 @@ export const splitBlockCommand = ( if (dispatch) { // Insert new block state.tr.insert(blockContainer.afterPos, newBlock); + // Update selection + const newBlockInfo = getBlockInfoFromPos( + state.doc, + blockContainer.afterPos + ); + state.tr.setSelection( + TextSelection.create(state.doc, newBlockInfo.blockContent.beforePos + 1) + ); // Delete original block's children, if they exist if (blockGroup) { state.tr.delete(blockGroup.beforePos, blockGroup.afterPos); From ba11dd2bef9dbefd971ceda9d99b0fe222e4ccba Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Mon, 14 Oct 2024 10:42:56 +0200 Subject: [PATCH 20/34] Small fix --- .../src/api/blockManipulation/commands/splitBlock/splitBlock.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index ed754519a..aa153ff0f 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -37,7 +37,7 @@ export const splitBlockCommand = ( state.tr.insert(blockContainer.afterPos, newBlock); // Update selection const newBlockInfo = getBlockInfoFromPos( - state.doc, + state.tr.doc, blockContainer.afterPos ); state.tr.setSelection( From dd572c3ef9ffccbb6816a22bce7b48fef9d59dcb Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Mon, 14 Oct 2024 12:10:13 +0200 Subject: [PATCH 21/34] Added unit tests to check selection setting --- .../commands/mergeBlocks/mergeBlocks.test.ts | 53 +- .../__snapshots__/splitBlock.test.ts.snap | 454 ++++++++++++++++++ .../commands/splitBlock/splitBlock.test.ts | 26 + .../commands/updateBlock/updateBlock.ts | 2 + 4 files changed, 517 insertions(+), 18 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 8f1bb7445..0cdb74155 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -13,75 +13,92 @@ function mergeBlocks(posBetweenBlocks: number) { ); } -function getPosAfterSelectedBlock() { +function getPosBeforeSelectedBlock() { return getBlockInfoFromPos( getEditor()._tiptapEditor.state.doc, getEditor()._tiptapEditor.state.selection.from - ).blockContainer.afterPos; + ).blockContainer.beforePos; } describe("Test mergeBlocks", () => { it("Basic", () => { - getEditor().setTextCursorPosition("paragraph-0"); + getEditor().setTextCursorPosition("paragraph-1"); - mergeBlocks(getPosAfterSelectedBlock()); + mergeBlocks(getPosBeforeSelectedBlock()); expect(getEditor().document).toMatchSnapshot(); }); it("First block has children", () => { - getEditor().setTextCursorPosition("paragraph-with-children"); + getEditor().setTextCursorPosition("paragraph-2"); - mergeBlocks(getPosAfterSelectedBlock()); + mergeBlocks(getPosBeforeSelectedBlock()); expect(getEditor().document).toMatchSnapshot(); }); it("Second block has children", () => { - getEditor().setTextCursorPosition("paragraph-1"); + getEditor().setTextCursorPosition("paragraph-with-children"); - mergeBlocks(getPosAfterSelectedBlock()); + mergeBlocks(getPosBeforeSelectedBlock()); expect(getEditor().document).toMatchSnapshot(); }); it("Blocks have different types", () => { - getEditor().setTextCursorPosition("heading-0"); + getEditor().setTextCursorPosition("paragraph-5"); - mergeBlocks(getPosAfterSelectedBlock()); + mergeBlocks(getPosBeforeSelectedBlock()); expect(getEditor().document).toMatchSnapshot(); }); it("Inline content & no content", () => { - getEditor().setTextCursorPosition("paragraph-5"); + getEditor().setTextCursorPosition("image-0"); - mergeBlocks(getPosAfterSelectedBlock()); + mergeBlocks(getPosBeforeSelectedBlock()); expect(getEditor().document).toMatchSnapshot(); }); it.skip("Inline content & table content", () => { - getEditor().setTextCursorPosition("paragraph-6"); + getEditor().setTextCursorPosition("table-0"); - mergeBlocks(getPosAfterSelectedBlock()); + mergeBlocks(getPosBeforeSelectedBlock()); expect(getEditor().document).toMatchSnapshot(); }); it("No content & inline content", () => { - getEditor().setTextCursorPosition("image-0"); + getEditor().setTextCursorPosition("paragraph-6"); - mergeBlocks(getPosAfterSelectedBlock()); + mergeBlocks(getPosBeforeSelectedBlock()); expect(getEditor().document).toMatchSnapshot(); }); it.skip("Table content & inline content", () => { - getEditor().setTextCursorPosition("table-0"); + getEditor().setTextCursorPosition("paragraph-7"); - mergeBlocks(getPosAfterSelectedBlock()); + mergeBlocks(getPosBeforeSelectedBlock()); expect(getEditor().document).toMatchSnapshot(); }); + + it("Selection is set", () => { + 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(); + }); }); 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 index eee27c58b..fb9f0269e 100644 --- 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 @@ -1840,6 +1840,460 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` ] `; +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": [ + { + "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`] = ` [ { diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts index 659f7f6bf..34edd2fcc 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; +import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { splitBlockCommand } from "./splitBlock.js"; @@ -29,6 +30,14 @@ describe("Test splitBlocks", () => { expect(getEditor().document).toMatchSnapshot(); }); + it("End of content", () => { + getEditor().setTextCursorPosition("paragraph-0"); + + splitBlock(getSelectionAnchorPosWithOffset(11)); + + expect(getEditor().document).toMatchSnapshot(); + }); + it("Block has children", () => { getEditor().setTextCursorPosition("paragraph-with-children"); @@ -68,4 +77,21 @@ describe("Test splitBlocks", () => { expect(getEditor().document).toMatchSnapshot(); }); + + it("Selection is set", () => { + getEditor().setTextCursorPosition("paragraph-0"); + + splitBlock(getSelectionAnchorPosWithOffset(4)); + + const { blockContainer } = getBlockInfoFromPos( + getEditor()._tiptapEditor.state.doc, + getEditor()._tiptapEditor.state.selection.anchor + ); + + 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/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 37888809b..782bd6769 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -157,6 +157,8 @@ export const updateBlockCommand = content ) ) + // TODO: This seems off - the selection is not necessarily in the block + // being updated but this will set it anyway. // 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. From 74a0cda8a5764be797b1136dbb80ba22d4039228 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 14 Oct 2024 12:30:01 +0200 Subject: [PATCH 22/34] simplify splitblocks --- .../commands/splitBlock/splitBlock.ts | 47 ++++++------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index aa153ff0f..ee1994978 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -1,4 +1,4 @@ -import { EditorState, TextSelection } from "prosemirror-state"; +import { EditorState } from "prosemirror-state"; import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; @@ -14,43 +14,26 @@ export const splitBlockCommand = ( state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const { blockContainer, blockContent, blockGroup } = getBlockInfoFromPos( + const { blockContainer, blockContent } = getBlockInfoFromPos( state.doc, posInBlock ); - const newBlock = state.schema.nodes["blockContainer"].createAndFill( - keepProps ? { ...blockContainer.node.attrs, id: undefined } : null, - [ - state.schema.nodes[ - keepType ? blockContent.node.type.name : "paragraph" - ].createAndFill( - keepProps ? blockContent.node.attrs : null, - blockContent.node.content.cut(posInBlock - blockContent.beforePos - 1) - )!, - ...(blockGroup ? [blockGroup.node] : []), - ] - )!; + 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) { - // Insert new block - state.tr.insert(blockContainer.afterPos, newBlock); - // Update selection - const newBlockInfo = getBlockInfoFromPos( - state.tr.doc, - blockContainer.afterPos - ); - state.tr.setSelection( - TextSelection.create(state.doc, newBlockInfo.blockContent.beforePos + 1) - ); - // Delete original block's children, if they exist - if (blockGroup) { - state.tr.delete(blockGroup.beforePos, blockGroup.afterPos); - } - // Delete original block's content past the cursor - state.tr.delete(posInBlock, blockContent.afterPos - 1); - - // state.tr.scrollIntoView(); + state.tr.split(posInBlock, 2, types); } return true; From f57b0eb65497daaa8b78c04e2a1b377a36d5e1c6 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Mon, 14 Oct 2024 12:46:30 +0200 Subject: [PATCH 23/34] Fixed selection in `splitBlock` tests --- .../commands/splitBlock/splitBlock.test.ts | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts index 34edd2fcc..bbe9972ec 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -1,8 +1,11 @@ +import { Node } from "prosemirror-model"; import { describe, expect, it } from "vitest"; import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { getNodeById } from "../../../nodeUtil.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { splitBlockCommand } from "./splitBlock.js"; +import { TextSelection } from "prosemirror-state"; const getEditor = setupTestEnv(); @@ -17,71 +20,106 @@ function splitBlock( ); } -function getSelectionAnchorPosWithOffset(offset: number) { - return getEditor()._tiptapEditor.state.selection.anchor + offset; +function setSelectionWithOffset( + doc: Node, + targetBlockId: string, + offset: number +) { + const { posBeforeNode } = getNodeById(targetBlockId, doc); + const { blockContent } = getBlockInfoFromPos(doc, posBeforeNode); + + getEditor()._tiptapEditor.view.dispatch( + getEditor()._tiptapEditor.state.tr.setSelection( + TextSelection.create(doc, blockContent.beforePos + offset + 1) + ) + ); } describe("Test splitBlocks", () => { it("Basic", () => { - getEditor().setTextCursorPosition("paragraph-0"); + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-0", + 4 + ); - splitBlock(getSelectionAnchorPosWithOffset(4)); + splitBlock(getEditor()._tiptapEditor.state.selection.anchor); expect(getEditor().document).toMatchSnapshot(); }); it("End of content", () => { - getEditor().setTextCursorPosition("paragraph-0"); + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-0", + 11 + ); - splitBlock(getSelectionAnchorPosWithOffset(11)); + splitBlock(getEditor()._tiptapEditor.state.selection.anchor); expect(getEditor().document).toMatchSnapshot(); }); it("Block has children", () => { - getEditor().setTextCursorPosition("paragraph-with-children"); + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-with-children", + 4 + ); - splitBlock(getSelectionAnchorPosWithOffset(4)); + splitBlock(getEditor()._tiptapEditor.state.selection.anchor); expect(getEditor().document).toMatchSnapshot(); }); it("Keep type", () => { - getEditor().setTextCursorPosition("heading-0"); + setSelectionWithOffset(getEditor()._tiptapEditor.state.doc, "heading-0", 4); - splitBlock(getSelectionAnchorPosWithOffset(4), true); + splitBlock(getEditor()._tiptapEditor.state.selection.anchor, true); expect(getEditor().document).toMatchSnapshot(); }); it("Don't keep type", () => { - getEditor().setTextCursorPosition("heading-0"); + setSelectionWithOffset(getEditor()._tiptapEditor.state.doc, "heading-0", 4); - splitBlock(getSelectionAnchorPosWithOffset(4)); + splitBlock(getEditor()._tiptapEditor.state.selection.anchor, false); expect(getEditor().document).toMatchSnapshot(); }); it.skip("Keep props", () => { - getEditor().setTextCursorPosition("paragraph-with-props"); + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-with-props", + 4 + ); - splitBlock(getSelectionAnchorPosWithOffset(4), false, true); + splitBlock(getEditor()._tiptapEditor.state.selection.anchor, false, true); expect(getEditor().document).toMatchSnapshot(); }); it("Don't keep props", () => { - getEditor().setTextCursorPosition("paragraph-with-props"); + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-with-props", + 4 + ); - splitBlock(getSelectionAnchorPosWithOffset(4)); + splitBlock(getEditor()._tiptapEditor.state.selection.anchor, false, false); expect(getEditor().document).toMatchSnapshot(); }); it("Selection is set", () => { - getEditor().setTextCursorPosition("paragraph-0"); + setSelectionWithOffset( + getEditor()._tiptapEditor.state.doc, + "paragraph-0", + 4 + ); - splitBlock(getSelectionAnchorPosWithOffset(4)); + splitBlock(getEditor()._tiptapEditor.state.selection.anchor); const { blockContainer } = getBlockInfoFromPos( getEditor()._tiptapEditor.state.doc, From 1236476dd93d627c3c49a708831b5d56067b0ca2 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 14 Oct 2024 14:09:35 +0200 Subject: [PATCH 24/34] wip: deprecate getBlockInfoFromPos --- .../__snapshots__/mergeBlocks.test.ts.snap | 2 +- .../commands/mergeBlocks/mergeBlocks.test.ts | 4 +- .../commands/mergeBlocks/mergeBlocks.ts | 75 ++++++++++--------- .../commands/moveBlock/moveBlock.test.ts | 6 +- .../commands/moveBlock/moveBlock.ts | 4 +- .../commands/splitBlock/splitBlock.test.ts | 8 +- .../commands/splitBlock/splitBlock.ts | 12 ++- .../commands/updateBlock/updateBlock.ts | 18 ++--- .../textCursorPosition/textCursorPosition.ts | 6 +- .../fromClipboard/handleFileInsertion.ts | 4 +- packages/core/src/api/getBlockInfoFromPos.ts | 11 ++- .../HeadingBlockContent.ts | 56 ++++++-------- .../BulletListItemBlockContent.ts | 32 ++++---- .../CheckListItemBlockContent.ts | 71 ++++++++++-------- .../ListItemKeyboardShortcuts.ts | 11 ++- .../NumberedListIndexingPlugin.ts | 6 +- .../NumberedListItemBlockContent.ts | 32 ++++---- .../ParagraphBlockContent.ts | 12 +-- .../core/src/editor/BlockNoteEditor.test.ts | 4 +- packages/core/src/editor/BlockNoteEditor.ts | 6 +- .../KeyboardShortcutsExtension.ts | 47 ++++++------ 21 files changed, 215 insertions(+), 212 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index 781150a14..0ff7cbf67 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -1938,7 +1938,7 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` "type": "text", }, ], - "id": "image-0", + "id": "paragraph-6", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 0cdb74155..a66732e6d 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { mergeBlocksCommand } from "./mergeBlocks.js"; @@ -14,7 +14,7 @@ function mergeBlocks(posBetweenBlocks: number) { } function getPosBeforeSelectedBlock() { - return getBlockInfoFromPos( + return getBlockInfoFromPos_DEPRECATED( getEditor()._tiptapEditor.state.doc, getEditor()._tiptapEditor.state.selection.from ).blockContainer.beforePos; diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index c84b8f06b..aa9b1ed09 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -1,7 +1,6 @@ -import { Slice } from "prosemirror-model"; -import { EditorState, TextSelection } from "prosemirror-state"; +import { EditorState } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfo } from "../../../getBlockInfoFromPos.js"; export const mergeBlocksCommand = (posBetweenBlocks: number) => @@ -12,18 +11,17 @@ export const mergeBlocksCommand = state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const nextNodeIsBlock = - state.doc.resolve(posBetweenBlocks + 1).node().type.name === - "blockContainer"; - const prevNodeIsBlock = - state.doc.resolve(posBetweenBlocks - 1).node().type.name === - "blockContainer"; + const $pos = state.doc.resolve(posBetweenBlocks); + const nextNodeIsBlock = $pos.nodeAfter?.type.name === "blockContainer"; + const prevNodeIsBlock = $pos.nodeBefore?.type.name === "blockContainer"; if (!nextNodeIsBlock || !prevNodeIsBlock) { - return false; + throw new Error( + "invalid `posBetweenBlocks` passed to mergeBlocksCommand" + ); } - const nextBlockInfo = getBlockInfoFromPos(state.doc, posBetweenBlocks); + const nextBlockInfo = getBlockInfo($pos.nodeAfter, posBetweenBlocks); // Removes a level of nesting all children of the next block by 1 level, if it contains both content and block // group nodes. @@ -38,42 +36,51 @@ export const mergeBlocksCommand = // 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!, nextBlockInfo.depth); + state.tr.lift(childBlocksRange!, $pos.depth); } } - let prevBlockEndPos = posBetweenBlocks - 1; - let prevBlockInfo = getBlockInfoFromPos(state.doc, prevBlockEndPos); - + // TODO: extract helper? // Finds the nearest previous block, regardless of nesting level. + let prevBlockStartPos = $pos.posAtIndex($pos.index() - 1); + let prevBlockInfo = getBlockInfo( + state.doc.resolve(prevBlockStartPos).nodeAfter!, + prevBlockStartPos + ); while (prevBlockInfo.blockGroup) { - prevBlockEndPos--; - prevBlockInfo = getBlockInfoFromPos(state.doc, prevBlockEndPos); - if (prevBlockInfo === undefined) { - return false; - } + const group = prevBlockInfo.blockGroup.node; + + prevBlockStartPos = state.doc + .resolve(prevBlockInfo.blockGroup.beforePos + 1) + .posAtIndex(group.childCount - 1); + prevBlockInfo = getBlockInfo( + state.doc.resolve(prevBlockStartPos).nodeAfter!, + prevBlockStartPos + ); } + console.log( + prevBlockInfo.blockContent.afterPos, + nextBlockInfo.blockContent.beforePos + ); + debugger; // Deletes next block and adds its text content to the nearest previous block. - if (dispatch) { dispatch( - state.tr - .deleteRange( - nextBlockInfo.blockContent.beforePos, - nextBlockInfo.blockContent.afterPos - ) - .replace( - prevBlockEndPos - 1, - nextBlockInfo.blockContent.beforePos, - new Slice(nextBlockInfo.blockContent.node.content, 0, 0) - ) + state.tr.deleteRange( + prevBlockInfo.blockContent.afterPos - 1, + nextBlockInfo.blockContent.beforePos + 1 + ) + // .scrollIntoView() ); - state.tr.setSelection( - new TextSelection(state.doc.resolve(prevBlockEndPos - 1)) - ); + // TODO: fix unit test + think of missing tests + // TODO: reenable set selection + + // state.tr.setSelection( + // new TextSelection(state.doc.resolve(prevBlockEndPos - 1)) + // ); } return true; diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts index 92988bc2f..60d1f8510 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts @@ -2,18 +2,18 @@ import { NodeSelection, TextSelection } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; import { describe, expect, it } from "vitest"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; +import { setupTestEnv } from "../../setupTestEnv.js"; import { moveBlockDown, moveBlockUp, moveSelectedBlockAndSelection, } from "./moveBlock.js"; -import { setupTestEnv } from "../../setupTestEnv.js"; const getEditor = setupTestEnv(); function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { - const { blockContent } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos_DEPRECATED( getEditor()._tiptapEditor.state.doc, getEditor()._tiptapEditor.state.selection.from ); diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts index c58eecb8a..888b2b1ef 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts @@ -3,7 +3,7 @@ import { CellSelection } from "prosemirror-tables"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; import { BlockIdentifier } from "../../../../schema/index.js"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; import { getNodeById } from "../../../nodeUtil.js"; type BlockSelectionData = ( @@ -31,7 +31,7 @@ type BlockSelectionData = ( function getBlockSelectionData( editor: BlockNoteEditor ): BlockSelectionData { - const { blockContainer } = getBlockInfoFromPos( + const { blockContainer } = getBlockInfoFromPos_DEPRECATED( editor._tiptapEditor.state.doc, editor._tiptapEditor.state.selection.from ); diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts index bbe9972ec..63389a7b6 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -1,11 +1,11 @@ import { Node } from "prosemirror-model"; import { describe, expect, it } from "vitest"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { TextSelection } from "prosemirror-state"; +import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; import { getNodeById } from "../../../nodeUtil.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { splitBlockCommand } from "./splitBlock.js"; -import { TextSelection } from "prosemirror-state"; const getEditor = setupTestEnv(); @@ -26,7 +26,7 @@ function setSelectionWithOffset( offset: number ) { const { posBeforeNode } = getNodeById(targetBlockId, doc); - const { blockContent } = getBlockInfoFromPos(doc, posBeforeNode); + const { blockContent } = getBlockInfoFromPos_DEPRECATED(doc, posBeforeNode); getEditor()._tiptapEditor.view.dispatch( getEditor()._tiptapEditor.state.tr.setSelection( @@ -121,7 +121,7 @@ describe("Test splitBlocks", () => { splitBlock(getEditor()._tiptapEditor.state.selection.anchor); - const { blockContainer } = getBlockInfoFromPos( + const { blockContainer } = getBlockInfoFromPos_DEPRECATED( getEditor()._tiptapEditor.state.doc, getEditor()._tiptapEditor.state.selection.anchor ); diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index ee1994978..9f427f118 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -1,6 +1,9 @@ import { EditorState } from "prosemirror-state"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { + getBlockInfo, + getNearestBlockContainerPos, +} from "../../../getBlockInfoFromPos.js"; export const splitBlockCommand = ( posInBlock: number, @@ -14,11 +17,16 @@ export const splitBlockCommand = ( state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const { blockContainer, blockContent } = getBlockInfoFromPos( + const nearestBlockContainerPos = getNearestBlockContainerPos( state.doc, posInBlock ); + const { blockContainer, blockContent } = getBlockInfo( + state.doc, + nearestBlockContainerPos + ); + const types = [ { type: blockContainer.node.type, // always keep blockcontainer type diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 782bd6769..7967f9970 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -10,7 +10,7 @@ import { import { InlineContentSchema } from "../../../../schema/inlineContent/types.js"; import { StyleSchema } from "../../../../schema/styles/types.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfo } from "../../../getBlockInfoFromPos.js"; import { blockToNode, inlineContentToNodes, @@ -26,7 +26,7 @@ export const updateBlockCommand = S extends StyleSchema >( editor: BlockNoteEditor, - posInBlock: number, + posBeforeBlock: number, block: PartialBlock ) => ({ @@ -36,12 +36,10 @@ export const updateBlockCommand = state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const blockInfo = getBlockInfoFromPos(state.doc, posInBlock); - if (blockInfo === undefined) { - return false; - } - - const { blockContainer, blockContent, blockGroup } = blockInfo; + const { blockContainer, blockContent, blockGroup } = getBlockInfo( + state.doc, + posBeforeBlock + ); if (dispatch) { // Adds blockGroup node with child blocks if necessary. @@ -203,12 +201,12 @@ export function updateBlock< const { posBeforeNode } = getNodeById(id, ttEditor.state.doc); ttEditor.commands.command(({ state, dispatch }) => { - updateBlockCommand(editor, posBeforeNode + 1, update)({ state, dispatch }); + updateBlockCommand(editor, posBeforeNode, update)({ state, dispatch }); return true; }); const blockContainerNode = ttEditor.state.doc - .resolve(posBeforeNode + 1) + .resolve(posBeforeNode + 1) // TODO: clean? .node(); return nodeToBlock( diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts index ec6ec1c78..93ed32a6e 100644 --- a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -9,7 +9,7 @@ import { StyleSchema, } from "../../../../schema/index.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; -import { getBlockInfoFromPos } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; import { nodeToBlock } from "../../../nodeConversions/nodeConversions.js"; import { getNodeById } from "../../../nodeUtil.js"; @@ -18,7 +18,7 @@ export function getTextCursorPosition< I extends InlineContentSchema, S extends StyleSchema >(editor: BlockNoteEditor): TextCursorPosition { - const { depth, blockContainer } = getBlockInfoFromPos( + const { depth, blockContainer } = getBlockInfoFromPos_DEPRECATED( editor._tiptapEditor.state.doc, editor._tiptapEditor.state.selection.from )!; @@ -94,7 +94,7 @@ export function setTextCursorPosition< const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id; const { posBeforeNode } = getNodeById(id, editor._tiptapEditor.state.doc); - const { blockContent } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos_DEPRECATED( editor._tiptapEditor.state.doc, posBeforeNode )!; diff --git a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts index 4fd86efe2..ce1442c13 100644 --- a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts +++ b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts @@ -6,7 +6,7 @@ import { InlineContentSchema, StyleSchema, } from "../../../schema/index.js"; -import { getBlockInfoFromPos } from "../../getBlockInfoFromPos.js"; +import { getBlockInfoFromPos_DEPRECATED } from "../../getBlockInfoFromPos.js"; import { acceptedMIMETypes } from "./acceptedMIMETypes.js"; function checkFileExtensionsMatch( @@ -132,7 +132,7 @@ export async function handleFileInsertion< return; } - const blockInfo = getBlockInfoFromPos( + const blockInfo = getBlockInfoFromPos_DEPRECATED( editor._tiptapEditor.state.doc, pos.pos ); diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index 72cfe03ef..2a5b8526d 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -1,4 +1,5 @@ import { Node } from "prosemirror-model"; +import { EditorState } from "prosemirror-state"; type SingleBlockInfo = { node: Node; @@ -148,7 +149,10 @@ export function getBlockInfo( * @param pos An integer position. * @returns A BlockInfo object for the nearest blockContainer node. */ -export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo { +export function getBlockInfoFromPos_DEPRECATED( + doc: Node, + pos: number +): BlockInfo { const $pos = doc.resolve(getNearestBlockContainerPos(doc, pos)); const depth = $pos.depth; @@ -166,3 +170,8 @@ export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo { ...getBlockInfo(node, beforePos), }; } + +export function getBlockInfoFromSelection(state: EditorState) { + const pos = getNearestBlockContainerPos(state.doc, state.selection.anchor); + return getBlockInfoFromPos_DEPRECATED(state.doc, pos); +} diff --git a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index a501c4647..96eb8a004 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -1,6 +1,6 @@ import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -47,23 +47,23 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ return new InputRule({ find: new RegExp(`^(#{${level}})\\s$`), handler: ({ state, chain, range }) => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() .command( - updateBlockCommand(this.options.editor, state.selection.from, { - type: "heading", - props: { - level: level as any, - }, - }) + 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 }) @@ -77,12 +77,8 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { "Mod-Alt-1": () => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } @@ -90,7 +86,7 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ return this.editor.commands.command( updateBlockCommand( this.options.editor, - this.editor.state.selection.anchor, + blockInfo.blockContainer.beforePos, { type: "heading", props: { @@ -101,19 +97,15 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ ); }, "Mod-Alt-2": () => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } return this.editor.commands.command( updateBlockCommand( this.options.editor, - this.editor.state.selection.anchor, + blockInfo.blockContainer.beforePos, { type: "heading", props: { @@ -124,19 +116,15 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ ); }, "Mod-Alt-3": () => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } return this.editor.commands.command( updateBlockCommand( this.options.editor, - this.editor.state.selection.anchor, + blockInfo.blockContainer.beforePos, { type: "heading", props: { diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts index 9cd518b28..3f19c403a 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -1,6 +1,6 @@ import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getBlockInfoFromPos } from "../../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -27,21 +27,21 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`^[-+*]\\s$`), handler: ({ state, chain, range }) => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() .command( - updateBlockCommand(this.options.editor, state.selection.from, { - type: "bulletListItem", - props: {}, - }) + 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 }); @@ -54,19 +54,15 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ return { Enter: () => handleEnter(this.options.editor), "Mod-Shift-8": () => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } return this.options.editor.commands.command( updateBlockCommand( this.options.editor, - this.options.editor.state.selection.anchor, + 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 f221574bc..6723997fd 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -1,6 +1,9 @@ import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getBlockInfoFromPos } from "../../../api/getBlockInfoFromPos.js"; +import { + getBlockInfoFromSelection, + getNearestBlockContainerPos, +} from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -45,23 +48,23 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`\\[\\s*\\]\\s$`), handler: ({ state, chain, range }) => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() .command( - updateBlockCommand(this.options.editor, state.selection.from, { - type: "checkListItem", - props: { - checked: false as any, - }, - }) + 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 }); @@ -70,23 +73,24 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`\\[[Xx]\\]\\s$`), handler: ({ state, chain, range }) => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(state); + + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() .command( - updateBlockCommand(this.options.editor, state.selection.from, { - type: "checkListItem", - props: { - checked: true as any, - }, - }) + 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 }); @@ -99,19 +103,15 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ return { Enter: () => handleEnter(this.options.editor), "Mod-Shift-9": () => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(this.options.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } return this.editor.commands.command( updateBlockCommand( this.options.editor, - this.editor.state.selection.anchor, + blockInfo.blockContainer.beforePos, { type: "checkListItem", props: {}, @@ -234,9 +234,14 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ return; } + // TODO: test if (typeof getPos !== "boolean") { + const beforeBlockContainerPos = getNearestBlockContainerPos( + editor.state.doc, + getPos() + ); this.editor.commands.command( - updateBlockCommand(this.options.editor, getPos(), { + updateBlockCommand(this.options.editor, beforeBlockContainerPos, { type: "checkListItem", props: { checked: checkbox.checked as any, diff --git a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts index 48201b68b..a9ffa5d68 100644 --- a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +++ b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts @@ -1,14 +1,13 @@ import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock/splitBlock.js"; import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; export const handleEnter = (editor: BlockNoteEditor) => { const ttEditor = editor._tiptapEditor; - const { blockContent } = getBlockInfoFromPos( - ttEditor.state.doc, - ttEditor.state.selection.from - )!; + const { blockContent, blockContainer } = getBlockInfoFromSelection( + ttEditor.state + ); const selectionEmpty = ttEditor.state.selection.anchor === ttEditor.state.selection.head; @@ -30,7 +29,7 @@ export const handleEnter = (editor: BlockNoteEditor) => { commands.command(() => { if (blockContent.node.childCount === 0) { return commands.command( - updateBlockCommand(editor, state.selection.from, { + updateBlockCommand(editor, blockContainer.beforePos, { type: "paragraph", props: {}, }) diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts index fec5350bf..9d19f3486 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 { getBlockInfoFromPos_DEPRECATED } 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`); @@ -23,7 +23,7 @@ export const NumberedListIndexingPlugin = () => { let newIndex = "1"; const isFirstBlockInDoc = pos === 1; - const blockInfo = getBlockInfoFromPos(tr.doc, pos)!; + const blockInfo = getBlockInfoFromPos_DEPRECATED(tr.doc, pos)!; // 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. @@ -31,7 +31,7 @@ export const NumberedListIndexingPlugin = () => { const prevBlock = tr.doc.resolve( blockInfo.blockContainer.beforePos - 1 ).nodeBefore!; - const prevBlockInfo = getBlockInfoFromPos( + const prevBlockInfo = getBlockInfoFromPos_DEPRECATED( tr.doc, blockInfo.blockContainer.beforePos - prevBlock.nodeSize )!; diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts index 65561da5f..8c4dc5b16 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -1,6 +1,6 @@ import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getBlockInfoFromPos } from "../../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, @@ -40,21 +40,21 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ new InputRule({ find: new RegExp(`^1\\.\\s$`), handler: ({ state, chain, range }) => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return; } chain() .command( - updateBlockCommand(this.options.editor, state.selection.from, { - type: "numberedListItem", - props: {}, - }) + 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 }); @@ -67,19 +67,15 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ return { Enter: () => handleEnter(this.options.editor), "Mod-Shift-7": () => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } return this.editor.commands.command( updateBlockCommand( this.options.editor, - this.editor.state.selection.anchor, + 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 5d496e30c..a089cb325 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -1,5 +1,5 @@ import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; import { createBlockSpecFromStronglyTypedTiptapNode, createStronglyTypedTiptapNode, @@ -19,19 +19,15 @@ export const ParagraphBlockContent = createStronglyTypedTiptapNode({ addKeyboardShortcuts() { return { "Mod-Alt-0": () => { - if ( - getBlockInfoFromPos( - this.editor.state.doc, - this.editor.state.selection.anchor - ).blockContent.node.type.spec.content !== "inline*" - ) { + const blockInfo = getBlockInfoFromSelection(this.editor.state); + if (blockInfo.blockContent.node.type.spec.content !== "inline*") { return true; } return this.editor.commands.command( updateBlockCommand( this.options.editor, - this.editor.state.selection.anchor, + blockInfo.blockContainer.beforePos, { type: "paragraph", props: {}, diff --git a/packages/core/src/editor/BlockNoteEditor.test.ts b/packages/core/src/editor/BlockNoteEditor.test.ts index 6e1e056af..5336012eb 100644 --- a/packages/core/src/editor/BlockNoteEditor.test.ts +++ b/packages/core/src/editor/BlockNoteEditor.test.ts @@ -1,5 +1,5 @@ import { expect, it } from "vitest"; -import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromPos_DEPRECATED } from "../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "./BlockNoteEditor.js"; /** @@ -7,7 +7,7 @@ import { BlockNoteEditor } from "./BlockNoteEditor.js"; */ it("creates an editor", () => { const editor = BlockNoteEditor.create(); - const { blockContent } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos_DEPRECATED( editor._tiptapEditor.state.doc, 2 ); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 9d7e0683b..31cd0a8eb 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -17,7 +17,7 @@ import { } 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 { getBlockInfoFromPos_DEPRECATED } from "../api/getBlockInfoFromPos.js"; import { inlineContentToNodes, nodeToBlock, @@ -965,7 +965,7 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor can be nested. */ public canNestBlock() { - const { blockContainer } = getBlockInfoFromPos( + const { blockContainer } = getBlockInfoFromPos_DEPRECATED( this._tiptapEditor.state.doc, this._tiptapEditor.state.selection.from )!; @@ -987,7 +987,7 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor is nested. */ public canUnnestBlock() { - const { depth } = getBlockInfoFromPos( + const { depth } = getBlockInfoFromPos_DEPRECATED( this._tiptapEditor.state.doc, this._tiptapEditor.state.selection.from )!; diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 823edc838..582bb7e8b 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -4,7 +4,10 @@ import { TextSelection } from "prosemirror-state"; import { 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 { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos.js"; +import { + getBlockInfoFromPos_DEPRECATED, + getBlockInfoFromSelection, +} from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; export const KeyboardShortcutsExtension = Extension.create<{ @@ -23,21 +26,23 @@ export const KeyboardShortcutsExtension = Extension.create<{ // Reverts block content type to a paragraph if the selection is at the start of the block. () => commands.command(({ state }) => { - const { blockContent } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; + const blockInfo = getBlockInfoFromSelection(state); const selectionAtBlockStart = - state.selection.from === blockContent.beforePos + 1; - const isParagraph = blockContent.node.type.name === "paragraph"; + 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, state.selection.from, { - type: "paragraph", - props: {}, - }) + updateBlockCommand( + this.options.editor, + blockInfo.blockContainer.beforePos, + { + type: "paragraph", + props: {}, + } + ) ); } @@ -46,7 +51,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // 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 } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos_DEPRECATED( state.doc, state.selection.from )!; @@ -64,10 +69,8 @@ export const KeyboardShortcutsExtension = Extension.create<{ // is at the start of the block. () => commands.command(({ state }) => { - const { depth, blockContainer, blockContent } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; + const { depth, blockContainer, blockContent } = + getBlockInfoFromPos_DEPRECATED(state.doc, state.selection.from)!; const selectionAtBlockStart = state.selection.from === blockContent.beforePos + 1; @@ -100,7 +103,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ commands.command(({ state }) => { // TODO: Change this to not rely on offsets & schema assumptions const { depth, blockContainer, blockContent, blockGroup } = - getBlockInfoFromPos(state.doc, state.selection.from)!; + getBlockInfoFromPos_DEPRECATED(state.doc, state.selection.from)!; const blockAtDocEnd = blockContainer.afterPos === state.doc.nodeSize - 3; @@ -138,7 +141,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // of the block. () => commands.command(({ state }) => { - const { depth, blockContent } = getBlockInfoFromPos( + const { depth, blockContent } = getBlockInfoFromPos_DEPRECATED( state.doc, state.selection.from )!; @@ -165,10 +168,8 @@ export const KeyboardShortcutsExtension = Extension.create<{ // empty & at the start of the block. () => commands.command(({ state, dispatch }) => { - const { blockContainer, blockContent } = getBlockInfoFromPos( - state.doc, - state.selection.from - )!; + const { blockContainer, blockContent } = + getBlockInfoFromPos_DEPRECATED(state.doc, state.selection.from)!; const selectionAtBlockStart = state.selection.$anchor.parentOffset === 0; @@ -201,7 +202,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // deletes the selection beforehand, if it's not empty. () => commands.command(({ state, chain }) => { - const { blockContent } = getBlockInfoFromPos( + const { blockContent } = getBlockInfoFromPos_DEPRECATED( state.doc, state.selection.from )!; From 7b17ded1e2a146d39a7d7f155aad2b743a3ad6e3 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 14 Oct 2024 15:49:39 +0200 Subject: [PATCH 25/34] finish cleanup --- .../commands/mergeBlocks/mergeBlocks.test.ts | 8 +-- .../commands/mergeBlocks/mergeBlocks.ts | 14 ++-- .../commands/moveBlock/moveBlock.test.ts | 7 +- .../commands/moveBlock/moveBlock.ts | 7 +- .../commands/splitBlock/splitBlock.test.ts | 14 ++-- .../commands/splitBlock/splitBlock.ts | 1 - .../commands/updateBlock/updateBlock.ts | 8 +-- .../textCursorPosition/textCursorPosition.ts | 32 ++++----- .../fromClipboard/handleFileInsertion.ts | 9 ++- packages/core/src/api/getBlockInfoFromPos.ts | 68 +++++++++---------- .../api/nodeConversions/nodeConversions.ts | 5 +- .../NumberedListIndexingPlugin.ts | 45 ++++++------ .../core/src/editor/BlockNoteEditor.test.ts | 8 ++- packages/core/src/editor/BlockNoteEditor.ts | 20 +++--- .../KeyboardShortcutsExtension.ts | 36 ++++------ 15 files changed, 133 insertions(+), 149 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index a66732e6d..9ce754a8a 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { mergeBlocksCommand } from "./mergeBlocks.js"; @@ -14,10 +14,8 @@ function mergeBlocks(posBetweenBlocks: number) { } function getPosBeforeSelectedBlock() { - return getBlockInfoFromPos_DEPRECATED( - getEditor()._tiptapEditor.state.doc, - getEditor()._tiptapEditor.state.selection.from - ).blockContainer.beforePos; + return getBlockInfoFromSelection(getEditor()._tiptapEditor.state) + .blockContainer.beforePos; } describe("Test mergeBlocks", () => { diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index aa9b1ed09..b264d26ed 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -1,6 +1,6 @@ import { EditorState } from "prosemirror-state"; -import { getBlockInfo } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromResolvedPos } from "../../../getBlockInfoFromPos.js"; export const mergeBlocksCommand = (posBetweenBlocks: number) => @@ -21,7 +21,7 @@ export const mergeBlocksCommand = ); } - const nextBlockInfo = getBlockInfo($pos.nodeAfter, posBetweenBlocks); + const nextBlockInfo = getBlockInfoFromResolvedPos($pos); // Removes a level of nesting all children of the next block by 1 level, if it contains both content and block // group nodes. @@ -43,9 +43,8 @@ export const mergeBlocksCommand = // TODO: extract helper? // Finds the nearest previous block, regardless of nesting level. let prevBlockStartPos = $pos.posAtIndex($pos.index() - 1); - let prevBlockInfo = getBlockInfo( - state.doc.resolve(prevBlockStartPos).nodeAfter!, - prevBlockStartPos + let prevBlockInfo = getBlockInfoFromResolvedPos( + state.doc.resolve(prevBlockStartPos) ); while (prevBlockInfo.blockGroup) { const group = prevBlockInfo.blockGroup.node; @@ -53,9 +52,8 @@ export const mergeBlocksCommand = prevBlockStartPos = state.doc .resolve(prevBlockInfo.blockGroup.beforePos + 1) .posAtIndex(group.childCount - 1); - prevBlockInfo = getBlockInfo( - state.doc.resolve(prevBlockStartPos).nodeAfter!, - prevBlockStartPos + prevBlockInfo = getBlockInfoFromResolvedPos( + state.doc.resolve(prevBlockStartPos) ); } diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts index 60d1f8510..fdaf6ee09 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts @@ -2,7 +2,7 @@ import { NodeSelection, TextSelection } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; import { describe, expect, it } from "vitest"; -import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { moveBlockDown, @@ -13,9 +13,8 @@ import { const getEditor = setupTestEnv(); function makeSelectionSpanContent(selectionType: "text" | "node" | "cell") { - const { blockContent } = getBlockInfoFromPos_DEPRECATED( - getEditor()._tiptapEditor.state.doc, - getEditor()._tiptapEditor.state.selection.from + const { blockContent } = getBlockInfoFromSelection( + getEditor()._tiptapEditor.state ); if (selectionType === "cell") { diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts index 888b2b1ef..89ed457b4 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts @@ -3,7 +3,7 @@ import { CellSelection } from "prosemirror-tables"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; import { BlockIdentifier } from "../../../../schema/index.js"; -import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js"; import { getNodeById } from "../../../nodeUtil.js"; type BlockSelectionData = ( @@ -31,9 +31,8 @@ type BlockSelectionData = ( function getBlockSelectionData( editor: BlockNoteEditor ): BlockSelectionData { - const { blockContainer } = getBlockInfoFromPos_DEPRECATED( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.selection.from + const { blockContainer } = getBlockInfoFromSelection( + editor._tiptapEditor.state ); const selectionData = { diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts index 63389a7b6..544c24d58 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -2,7 +2,10 @@ import { Node } from "prosemirror-model"; import { describe, expect, it } from "vitest"; import { TextSelection } from "prosemirror-state"; -import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; +import { + getBlockInfo, + getBlockInfoFromSelection, +} from "../../../getBlockInfoFromPos.js"; import { getNodeById } from "../../../nodeUtil.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { splitBlockCommand } from "./splitBlock.js"; @@ -25,8 +28,8 @@ function setSelectionWithOffset( targetBlockId: string, offset: number ) { - const { posBeforeNode } = getNodeById(targetBlockId, doc); - const { blockContent } = getBlockInfoFromPos_DEPRECATED(doc, posBeforeNode); + const posInfo = getNodeById(targetBlockId, doc); + const { blockContent } = getBlockInfo(posInfo); getEditor()._tiptapEditor.view.dispatch( getEditor()._tiptapEditor.state.tr.setSelection( @@ -121,9 +124,8 @@ describe("Test splitBlocks", () => { splitBlock(getEditor()._tiptapEditor.state.selection.anchor); - const { blockContainer } = getBlockInfoFromPos_DEPRECATED( - getEditor()._tiptapEditor.state.doc, - getEditor()._tiptapEditor.state.selection.anchor + const { blockContainer } = getBlockInfoFromSelection( + getEditor()._tiptapEditor.state ); const anchorIsAtStartOfNewBlock = diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index 9f427f118..07df1a9a5 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -23,7 +23,6 @@ export const splitBlockCommand = ( ); const { blockContainer, blockContent } = getBlockInfo( - state.doc, nearestBlockContainerPos ); diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 7967f9970..ab1a9c89c 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -10,7 +10,7 @@ import { import { InlineContentSchema } from "../../../../schema/inlineContent/types.js"; import { StyleSchema } from "../../../../schema/styles/types.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; -import { getBlockInfo } from "../../../getBlockInfoFromPos.js"; +import { getBlockInfoFromResolvedPos } from "../../../getBlockInfoFromPos.js"; import { blockToNode, inlineContentToNodes, @@ -36,10 +36,8 @@ export const updateBlockCommand = state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const { blockContainer, blockContent, blockGroup } = getBlockInfo( - state.doc, - posBeforeBlock - ); + const { blockContainer, blockContent, blockGroup } = + getBlockInfoFromResolvedPos(state.doc.resolve(posBeforeBlock)); if (dispatch) { // Adds blockGroup node with child blocks if necessary. diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts index 93ed32a6e..c397c2d87 100644 --- a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -9,7 +9,10 @@ import { StyleSchema, } from "../../../../schema/index.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; -import { getBlockInfoFromPos_DEPRECATED } from "../../../getBlockInfoFromPos.js"; +import { + getBlockInfo, + getBlockInfoFromSelection, +} from "../../../getBlockInfoFromPos.js"; import { nodeToBlock } from "../../../nodeConversions/nodeConversions.js"; import { getNodeById } from "../../../nodeUtil.js"; @@ -18,15 +21,15 @@ export function getTextCursorPosition< I extends InlineContentSchema, S extends StyleSchema >(editor: BlockNoteEditor): TextCursorPosition { - const { depth, blockContainer } = getBlockInfoFromPos_DEPRECATED( - editor._tiptapEditor.state.doc, - editor._tiptapEditor.state.selection.from - )!; + const { blockContainer } = getBlockInfoFromSelection( + editor._tiptapEditor.state + ); - // Gets previous blockContainer node at the same nesting level, if the current node isn't the first child. - const prevNode = editor._tiptapEditor.state.doc.resolve( + const resolvedPos = editor._tiptapEditor.state.doc.resolve( blockContainer.beforePos - ).nodeBefore; + ); + // 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( @@ -35,10 +38,8 @@ export function getTextCursorPosition< // Gets parent blockContainer node, if the current node is nested. let parentNode: Node | undefined = undefined; - if (depth > 1) { - parentNode = editor._tiptapEditor.state.doc - .resolve(blockContainer.beforePos) - .node(depth - 1); + if (resolvedPos.depth > 1) { + parentNode = resolvedPos.node(resolvedPos.depth - 1); } return { @@ -93,11 +94,8 @@ export function setTextCursorPosition< ) { const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id; - const { posBeforeNode } = getNodeById(id, editor._tiptapEditor.state.doc); - const { blockContent } = getBlockInfoFromPos_DEPRECATED( - editor._tiptapEditor.state.doc, - posBeforeNode - )!; + 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; diff --git a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts index ce1442c13..c7944bf4a 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_DEPRECATED } from "../../getBlockInfoFromPos.js"; +import { + getBlockInfo, + getNearestBlockContainerPos, +} from "../../getBlockInfoFromPos.js"; import { acceptedMIMETypes } from "./acceptedMIMETypes.js"; function checkFileExtensionsMatch( @@ -132,11 +135,13 @@ export async function handleFileInsertion< return; } - const blockInfo = getBlockInfoFromPos_DEPRECATED( + const posInfo = getNearestBlockContainerPos( editor._tiptapEditor.state.doc, pos.pos ); + const blockInfo = getBlockInfo(posInfo); + insertedBlockId = editor.insertBlocks( [fileBlock], blockInfo.blockContainer.node.attrs.id, diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index 2a5b8526d..3042fb36e 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -1,4 +1,4 @@ -import { Node } from "prosemirror-model"; +import { Node, ResolvedPos } from "prosemirror-model"; import { EditorState } from "prosemirror-state"; type SingleBlockInfo = { @@ -8,7 +8,6 @@ type SingleBlockInfo = { }; export type BlockInfo = { - depth: number; blockContainer: SingleBlockInfo; blockContent: SingleBlockInfo; blockGroup?: SingleBlockInfo; @@ -32,7 +31,10 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { // 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 $pos.pos; + return { + posBeforeNode: $pos.pos, + node: $pos.nodeAfter, + }; } // Checks the node containing the position and its ancestors until a @@ -41,7 +43,10 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { let node = $pos.node(depth); while (depth > 0) { if (node.type.name === "blockContainer") { - return $pos.before(depth); + return { + posBeforeNode: $pos.before(depth), + node: node, + }; } depth--; @@ -66,18 +71,22 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { // eslint-disable-next-line no-console console.warn(`Position ${pos} is not within a blockContainer node.`); - return ( + const resolvedPos = doc.resolve( allBlockContainerPositions.find((position) => position >= pos) || - allBlockContainerPositions[allBlockContainerPositions.length - 1] + allBlockContainerPositions[allBlockContainerPositions.length - 1] ); + return { + posBeforeNode: resolvedPos.pos, + node: resolvedPos.nodeAfter!, + }; } -export function getBlockInfo( +export function getBlockInfoWithManualOffset( node: Node, - beforePos?: number -): Omit { + blockContainerBeforePosOffset: number +): BlockInfo { const blockContainerNode = node; - const blockContainerBeforePos = beforePos || 0; + const blockContainerBeforePos = blockContainerBeforePosOffset; const blockContainerAfterPos = blockContainerBeforePos + blockContainerNode.nodeSize; @@ -142,36 +151,21 @@ export function getBlockInfo( }; } -/** - * 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_DEPRECATED( - doc: Node, - pos: number -): BlockInfo { - const $pos = doc.resolve(getNearestBlockContainerPos(doc, pos)); - const depth = $pos.depth; - - const node = $pos.nodeAfter; - const beforePos = $pos.pos; +export function getBlockInfo(posInfo: { posBeforeNode: number; node: Node }) { + return getBlockInfoWithManualOffset(posInfo.node, posInfo.posBeforeNode); +} - if (node === null || node.type.name !== "blockContainer") { - throw new Error( - `No blockContainer node found near position ${pos}. getNearestBlockContainerPos returned ${beforePos}.` - ); +export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) { + if (!resolvedPos.nodeAfter) { + throw new Error("ResolvedPos does not point to a node"); } - - return { - depth, - ...getBlockInfo(node, beforePos), - }; + return getBlockInfoWithManualOffset(resolvedPos.nodeAfter, resolvedPos.pos); } export function getBlockInfoFromSelection(state: EditorState) { - const pos = getNearestBlockContainerPos(state.doc, state.selection.anchor); - return getBlockInfoFromPos_DEPRECATED(state.doc, pos); + const posInfo = getNearestBlockContainerPos( + state.doc, + state.selection.anchor + ); + return getBlockInfo(posInfo); } diff --git a/packages/core/src/api/nodeConversions/nodeConversions.ts b/packages/core/src/api/nodeConversions/nodeConversions.ts index c3bd4bb57..5978cc6bf 100644 --- a/packages/core/src/api/nodeConversions/nodeConversions.ts +++ b/packages/core/src/api/nodeConversions/nodeConversions.ts @@ -17,7 +17,7 @@ import type { 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 { @@ -566,7 +566,8 @@ export function nodeToBlock< return cachedBlock; } - const { blockContainer, blockContent, blockGroup } = getBlockInfo(node); + const { blockContainer, blockContent, blockGroup } = + getBlockInfoWithManualOffset(node, 0); let id = blockContainer.node.attrs.id; diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts index 9d19f3486..ae77be402 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_DEPRECATED } 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,39 +21,34 @@ export const NumberedListIndexingPlugin = () => { node.firstChild!.type.name === "numberedListItem" ) { let newIndex = "1"; - const isFirstBlockInDoc = pos === 1; - const blockInfo = getBlockInfoFromPos_DEPRECATED(tr.doc, pos)!; + 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 prevBlock = tr.doc.resolve( - blockInfo.blockContainer.beforePos - 1 - ).nodeBefore!; - const prevBlockInfo = getBlockInfoFromPos_DEPRECATED( - tr.doc, - blockInfo.blockContainer.beforePos - prevBlock.nodeSize - )!; - if (prevBlockInfo === undefined) { - return; - } - const isFirstBlockInNestingLevel = - blockInfo.depth !== prevBlockInfo.depth; + const prevBlock = tr.doc.resolve( + blockInfo.blockContainer.beforePos + ).nodeBefore; - if (!isFirstBlockInNestingLevel) { - const prevBlockContentNode = prevBlockInfo.blockContent.node; - const prevBlockContentType = prevBlockContentNode.type; + 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(); } } diff --git a/packages/core/src/editor/BlockNoteEditor.test.ts b/packages/core/src/editor/BlockNoteEditor.test.ts index 5336012eb..f48abd9b4 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_DEPRECATED } from "../api/getBlockInfoFromPos.js"; +import { + getBlockInfo, + getNearestBlockContainerPos, +} from "../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "./BlockNoteEditor.js"; /** @@ -7,10 +10,11 @@ import { BlockNoteEditor } from "./BlockNoteEditor.js"; */ it("creates an editor", () => { const editor = BlockNoteEditor.create(); - const { blockContent } = getBlockInfoFromPos_DEPRECATED( + const posInfo = getNearestBlockContainerPos( editor._tiptapEditor.state.doc, 2 ); + const { blockContent } = getBlockInfo(posInfo); expect(blockContent.node.type.name).toEqual("paragraph"); }); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 31cd0a8eb..155665135 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -17,7 +17,7 @@ import { } 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_DEPRECATED } from "../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../api/getBlockInfoFromPos.js"; import { inlineContentToNodes, nodeToBlock, @@ -965,10 +965,9 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor can be nested. */ public canNestBlock() { - const { blockContainer } = getBlockInfoFromPos_DEPRECATED( - this._tiptapEditor.state.doc, - this._tiptapEditor.state.selection.from - )!; + const { blockContainer } = getBlockInfoFromSelection( + this._tiptapEditor.state + ); return ( this._tiptapEditor.state.doc.resolve(blockContainer.beforePos) @@ -987,12 +986,13 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor is nested. */ public canUnnestBlock() { - const { depth } = getBlockInfoFromPos_DEPRECATED( - this._tiptapEditor.state.doc, - this._tiptapEditor.state.selection.from - )!; + const { blockContainer } = getBlockInfoFromSelection( + this._tiptapEditor.state + ); - return depth > 1; + return ( + this._tiptapEditor.state.doc.resolve(blockContainer.beforePos).depth > 1 + ); } /** diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 582bb7e8b..a4513b381 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -4,10 +4,7 @@ import { TextSelection } from "prosemirror-state"; import { 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 { - getBlockInfoFromPos_DEPRECATED, - getBlockInfoFromSelection, -} from "../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; export const KeyboardShortcutsExtension = Extension.create<{ @@ -51,10 +48,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // 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 } = getBlockInfoFromPos_DEPRECATED( - state.doc, - state.selection.from - )!; + const { blockContent } = getBlockInfoFromSelection(state); const selectionAtBlockStart = state.selection.from === blockContent.beforePos + 1; @@ -69,8 +63,10 @@ export const KeyboardShortcutsExtension = Extension.create<{ // is at the start of the block. () => commands.command(({ state }) => { - const { depth, blockContainer, blockContent } = - getBlockInfoFromPos_DEPRECATED(state.doc, state.selection.from)!; + const { blockContainer, blockContent } = + getBlockInfoFromSelection(state); + + const { depth } = state.doc.resolve(blockContainer.beforePos); const selectionAtBlockStart = state.selection.from === blockContent.beforePos + 1; @@ -102,9 +98,10 @@ export const KeyboardShortcutsExtension = Extension.create<{ () => commands.command(({ state }) => { // TODO: Change this to not rely on offsets & schema assumptions - const { depth, blockContainer, blockContent, blockGroup } = - getBlockInfoFromPos_DEPRECATED(state.doc, state.selection.from)!; + const { blockContainer, blockContent, blockGroup } = + getBlockInfoFromSelection(state); + const { depth } = state.doc.resolve(blockContainer.beforePos); const blockAtDocEnd = blockContainer.afterPos === state.doc.nodeSize - 3; const selectionAtBlockEnd = @@ -141,10 +138,10 @@ export const KeyboardShortcutsExtension = Extension.create<{ // of the block. () => commands.command(({ state }) => { - const { depth, blockContent } = getBlockInfoFromPos_DEPRECATED( - state.doc, - state.selection.from - )!; + const { blockContent, blockContainer } = + getBlockInfoFromSelection(state); + + const { depth } = state.doc.resolve(blockContainer.beforePos); const selectionAtBlockStart = state.selection.$anchor.parentOffset === 0; @@ -169,7 +166,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ () => commands.command(({ state, dispatch }) => { const { blockContainer, blockContent } = - getBlockInfoFromPos_DEPRECATED(state.doc, state.selection.from)!; + getBlockInfoFromSelection(state); const selectionAtBlockStart = state.selection.$anchor.parentOffset === 0; @@ -202,10 +199,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // deletes the selection beforehand, if it's not empty. () => commands.command(({ state, chain }) => { - const { blockContent } = getBlockInfoFromPos_DEPRECATED( - state.doc, - state.selection.from - )!; + const { blockContent } = getBlockInfoFromSelection(state); const selectionAtBlockStart = state.selection.$anchor.parentOffset === 0; From bf4635e295ef5dae81202b587c3238f344c950d5 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Mon, 14 Oct 2024 17:46:42 +0200 Subject: [PATCH 26/34] Fixed `mergeBlocks` edge cases --- .../__snapshots__/mergeBlocks.test.ts.snap | 932 +++++++++++++++++- .../commands/mergeBlocks/mergeBlocks.test.ts | 4 +- .../commands/mergeBlocks/mergeBlocks.ts | 166 ++-- packages/core/src/api/getBlockInfoFromPos.ts | 10 +- .../KeyboardShortcutsExtension.ts | 48 +- 5 files changed, 1078 insertions(+), 82 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index 0ff7cbf67..5d904849c 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -1501,6 +1501,21 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` }, "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": [ @@ -1706,7 +1721,7 @@ exports[`Test mergeBlocks > Inline content & no content 1`] = ` ] `; -exports[`Test mergeBlocks > No content & inline content 1`] = ` +exports[`Test mergeBlocks > Inline content & table content 1`] = ` [ { "children": [], @@ -1929,6 +1944,21 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` }, "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": [ @@ -2134,7 +2164,7 @@ exports[`Test mergeBlocks > No content & inline content 1`] = ` ] `; -exports[`Test mergeBlocks > Second block has children 1`] = ` +exports[`Test mergeBlocks > No content & inline content 1`] = ` [ { "children": [], @@ -2158,7 +2188,7 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 1Paragraph with children", + "text": "Paragraph 1", "type": "text", }, ], @@ -2173,15 +2203,33 @@ exports[`Test mergeBlocks > Second block has children 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": "Double Nested Paragraph 0", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-0", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2193,11 +2241,879 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` "content": [ { "styles": {}, - "text": "Nested Paragraph 0", + "text": "Paragraph with children", "type": "text", }, ], - "id": "nested-paragraph-0", + "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": [ + { + "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 mergeBlocks > Second 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 1Paragraph with children", + "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", + }, + { + "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": [ + { + "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 mergeBlocks > Table content & 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", diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 9ce754a8a..1e686666f 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -59,7 +59,7 @@ describe("Test mergeBlocks", () => { expect(getEditor().document).toMatchSnapshot(); }); - it.skip("Inline content & table content", () => { + it("Inline content & table content", () => { getEditor().setTextCursorPosition("table-0"); mergeBlocks(getPosBeforeSelectedBlock()); @@ -75,7 +75,7 @@ describe("Test mergeBlocks", () => { expect(getEditor().document).toMatchSnapshot(); }); - it.skip("Table content & inline content", () => { + it("Table content & inline content", () => { getEditor().setTextCursorPosition("paragraph-7"); mergeBlocks(getPosBeforeSelectedBlock()); diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index b264d26ed..6b5a6964a 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -1,7 +1,99 @@ +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); +}; + +export 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*" + ); +}; + +export const mergeBlocks = ( + state: EditorState, + dispatch: ((args?: any) => any) | undefined, + $prevBlockPos: ResolvedPos, + $nextBlockPos: ResolvedPos +) => { + const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); + + // Removes a level of nesting all children of the next block by 1 level, if it contains both content and block + // group nodes. + 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); + + // 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!, $nextBlockPos.depth); + } + } + + // Deletes next block and adds its text content to the nearest previous block. + if (dispatch) { + const prevBlockInfo = getBlockInfoFromResolvedPos($prevBlockPos); + + dispatch( + state.tr.deleteRange( + prevBlockInfo.blockContent.afterPos - 1, + nextBlockInfo.blockContent.beforePos + 1 + ) + + // .scrollIntoView() + ); + + // TODO: fix unit test + think of missing tests + // TODO: reenable set selection + + // state.tr.setSelection( + // new TextSelection(state.doc.resolve(prevBlockEndPos - 1)) + // ); + } + + return true; +}; + export const mergeBlocksCommand = (posBetweenBlocks: number) => ({ @@ -11,75 +103,15 @@ export const mergeBlocksCommand = state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const $pos = state.doc.resolve(posBetweenBlocks); - const nextNodeIsBlock = $pos.nodeAfter?.type.name === "blockContainer"; - const prevNodeIsBlock = $pos.nodeBefore?.type.name === "blockContainer"; - - if (!nextNodeIsBlock || !prevNodeIsBlock) { - throw new Error( - "invalid `posBetweenBlocks` passed to mergeBlocksCommand" - ); - } - - const nextBlockInfo = getBlockInfoFromResolvedPos($pos); - - // Removes a level of nesting all children of the next block by 1 level, if it contains both content and block - // group nodes. - 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); - - // 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!, $pos.depth); - } - } - - // TODO: extract helper? - // Finds the nearest previous block, regardless of nesting level. - let prevBlockStartPos = $pos.posAtIndex($pos.index() - 1); - let prevBlockInfo = getBlockInfoFromResolvedPos( - state.doc.resolve(prevBlockStartPos) - ); - while (prevBlockInfo.blockGroup) { - const group = prevBlockInfo.blockGroup.node; - - prevBlockStartPos = state.doc - .resolve(prevBlockInfo.blockGroup.beforePos + 1) - .posAtIndex(group.childCount - 1); - prevBlockInfo = getBlockInfoFromResolvedPos( - state.doc.resolve(prevBlockStartPos) - ); - } - - console.log( - prevBlockInfo.blockContent.afterPos, - nextBlockInfo.blockContent.beforePos - ); - debugger; - // Deletes next block and adds its text content to the nearest previous block. - if (dispatch) { - dispatch( - state.tr.deleteRange( - prevBlockInfo.blockContent.afterPos - 1, - nextBlockInfo.blockContent.beforePos + 1 - ) - - // .scrollIntoView() - ); - - // TODO: fix unit test + think of missing tests - // TODO: reenable set selection + const $nextBlockPos = state.doc.resolve(posBetweenBlocks); + const $prevBlockPos = getPrevBlockPos(state.doc, $nextBlockPos); - // state.tr.setSelection( - // new TextSelection(state.doc.resolve(prevBlockEndPos - 1)) + if (!canMerge($prevBlockPos, $nextBlockPos)) { + // throw new Error( + // `Attempting to merge block at position ${$nextBlockPos.pos} into previous block at position ${$prevBlockPos.pos}, but previous block has invalid content type` // ); + return false; } - return true; + return mergeBlocks(state, dispatch, $prevBlockPos, $nextBlockPos); }; diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index 3042fb36e..d4d11a15f 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -53,7 +53,6 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { node = $pos.node(depth); } - // TODO: Make more specific & log warn // 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 @@ -157,7 +156,14 @@ export function getBlockInfo(posInfo: { posBeforeNode: number; node: Node }) { export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) { if (!resolvedPos.nodeAfter) { - throw new Error("ResolvedPos does not point to a node"); + 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); } diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index a4513b381..49b87d7ac 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -4,7 +4,10 @@ import { TextSelection } from "prosemirror-state"; import { 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 { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; +import { + getBlockInfoFromResolvedPos, + getBlockInfoFromSelection, +} from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; export const KeyboardShortcutsExtension = Extension.create<{ @@ -59,8 +62,9 @@ export const KeyboardShortcutsExtension = Extension.create<{ 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. + // 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 } = @@ -84,6 +88,44 @@ export const KeyboardShortcutsExtension = Extension.create<{ return commands.command(mergeBlocksCommand(posBetweenBlocks)); } + return false; + }), + // Deletes previous block if it contains no content. If it has inline + // content, it's merged instead. Otherwise, it's a no-op. + () => + commands.command(({ state }) => { + const { blockContainer, blockContent } = + getBlockInfoFromSelection(state); + + const selectionAtBlockStart = + state.selection.from === blockContent.beforePos + 1; + const selectionEmpty = state.selection.empty; + const blockAtDocStart = blockContainer.beforePos === 1; + + const $currentBlockPos = state.doc.resolve( + blockContainer.beforePos + ); + const prevBlockPos = $currentBlockPos.posAtIndex( + $currentBlockPos.index() - 1 + ); + const prevBlockInfo = getBlockInfoFromResolvedPos( + state.doc.resolve(prevBlockPos) + ); + + if ( + !blockAtDocStart && + selectionAtBlockStart && + selectionEmpty && + $currentBlockPos.depth === 1 && + prevBlockInfo.blockGroup === undefined && + prevBlockInfo.blockContent.node.type.spec.content === "" + ) { + return commands.deleteRange({ + from: prevBlockPos, + to: $currentBlockPos.pos, + }); + } + return false; }), ]); From 3f73033508bdcfea28d05d0cbefd68010ac51d89 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 06:34:21 +0200 Subject: [PATCH 27/34] fix build --- .../CheckListItemBlockContent.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts index 6723997fd..d6df12855 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -241,12 +241,16 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ getPos() ); this.editor.commands.command( - updateBlockCommand(this.options.editor, beforeBlockContainerPos, { - type: "checkListItem", - props: { - checked: checkbox.checked as any, - }, - }) + updateBlockCommand( + this.options.editor, + beforeBlockContainerPos.posBeforeNode, + { + type: "checkListItem", + props: { + checked: checkbox.checked as any, + }, + } + ) ); } }; From 9ae58f6a2f255462e7adc2c18d2e3ebd34a80b4b Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 07:01:36 +0200 Subject: [PATCH 28/34] clean nodeconversions --- .../commands/insertBlocks/insertBlocks.ts | 7 +- .../commands/removeBlocks/removeBlocks.ts | 4 +- .../commands/replaceBlocks/replaceBlocks.ts | 13 +- .../commands/updateBlock/updateBlock.ts | 5 +- .../textCursorPosition/textCursorPosition.ts | 2 +- .../clipboard/toClipboard/copyExtension.ts | 2 +- .../html/util/sharedHTMLConversion.ts | 2 +- .../src/api/nodeConversions/blockToNode.ts | 257 ++++++++++++++++++ .../api/nodeConversions/fragmentToBlocks.ts | 2 +- .../nodeConversions/nodeConversions.test.ts | 3 +- .../{nodeConversions.ts => nodeToBlock.ts} | 247 +---------------- .../core/src/api/parsers/html/parseHTML.ts | 2 +- .../core/src/blocks/defaultBlockHelpers.ts | 2 +- packages/core/src/editor/BlockNoteEditor.ts | 7 +- .../core/src/editor/BlockNoteTipTapEditor.ts | 2 +- .../TableHandles/TableHandlesPlugin.ts | 2 +- packages/core/src/index.ts | 3 +- .../src/schema/inlineContent/createSpec.ts | 6 +- 18 files changed, 290 insertions(+), 278 deletions(-) create mode 100644 packages/core/src/api/nodeConversions/blockToNode.ts rename packages/core/src/api/nodeConversions/{nodeConversions.ts => nodeToBlock.ts} (61%) diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts index b63a1439d..0ae26fa40 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -8,10 +8,9 @@ import { InlineContentSchema, StyleSchema, } from "../../../../schema/index.js"; -import { - blockToNode, - nodeToBlock, -} from "../../../nodeConversions/nodeConversions.js"; + +import { blockToNode } from "../../../nodeConversions/blockToNode.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../../nodeUtil.js"; export function insertBlocks< diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts index d38ea3622..550e20ecc 100644 --- a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts @@ -1,15 +1,15 @@ import { Node } from "prosemirror-model"; import { Transaction } from "prosemirror-state"; -import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; 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/nodeConversions.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; export function removeBlocksWithCallback< BSchema extends BlockSchema, diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts index bf81b6bce..27123b9b6 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -1,16 +1,15 @@ +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 { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; -import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; -import { Node } from "prosemirror-model"; -import { - blockToNode, - nodeToBlock, -} from "../../../nodeConversions/nodeConversions.js"; + +import { blockToNode } from "../../../nodeConversions/blockToNode.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; import { removeBlocksWithCallback } from "../removeBlocks/removeBlocks.js"; export function replaceBlocks< diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index ab1a9c89c..112ee8a61 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -11,12 +11,13 @@ 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, - nodeToBlock, tableContentToNodes, -} from "../../../nodeConversions/nodeConversions.js"; +} from "../../../nodeConversions/blockToNode.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../../nodeUtil.js"; export const updateBlockCommand = diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts index c397c2d87..d7237b0b2 100644 --- a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -13,7 +13,7 @@ import { getBlockInfo, getBlockInfoFromSelection, } from "../../../getBlockInfoFromPos.js"; -import { nodeToBlock } from "../../../nodeConversions/nodeConversions.js"; +import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../../nodeUtil.js"; export function getTextCursorPosition< diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index 14d7fbad1..14df07902 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 62e89a14a..ac8ff29f1 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/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts new file mode 100644 index 000000000..f63ee0997 --- /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 dcbfeeb8b..8b0cd2103 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 b2a84c931..b196af924 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 61% rename from packages/core/src/api/nodeConversions/nodeConversions.ts rename to packages/core/src/api/nodeConversions/nodeToBlock.ts index 5978cc6bf..55cfa045a 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 { 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 */ diff --git a/packages/core/src/api/parsers/html/parseHTML.ts b/packages/core/src/api/parsers/html/parseHTML.ts index 24852c47e..04f8b0f02 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/defaultBlockHelpers.ts b/packages/core/src/blocks/defaultBlockHelpers.ts index 1c5cbcae7..4e758bd16 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.ts b/packages/core/src/editor/BlockNoteEditor.ts index 155665135..f30004d6e 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -18,10 +18,7 @@ import { import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; import { getBlockInfoFromSelection } from "../api/getBlockInfoFromPos.js"; -import { - inlineContentToNodes, - nodeToBlock, -} from "../api/nodeConversions/nodeConversions.js"; + import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js"; import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown.js"; import { @@ -72,6 +69,8 @@ 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"; diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index 22aa626f1..fab4469e1 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/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 9b7b65062..77af68523 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"; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index da6ddc9f0..ae98dff89 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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/schema/inlineContent/createSpec.ts b/packages/core/src/schema/inlineContent/createSpec.ts index e8f13540b..ba37d8904 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"; From 80c8b466b500c03f497d7a6740d8ab3ef64752d0 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 15 Oct 2024 15:46:47 +0200 Subject: [PATCH 29/34] Implemented PR feedback --- .../__snapshots__/mergeBlocks.test.ts.snap | 1786 +---------------- .../commands/mergeBlocks/mergeBlocks.test.ts | 64 +- .../commands/mergeBlocks/mergeBlocks.ts | 13 +- .../commands/updateBlock/updateBlock.test.ts | 28 - packages/core/src/api/getBlockInfoFromPos.ts | 15 - .../KeyboardShortcutsExtension.ts | 15 +- 6 files changed, 59 insertions(+), 1862 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index 5d904849c..4f2ecd592 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -1278,1761 +1278,7 @@ exports[`Test mergeBlocks > First block has children 1`] = ` ] `; -exports[`Test mergeBlocks > Inline content & 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": { - "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": [ - { - "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 mergeBlocks > Inline content & 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": 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": [ - { - "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 mergeBlocks > No content & 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": { - "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": [ - { - "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 mergeBlocks > Second 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 1Paragraph with children", - "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", - }, - { - "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": [ - { - "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 mergeBlocks > Table content & inline content 1`] = ` +exports[`Test mergeBlocks > Second block has children 1`] = ` [ { "children": [], @@ -3056,7 +1302,7 @@ exports[`Test mergeBlocks > Table content & inline content 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 1Paragraph with children", "type": "text", }, ], @@ -3071,33 +1317,15 @@ exports[`Test mergeBlocks > Table content & inline content 1`] = ` { "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", - }, - ], + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 0", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "nested-paragraph-0", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3109,11 +1337,11 @@ exports[`Test mergeBlocks > Table content & inline content 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "paragraph-with-children", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 1e686666f..dad318f8a 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -8,7 +8,7 @@ const getEditor = setupTestEnv(); function mergeBlocks(posBetweenBlocks: number) { // TODO: Replace with imported function after converting from TipTap command - getEditor()._tiptapEditor.commands.command( + return getEditor()._tiptapEditor.commands.command( mergeBlocksCommand(posBetweenBlocks) ); } @@ -51,52 +51,64 @@ describe("Test mergeBlocks", () => { 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("Inline content & no content", () => { getEditor().setTextCursorPosition("image-0"); - mergeBlocks(getPosBeforeSelectedBlock()); + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); - expect(getEditor().document).toMatchSnapshot(); + expect(getEditor().document).toEqual(originalDocument); + expect(ret).toBeFalsy(); }); it("Inline content & table content", () => { getEditor().setTextCursorPosition("table-0"); - mergeBlocks(getPosBeforeSelectedBlock()); + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); - expect(getEditor().document).toMatchSnapshot(); + expect(getEditor().document).toEqual(originalDocument); + expect(ret).toBeFalsy(); }); it("No content & inline content", () => { getEditor().setTextCursorPosition("paragraph-6"); - mergeBlocks(getPosBeforeSelectedBlock()); + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); - expect(getEditor().document).toMatchSnapshot(); + expect(getEditor().document).toEqual(originalDocument); + expect(ret).toBeFalsy(); }); it("Table content & inline content", () => { getEditor().setTextCursorPosition("paragraph-7"); - mergeBlocks(getPosBeforeSelectedBlock()); - - expect(getEditor().document).toMatchSnapshot(); - }); - - it("Selection is set", () => { - 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; + const originalDocument = getEditor().document; + const ret = mergeBlocks(getPosBeforeSelectedBlock()); - expect(anchorIsAtOldFirstBlockEndPos).toBeTruthy(); + 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 index 6b5a6964a..65ece23d2 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -70,7 +70,9 @@ export const mergeBlocks = ( } } - // Deletes next block and adds its text content to the nearest previous block. + // 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); @@ -79,16 +81,7 @@ export const mergeBlocks = ( prevBlockInfo.blockContent.afterPos - 1, nextBlockInfo.blockContent.beforePos + 1 ) - - // .scrollIntoView() ); - - // TODO: fix unit test + think of missing tests - // TODO: reenable set selection - - // state.tr.setSelection( - // new TextSelection(state.doc.resolve(prevBlockEndPos - 1)) - // ); } return true; diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts index d4f2ddcc4..bf548f914 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts @@ -298,31 +298,3 @@ describe("Test updateBlock", () => { expect(getEditor().document).toMatchSnapshot(); }); }); - -// TODO: This seems like it really tests converting strings to inline content? -// 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/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index d4d11a15f..cfcf434ee 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -128,21 +128,6 @@ export function getBlockInfoWithManualOffset( ); } - // TODO: Remove - if ( - blockGroup && - (blockContent as SingleBlockInfo).afterPos !== - (blockGroup as SingleBlockInfo).beforePos - ) { - throw new Error( - `blockContent.afterPos (${ - (blockContent as SingleBlockInfo).afterPos - }) does not match blockGroup.beforePos (${ - (blockGroup as SingleBlockInfo | undefined)?.beforePos - })` - ); - } - return { blockContainer, blockContent, diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 49b87d7ac..2810663e2 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -15,10 +15,12 @@ export const KeyboardShortcutsExtension = Extension.create<{ }>({ 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(({ commands }) => [ + 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. @@ -85,13 +87,18 @@ export const KeyboardShortcutsExtension = Extension.create<{ selectionEmpty && depth === 1 ) { - return commands.command(mergeBlocksCommand(posBetweenBlocks)); + return chain() + .command(mergeBlocksCommand(posBetweenBlocks)) + .scrollIntoView() + .run(); } return false; }), - // Deletes previous block if it contains no content. If it has inline - // content, it's merged instead. Otherwise, it's a no-op. + // Deletes previous block if it contains no content, when the selection + // is empty and at the start of the block. The previous block also has + // to not have any parents or children, as otherwise the UX becomes + // confusing. () => commands.command(({ state }) => { const { blockContainer, blockContent } = From 1414e14824733733d33d1a7ae489dc01c2188fd7 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 15 Oct 2024 20:05:59 +0200 Subject: [PATCH 30/34] Finished review and remaining changes --- .../commands/mergeBlocks/mergeBlocks.test.ts | 1 - .../commands/mergeBlocks/mergeBlocks.ts | 16 ++------ .../commands/removeBlocks/removeBlocks.ts | 2 +- .../commands/splitBlock/splitBlock.test.ts | 3 +- .../commands/updateBlock/updateBlock.ts | 40 +++++-------------- packages/core/src/api/getBlockInfoFromPos.ts | 36 ++++++++++++++++- .../getDefaultSlashMenuItems.ts | 10 +++++ .../TableHandles/TableHandlesPlugin.ts | 4 ++ .../DefaultButtons/AddButton.tsx | 8 ++++ .../DefaultButtons/DeleteButton.tsx | 8 ++++ 10 files changed, 82 insertions(+), 46 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index dad318f8a..1354dd3be 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -7,7 +7,6 @@ import { mergeBlocksCommand } from "./mergeBlocks.js"; const getEditor = setupTestEnv(); function mergeBlocks(posBetweenBlocks: number) { - // TODO: Replace with imported function after converting from TipTap command return getEditor()._tiptapEditor.commands.command( mergeBlocksCommand(posBetweenBlocks) ); diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index 65ece23d2..7cb73348a 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -3,7 +3,7 @@ import { EditorState } from "prosemirror-state"; import { getBlockInfoFromResolvedPos } from "../../../getBlockInfoFromPos.js"; -export const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { +const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { const prevNode = $nextBlockPos.nodeBefore; if (!prevNode) { @@ -32,10 +32,7 @@ export const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { return doc.resolve(prevBlockBeforePos); }; -export const canMerge = ( - $prevBlockPos: ResolvedPos, - $nextBlockPos: ResolvedPos -) => { +const canMerge = ($prevBlockPos: ResolvedPos, $nextBlockPos: ResolvedPos) => { const prevBlockInfo = getBlockInfoFromResolvedPos($prevBlockPos); const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); @@ -45,7 +42,7 @@ export const canMerge = ( ); }; -export const mergeBlocks = ( +const mergeBlocks = ( state: EditorState, dispatch: ((args?: any) => any) | undefined, $prevBlockPos: ResolvedPos, @@ -53,8 +50,7 @@ export const mergeBlocks = ( ) => { const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); - // Removes a level of nesting all children of the next block by 1 level, if it contains both content and block - // group nodes. + // Un-nests all children of the next block. if (nextBlockInfo.blockGroup) { const childBlocksStart = state.doc.resolve( nextBlockInfo.blockGroup.beforePos + 1 @@ -64,7 +60,6 @@ export const mergeBlocks = ( ); 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!, $nextBlockPos.depth); } @@ -100,9 +95,6 @@ export const mergeBlocksCommand = const $prevBlockPos = getPrevBlockPos(state.doc, $nextBlockPos); if (!canMerge($prevBlockPos, $nextBlockPos)) { - // throw new Error( - // `Attempting to merge block at position ${$nextBlockPos.pos} into previous block at position ${$prevBlockPos.pos}, but previous block has invalid content type` - // ); return false; } diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts index d38ea3622..73ffd5e44 100644 --- a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts @@ -1,8 +1,8 @@ import { Node } from "prosemirror-model"; import { Transaction } from "prosemirror-state"; -import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; import { Block } from "../../../../blocks/defaultBlocks.js"; +import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; import { BlockIdentifier, BlockSchema, diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts index 544c24d58..5d074ee1c 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -1,7 +1,7 @@ import { Node } from "prosemirror-model"; +import { TextSelection } from "prosemirror-state"; import { describe, expect, it } from "vitest"; -import { TextSelection } from "prosemirror-state"; import { getBlockInfo, getBlockInfoFromSelection, @@ -17,7 +17,6 @@ function splitBlock( keepType?: boolean, keepProps?: boolean ) { - // TODO: Replace with imported function after converting from TipTap command getEditor()._tiptapEditor.commands.command( splitBlockCommand(posInBlock, keepType, keepProps) ); diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index ab1a9c89c..3996376cc 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -1,5 +1,5 @@ import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; +import { EditorState } from "prosemirror-state"; import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; @@ -141,35 +141,17 @@ export const updateBlockCommand = // 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 - ) + state.tr.replaceWith( + blockContent.beforePos, + blockContent.afterPos, + state.schema.nodes[newType].create( + { + ...blockContent.node.attrs, + ...block.props, + }, + content ) - // TODO: This seems off - the selection is not necessarily in the block - // being updated but this will set it anyway. - // 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(blockContent.beforePos)) - : state.schema.nodes[newType].spec.content === "inline*" - ? new TextSelection(state.tr.doc.resolve(blockContent.beforePos)) - : // 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(blockContent.beforePos + 4) - ) - ); + ); } // Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index cfcf434ee..e5f3562d2 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -22,7 +22,7 @@ export type BlockInfo = { * 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. + * @param pos An integer position in the document. * @returns The position just before the nearest blockContainer node. */ export function getNearestBlockContainerPos(doc: Node, pos: number) { @@ -80,6 +80,17 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { }; } +/** + * 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 @@ -135,10 +146,27 @@ export function getBlockInfoWithManualOffset( }; } +/** + * 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( @@ -153,6 +181,12 @@ export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) { 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, diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index d931f3c23..152ca9482 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -69,6 +69,16 @@ export function insertOrUpdateBlock< } const insertedBlock = editor.getTextCursorPosition().block; + + // Edge case for table block content. Because the content type is changed, + // we have to reset text cursor position to the block as `updateBlock` moves + // the existing selection out of the block. + if (insertedBlock.content && !Array.isArray(insertedBlock.content)) { + editor.setTextCursorPosition(insertedBlock); + } + + // Edge case for updating none block content. For UX reasons, we want to move + // the selection to the next block which has content. setSelectionToNextContentEditableBlock(editor); return insertedBlock; diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 9b7b65062..dfc783a5e 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -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/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx b/packages/react/src/components/TableHandles/TableHandleMenu/DefaultButtons/AddButton.tsx index 51887e903..5f86a6839 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 fc8ebec1c..f5bd9ae7c 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} From f6277321c828a212023500e7c5d07c7a137237fd Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 15 Oct 2024 20:23:37 +0200 Subject: [PATCH 31/34] Fixed bug in `insertOrUpdateBlock` --- .../getDefaultSlashMenuItems.ts | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index 152ca9482..347daf005 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,29 +61,21 @@ export function insertOrUpdateBlock< currentBlock.content[0].text === "/") || currentBlock.content.length === 0) ) { - editor.updateBlock(currentBlock, block); - } else { - editor.insertBlocks([block], currentBlock, "after"); - editor.setTextCursorPosition( - editor.getTextCursorPosition().nextBlock!, - "end" - ); - } + newBlock = editor.updateBlock(currentBlock, block); - const insertedBlock = editor.getTextCursorPosition().block; - - // Edge case for table block content. Because the content type is changed, - // we have to reset text cursor position to the block as `updateBlock` moves - // the existing selection out of the block. - if (insertedBlock.content && !Array.isArray(insertedBlock.content)) { - editor.setTextCursorPosition(insertedBlock); + // 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 { + newBlock = editor.insertBlocks([block], currentBlock, "after")[0]; + editor.setTextCursorPosition(editor.getTextCursorPosition().nextBlock!); } - // Edge case for updating none block content. For UX reasons, we want to move - // the selection to the next block which has content. setSelectionToNextContentEditableBlock(editor); - return insertedBlock; + return newBlock; } export function getDefaultSlashMenuItems< @@ -211,6 +205,7 @@ export function getDefaultSlashMenuItems< const insertedBlock = insertOrUpdateBlock(editor, { type: "image", }); + console.log(insertedBlock.id); // Immediately open the file toolbar editor.dispatch( @@ -276,7 +271,7 @@ export function getDefaultSlashMenuItems< }) ); }, - key: "image", + key: "file", ...editor.dictionary.slash_menu.file, }); } From 3d09350748668f2c65ec9191af2746d7f25bfdcd Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 15 Oct 2024 20:30:39 +0200 Subject: [PATCH 32/34] Removed log --- .../src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index 347daf005..8a2bec0ea 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -205,7 +205,6 @@ export function getDefaultSlashMenuItems< const insertedBlock = insertOrUpdateBlock(editor, { type: "image", }); - console.log(insertedBlock.id); // Immediately open the file toolbar editor.dispatch( From 3d0e80ea7e77f91efcfa86413bbec0d720ddd5e8 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 16 Oct 2024 18:24:48 +0200 Subject: [PATCH 33/34] Tiny changes --- .../api/blockManipulation/commands/insertBlocks/insertBlocks.ts | 1 - .../blockManipulation/commands/replaceBlocks/replaceBlocks.ts | 2 +- .../api/blockManipulation/commands/updateBlock/updateBlock.ts | 1 - packages/core/src/editor/BlockNoteEditor.ts | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts index 0ae26fa40..66474cef0 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -8,7 +8,6 @@ import { InlineContentSchema, StyleSchema, } from "../../../../schema/index.js"; - import { blockToNode } from "../../../nodeConversions/blockToNode.js"; import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../../nodeUtil.js"; diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts index 27123b9b6..536c8b5d8 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -1,4 +1,5 @@ import { Node } from "prosemirror-model"; + import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; import { @@ -7,7 +8,6 @@ import { InlineContentSchema, StyleSchema, } from "../../../../schema/index.js"; - import { blockToNode } from "../../../nodeConversions/blockToNode.js"; import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; import { removeBlocksWithCallback } from "../removeBlocks/removeBlocks.js"; diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 112ee8a61..9536e6a9a 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -11,7 +11,6 @@ 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, diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index f30004d6e..0b16a2cb4 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -18,7 +18,6 @@ import { import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js"; import { getBlockInfoFromSelection } from "../api/getBlockInfoFromPos.js"; - import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js"; import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown.js"; import { From ab902c9d120442e67003e99d5357d27468331af7 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Thu, 17 Oct 2024 12:22:21 +0200 Subject: [PATCH 34/34] Fixed merge/delete behaviour on Backspace --- .../__snapshots__/insertBlocks.test.ts.snap | 168 +++++ .../__snapshots__/mergeBlocks.test.ts.snap | 572 ++++++++++++++++++ .../commands/mergeBlocks/mergeBlocks.test.ts | 18 + .../commands/mergeBlocks/mergeBlocks.ts | 7 +- .../__snapshots__/moveBlock.test.ts.snap | 224 +++++++ .../__snapshots__/removeBlocks.test.ts.snap | 84 +++ .../__snapshots__/replaceBlocks.test.ts.snap | 336 ++++++++++ .../__snapshots__/splitBlock.test.ts.snap | 168 +++++ .../__snapshots__/updateBlock.test.ts.snap | 504 +++++++++++++++ .../src/api/blockManipulation/setupTestEnv.ts | 10 + .../KeyboardShortcutsExtension.ts | 61 +- 11 files changed, 2127 insertions(+), 25 deletions(-) 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 index ca7ba1f56..4bac28e44 100644 --- 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 @@ -412,6 +412,34 @@ exports[`Test insertBlocks > Insert multiple blocks after 1`] = ` }, "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": [ { @@ -906,6 +934,34 @@ exports[`Test insertBlocks > Insert multiple blocks before 1`] = ` }, "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": [ { @@ -1360,6 +1416,34 @@ exports[`Test insertBlocks > Insert single basic block after 1`] = ` }, "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": [ { @@ -1814,6 +1898,34 @@ exports[`Test insertBlocks > Insert single basic block before 1`] = ` }, "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": [ { @@ -2325,6 +2437,34 @@ exports[`Test insertBlocks > Insert single complex block after 1`] = ` }, "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": [ { @@ -2836,6 +2976,34 @@ exports[`Test insertBlocks > Insert single complex block before 1`] = ` }, "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": [ { diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index 4f2ecd592..6b2592770 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -344,6 +344,34 @@ exports[`Test mergeBlocks > Basic 1`] = ` }, "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": [ { @@ -770,6 +798,34 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` }, "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": [ { @@ -1196,6 +1252,34 @@ exports[`Test mergeBlocks > First block has children 1`] = ` }, "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": [ { @@ -1621,6 +1705,494 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` }, "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 mergeBlocks > Second block is empty 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": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [ { diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 1354dd3be..4e33a3b1e 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -42,6 +42,14 @@ describe("Test mergeBlocks", () => { 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"); @@ -71,6 +79,16 @@ describe("Test mergeBlocks", () => { // 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"); diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index 7cb73348a..7af11571d 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -3,7 +3,7 @@ import { EditorState } from "prosemirror-state"; import { getBlockInfoFromResolvedPos } from "../../../getBlockInfoFromPos.js"; -const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { +export const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { const prevNode = $nextBlockPos.nodeBefore; if (!prevNode) { @@ -38,7 +38,8 @@ const canMerge = ($prevBlockPos: ResolvedPos, $nextBlockPos: ResolvedPos) => { return ( prevBlockInfo.blockContent.node.type.spec.content === "inline*" && - nextBlockInfo.blockContent.node.type.spec.content === "inline*" + nextBlockInfo.blockContent.node.type.spec.content === "inline*" && + prevBlockInfo.blockContent.node.childCount > 0 ); }; @@ -72,7 +73,7 @@ const mergeBlocks = ( const prevBlockInfo = getBlockInfoFromResolvedPos($prevBlockPos); dispatch( - state.tr.deleteRange( + state.tr.delete( prevBlockInfo.blockContent.afterPos - 1, nextBlockInfo.blockContent.beforePos + 1 ) 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 index b83261ae3..558e863c7 100644 --- 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 @@ -361,6 +361,34 @@ exports[`Test moveBlockDown > Basic 1`] = ` }, "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": [ { @@ -804,6 +832,34 @@ exports[`Test moveBlockDown > Into children 1`] = ` }, "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": [ { @@ -1247,6 +1303,34 @@ exports[`Test moveBlockDown > Last block 1`] = ` }, "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": [ { @@ -1690,6 +1774,34 @@ exports[`Test moveBlockDown > Out of children 1`] = ` }, "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": [ @@ -2132,6 +2244,34 @@ exports[`Test moveBlockUp > Basic 1`] = ` }, "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": [ { @@ -2575,6 +2715,34 @@ exports[`Test moveBlockUp > First block 1`] = ` }, "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": [ { @@ -3018,6 +3186,34 @@ exports[`Test moveBlockUp > Into children 1`] = ` }, "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": [ { @@ -3461,6 +3657,34 @@ exports[`Test moveBlockUp > Out of children 1`] = ` }, "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": [ { 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 index 0246521a4..68c3cbfa1 100644 --- 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 @@ -274,6 +274,34 @@ exports[`Test removeBlocks > Remove multiple consecutive blocks 1`] = ` }, "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": [ { @@ -611,6 +639,34 @@ exports[`Test removeBlocks > Remove multiple non-consecutive blocks 1`] = ` }, "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": [], @@ -969,6 +1025,34 @@ exports[`Test removeBlocks > Remove single block 1`] = ` }, "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": [ { 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 index 0026f59c2..add46c028 100644 --- 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 @@ -274,6 +274,34 @@ exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = ` }, "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": [ { @@ -611,6 +639,34 @@ exports[`Test replaceBlocks > Remove multiple non-consecutive blocks 1`] = ` }, "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": [], @@ -969,6 +1025,34 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` }, "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": [ { @@ -1376,6 +1460,34 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple }, "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": [ { @@ -1743,6 +1855,34 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single ba }, "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": [ { @@ -2167,6 +2307,34 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single co }, "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": [ { @@ -2555,6 +2723,34 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with multi }, "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": [], @@ -2835,6 +3031,34 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl }, "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": [], @@ -3172,6 +3396,34 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl }, "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": [], @@ -3581,6 +3833,34 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` }, "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": [ { @@ -4018,6 +4298,34 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` }, "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": [ { @@ -4512,6 +4820,34 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` }, "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": [ { 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 index fb9f0269e..d308e0046 100644 --- 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 @@ -378,6 +378,34 @@ exports[`Test splitBlocks > Basic 1`] = ` }, "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": [ { @@ -838,6 +866,34 @@ exports[`Test splitBlocks > Block has children 1`] = ` }, "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": [ { @@ -1298,6 +1354,34 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` }, "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": [ { @@ -1758,6 +1842,34 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` }, "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": [ { @@ -2212,6 +2324,34 @@ exports[`Test splitBlocks > End of content 1`] = ` }, "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": [ { @@ -2673,6 +2813,34 @@ exports[`Test splitBlocks > Keep type 1`] = ` }, "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": [ { 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 index 918fca62e..a069e613a 100644 --- 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 @@ -361,6 +361,34 @@ exports[`Test updateBlock > Revert all props 1`] = ` }, "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": [ { @@ -804,6 +832,34 @@ exports[`Test updateBlock > Revert single prop 1`] = ` }, "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": [ { @@ -1247,6 +1303,34 @@ exports[`Test updateBlock > Update all props 1`] = ` }, "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": [ { @@ -1690,6 +1774,34 @@ exports[`Test updateBlock > Update children 1`] = ` }, "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": [ { @@ -2129,6 +2241,34 @@ exports[`Test updateBlock > Update inline content to empty table content 1`] = ` }, "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": [ { @@ -2570,6 +2710,34 @@ exports[`Test updateBlock > Update inline content to no content 1`] = ` }, "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": [ { @@ -3085,6 +3253,34 @@ exports[`Test updateBlock > Update inline content to table content 1`] = ` }, "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": [ { @@ -3524,6 +3720,34 @@ exports[`Test updateBlock > Update no content to empty inline content 1`] = ` }, "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": [ { @@ -3965,6 +4189,34 @@ exports[`Test updateBlock > Update no content to empty table content 1`] = ` }, "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": [ { @@ -4410,6 +4662,34 @@ exports[`Test updateBlock > Update no content to inline content 1`] = ` }, "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": [ { @@ -4927,6 +5207,34 @@ exports[`Test updateBlock > Update no content to table content 1`] = ` }, "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": [ { @@ -5370,6 +5678,34 @@ exports[`Test updateBlock > Update single prop 1`] = ` }, "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": [ { @@ -5735,6 +6071,34 @@ exports[`Test updateBlock > Update table content to empty inline content 1`] = ` }, "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": [ { @@ -6106,6 +6470,34 @@ exports[`Test updateBlock > Update table content to inline content 1`] = ` }, "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": [ { @@ -6475,6 +6867,34 @@ exports[`Test updateBlock > Update table content to no content 1`] = ` }, "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": [ { @@ -6918,6 +7338,34 @@ exports[`Test updateBlock > Update type 1`] = ` }, "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": [ { @@ -7360,6 +7808,34 @@ exports[`Test updateBlock > Update with plain content 1`] = ` }, "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": [ { @@ -7789,6 +8265,34 @@ exports[`Test updateBlock > Update with styled content 1`] = ` }, "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": [ { diff --git a/packages/core/src/api/blockManipulation/setupTestEnv.ts b/packages/core/src/api/blockManipulation/setupTestEnv.ts index a1c4ceca9..318281649 100644 --- a/packages/core/src/api/blockManipulation/setupTestEnv.ts +++ b/packages/core/src/api/blockManipulation/setupTestEnv.ts @@ -133,6 +133,16 @@ const testDocument: PartialBlock[] = [ 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", diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 2810663e2..b64782d76 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -1,7 +1,10 @@ import { Extension } from "@tiptap/core"; import { TextSelection } from "prosemirror-state"; -import { mergeBlocksCommand } from "../../api/blockManipulation/commands/mergeBlocks/mergeBlocks.js"; +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 { @@ -95,42 +98,56 @@ export const KeyboardShortcutsExtension = Extension.create<{ return false; }), - // Deletes previous block if it contains no content, when the selection - // is empty and at the start of the block. The previous block also has - // to not have any parents or children, as otherwise the UX becomes - // confusing. + // 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 { blockContainer, blockContent } = - getBlockInfoFromSelection(state); + const blockInfo = getBlockInfoFromSelection(state); + + const { depth } = state.doc.resolve( + blockInfo.blockContainer.beforePos + ); const selectionAtBlockStart = - state.selection.from === blockContent.beforePos + 1; + state.selection.from === blockInfo.blockContent.beforePos + 1; const selectionEmpty = state.selection.empty; - const blockAtDocStart = blockContainer.beforePos === 1; + const blockAtDocStart = blockInfo.blockContainer.beforePos === 1; - const $currentBlockPos = state.doc.resolve( - blockContainer.beforePos - ); - const prevBlockPos = $currentBlockPos.posAtIndex( - $currentBlockPos.index() - 1 + const prevBlockPos = getPrevBlockPos( + state.doc, + state.doc.resolve(blockInfo.blockContainer.beforePos) ); const prevBlockInfo = getBlockInfoFromResolvedPos( - state.doc.resolve(prevBlockPos) + 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 && - $currentBlockPos.depth === 1 && - prevBlockInfo.blockGroup === undefined && - prevBlockInfo.blockContent.node.type.spec.content === "" + depth === 1 && + prevBlockNotTableAndNoContent ) { - return commands.deleteRange({ - from: prevBlockPos, - to: $currentBlockPos.pos, - }); + 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;