UNPKG

@blocknote/core

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

1 lines 336 kB
{"version":3,"file":"blocknote.cjs","sources":["../src/schema/inlineContent/createSpec.ts","../src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts","../src/api/blockManipulation/commands/replaceBlocks/util/fixColumnList.ts","../src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts","../src/api/exporters/html/util/serializeBlocksInternalHTML.ts","../src/api/exporters/html/internalHTMLSerializer.ts","../src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts","../src/api/blockManipulation/commands/nestBlock/nestBlock.ts","../src/api/blockManipulation/getBlock/getBlock.ts","../src/editor/managers/BlockManager.ts","../src/editor/managers/EventManager.ts","../src/api/parsers/html/util/nestedLists.ts","../src/api/parsers/html/parseHTML.ts","../src/api/parsers/markdown/parseMarkdown.ts","../src/editor/managers/ExportManager.ts","../src/api/clipboard/fromClipboard/acceptedMIMETypes.ts","../src/api/clipboard/fromClipboard/handleFileInsertion.ts","../src/api/clipboard/fromClipboard/fileDropExtension.ts","../src/api/parsers/markdown/detectMarkdown.ts","../src/api/clipboard/fromClipboard/handleVSCodePaste.ts","../src/api/clipboard/fromClipboard/pasteExtension.ts","../src/api/clipboard/toClipboard/copyExtension.ts","../src/extensions/tiptap-extensions/BackgroundColor/BackgroundColorExtension.ts","../src/extensions/tiptap-extensions/HardBreak/HardBreak.ts","../src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts","../src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts","../src/extensions/tiptap-extensions/Suggestions/SuggestionMarks.ts","../src/extensions/tiptap-extensions/TextAlignment/TextAlignmentExtension.ts","../src/extensions/tiptap-extensions/TextColor/TextColorExtension.ts","../src/pm-nodes/BlockContainer.ts","../src/pm-nodes/BlockGroup.ts","../src/pm-nodes/Doc.ts","../src/extensions/Collaboration/Collaboration.ts","../src/editor/managers/ExtensionManager/extensions.ts","../src/editor/managers/ExtensionManager/index.ts","../src/util/expandToWords.ts","../src/api/blockManipulation/selections/selection.ts","../src/api/blockManipulation/selections/textCursorPosition.ts","../src/editor/managers/SelectionManager.ts","../src/editor/managers/StateManager.ts","../src/api/blockManipulation/insertContentAt.ts","../src/editor/managers/StyleManager.ts","../src/editor/transformPasted.ts","../src/editor/BlockNoteEditor.ts","../src/exporter/Exporter.ts","../src/exporter/mapping.ts","../src/util/combineByGroup.ts"],"sourcesContent":["import { Node } from \"@tiptap/core\";\n\nimport { TagParseRule } from \"@tiptap/pm/model\";\nimport { inlineContentToNodes } from \"../../api/nodeConversions/blockToNode.js\";\nimport { nodeToCustomInlineContent } from \"../../api/nodeConversions/nodeToBlock.js\";\nimport type { BlockNoteEditor } from \"../../editor/BlockNoteEditor.js\";\nimport { propsToAttributes } from \"../blocks/internal.js\";\nimport { Props } from \"../propTypes.js\";\nimport { StyleSchema } from \"../styles/types.js\";\nimport {\n addInlineContentAttributes,\n addInlineContentKeyboardShortcuts,\n createInlineContentSpecFromTipTapNode,\n} from \"./internal.js\";\nimport {\n CustomInlineContentConfig,\n InlineContentFromConfig,\n InlineContentSpec,\n PartialCustomInlineContentFromConfig,\n} from \"./types.js\";\n\nexport type CustomInlineContentImplementation<\n T extends CustomInlineContentConfig,\n S extends StyleSchema,\n> = {\n meta?: {\n draggable?: boolean;\n };\n\n /**\n * Parses an external HTML element into a inline content of this type when it returns the block props object, otherwise undefined\n */\n parse?: (el: HTMLElement) => Partial<Props<T[\"propSchema\"]>> | undefined;\n\n /**\n * Renders an inline content to DOM elements\n */\n render: (\n /**\n * The custom inline content to render\n */\n inlineContent: InlineContentFromConfig<T, S>,\n /**\n * A callback that allows overriding the inline content element\n */\n updateInlineContent: (\n update: PartialCustomInlineContentFromConfig<T, S>,\n ) => void,\n /**\n * The BlockNote editor instance\n * This is typed generically. If you want an editor with your custom schema, you need to\n * cast it manually, e.g.: `const e = editor as BlockNoteEditor<typeof mySchema>;`\n */\n editor: BlockNoteEditor<any, any, S>,\n // (note) if we want to fix the manual cast, we need to prevent circular references and separate block definition and render implementations\n // or allow manually passing <BSchema>, but that's not possible without passing the other generics because Typescript doesn't support partial inferred generics\n ) => {\n dom: HTMLElement;\n contentDOM?: HTMLElement;\n destroy?: () => void;\n };\n\n /**\n * Renders an inline content to external HTML elements for use outside the editor\n * If not provided, falls back to the render method\n */\n toExternalHTML?: (\n /**\n * The custom inline content to render\n */\n inlineContent: InlineContentFromConfig<T, S>,\n /**\n * The BlockNote editor instance\n * This is typed generically. If you want an editor with your custom schema, you need to\n * cast it manually, e.g.: `const e = editor as BlockNoteEditor<typeof mySchema>;`\n */\n editor: BlockNoteEditor<any, any, S>,\n ) =>\n | {\n dom: HTMLElement | DocumentFragment;\n contentDOM?: HTMLElement;\n }\n | undefined;\n\n runsBefore?: string[];\n};\n\nexport function getInlineContentParseRules<C extends CustomInlineContentConfig>(\n config: C,\n customParseFunction?: CustomInlineContentImplementation<C, any>[\"parse\"],\n) {\n const rules: TagParseRule[] = [\n {\n tag: `[data-inline-content-type=\"${config.type}\"]`,\n contentElement: (element) => {\n const htmlElement = element as HTMLElement;\n\n if (htmlElement.matches(\"[data-editable]\")) {\n return htmlElement;\n }\n\n return htmlElement.querySelector(\"[data-editable]\") || htmlElement;\n },\n },\n ];\n\n if (customParseFunction) {\n rules.push({\n tag: \"*\",\n getAttrs(node: string | HTMLElement) {\n if (typeof node === \"string\") {\n return false;\n }\n\n const props = customParseFunction?.(node);\n\n if (props === undefined) {\n return false;\n }\n\n return props;\n },\n });\n }\n return rules;\n}\n\nexport function createInlineContentSpec<\n T extends CustomInlineContentConfig,\n S extends StyleSchema,\n>(\n inlineContentConfig: T,\n inlineContentImplementation: CustomInlineContentImplementation<T, S>,\n): InlineContentSpec<T> {\n const node = Node.create({\n name: inlineContentConfig.type,\n inline: true,\n group: \"inline\",\n draggable: inlineContentImplementation.meta?.draggable,\n selectable: inlineContentConfig.content === \"styled\",\n atom: inlineContentConfig.content === \"none\",\n content: inlineContentConfig.content === \"styled\" ? \"inline*\" : \"\",\n\n addAttributes() {\n return propsToAttributes(inlineContentConfig.propSchema);\n },\n\n addKeyboardShortcuts() {\n return addInlineContentKeyboardShortcuts(inlineContentConfig);\n },\n\n parseHTML() {\n return getInlineContentParseRules(\n inlineContentConfig,\n inlineContentImplementation.parse,\n );\n },\n\n renderHTML({ node }) {\n const editor = this.options.editor;\n\n const output = inlineContentImplementation.render.call(\n { renderType: \"dom\", props: undefined },\n nodeToCustomInlineContent(\n node,\n editor.schema.inlineContentSchema,\n editor.schema.styleSchema,\n ) as any as InlineContentFromConfig<T, S>, // TODO: fix cast\n () => {\n // No-op\n },\n editor,\n );\n\n return addInlineContentAttributes(\n output,\n inlineContentConfig.type,\n node.attrs as Props<T[\"propSchema\"]>,\n inlineContentConfig.propSchema,\n );\n },\n\n addNodeView() {\n return (props) => {\n const { node, getPos } = props;\n const editor = this.options.editor as BlockNoteEditor<any, any, S>;\n\n const output = inlineContentImplementation.render.call(\n { renderType: \"nodeView\", props },\n nodeToCustomInlineContent(\n node,\n editor.schema.inlineContentSchema,\n editor.schema.styleSchema,\n ) as any as InlineContentFromConfig<T, S>, // TODO: fix cast\n (update) => {\n const content = inlineContentToNodes([update], editor.pmSchema);\n\n const pos = getPos();\n\n if (!pos) {\n return;\n }\n\n editor.transact((tr) =>\n tr.replaceWith(pos, pos + node.nodeSize, content),\n );\n },\n editor,\n );\n\n return addInlineContentAttributes(\n output,\n inlineContentConfig.type,\n node.attrs as Props<T[\"propSchema\"]>,\n inlineContentConfig.propSchema,\n );\n };\n },\n });\n\n return createInlineContentSpecFromTipTapNode(\n node,\n inlineContentConfig.propSchema,\n {\n ...inlineContentImplementation,\n toExternalHTML: inlineContentImplementation.toExternalHTML,\n render(inlineContent, updateInlineContent, editor) {\n const output = inlineContentImplementation.render(\n inlineContent,\n updateInlineContent,\n editor,\n );\n\n return addInlineContentAttributes(\n output,\n inlineContentConfig.type,\n inlineContent.props,\n inlineContentConfig.propSchema,\n );\n },\n },\n ) as InlineContentSpec<T>;\n}\n","import { Fragment, Slice } from \"prosemirror-model\";\nimport type { Transaction } from \"prosemirror-state\";\nimport { ReplaceStep } from \"prosemirror-transform\";\nimport { Block, PartialBlock } from \"../../../../blocks/defaultBlocks.js\";\nimport {\n BlockIdentifier,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../../schema/index.js\";\nimport { blockToNode } from \"../../../nodeConversions/blockToNode.js\";\nimport { nodeToBlock } from \"../../../nodeConversions/nodeToBlock.js\";\nimport { getNodeById } from \"../../../nodeUtil.js\";\nimport { getPmSchema } from \"../../../pmUtil.js\";\n\nexport function insertBlocks<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n tr: Transaction,\n blocksToInsert: PartialBlock<BSchema, I, S>[],\n referenceBlock: BlockIdentifier,\n placement: \"before\" | \"after\" = \"before\",\n): Block<BSchema, I, S>[] {\n const id =\n typeof referenceBlock === \"string\" ? referenceBlock : referenceBlock.id;\n const pmSchema = getPmSchema(tr);\n const nodesToInsert = blocksToInsert.map((block) =>\n blockToNode(block, pmSchema),\n );\n\n const posInfo = getNodeById(id, tr.doc);\n if (!posInfo) {\n throw new Error(`Block with ID ${id} not found`);\n }\n\n let pos = posInfo.posBeforeNode;\n if (placement === \"after\") {\n pos += posInfo.node.nodeSize;\n }\n\n tr.step(\n new ReplaceStep(pos, pos, new Slice(Fragment.from(nodesToInsert), 0, 0)),\n );\n\n // Now that the `PartialBlock`s have been converted to nodes, we can\n // re-convert them into full `Block`s.\n const insertedBlocks = nodesToInsert.map((node) =>\n nodeToBlock(node, pmSchema),\n ) as Block<BSchema, I, S>[];\n\n return insertedBlocks;\n}\n","import { Slice, type Node } from \"prosemirror-model\";\nimport { type Transaction } from \"prosemirror-state\";\nimport { ReplaceAroundStep } from \"prosemirror-transform\";\n\n/**\n * Checks if a `column` node is empty, i.e. if it has only a single empty\n * paragraph.\n * @param column The column to check.\n * @returns Whether the column is empty.\n */\nexport function isEmptyColumn(column: Node) {\n if (!column || column.type.name !== \"column\") {\n throw new Error(\"Invalid columnPos: does not point to column node.\");\n }\n\n const blockContainer = column.firstChild;\n if (!blockContainer) {\n throw new Error(\"Invalid column: does not have child node.\");\n }\n\n const blockContent = blockContainer.firstChild;\n if (!blockContent) {\n throw new Error(\"Invalid blockContainer: does not have child node.\");\n }\n\n return (\n column.childCount === 1 &&\n blockContainer.childCount === 1 &&\n blockContent.type.name === \"paragraph\" &&\n blockContent.content.content.length === 0\n );\n}\n\n/**\n * Removes all empty `column` nodes in a `columnList`. A `column` node is empty\n * if it has only a single empty block. If, however, removing the `column`s\n * leaves the `columnList` that has fewer than two, ProseMirror will re-add\n * empty columns.\n * @param tr The `Transaction` to add the changes to.\n * @param columnListPos The position just before the `columnList` node.\n */\nexport function removeEmptyColumns(tr: Transaction, columnListPos: number) {\n const $columnListPos = tr.doc.resolve(columnListPos);\n const columnList = $columnListPos.nodeAfter;\n if (!columnList || columnList.type.name !== \"columnList\") {\n throw new Error(\n \"Invalid columnListPos: does not point to columnList node.\",\n );\n }\n\n for (\n let columnIndex = columnList.childCount - 1;\n columnIndex >= 0;\n columnIndex--\n ) {\n const columnPos = tr.doc\n .resolve($columnListPos.pos + 1)\n .posAtIndex(columnIndex);\n const $columnPos = tr.doc.resolve(columnPos);\n const column = $columnPos.nodeAfter;\n if (!column || column.type.name !== \"column\") {\n throw new Error(\"Invalid columnPos: does not point to column node.\");\n }\n\n if (isEmptyColumn(column)) {\n tr.delete(columnPos, columnPos + column.nodeSize);\n }\n }\n}\n\n/**\n * Fixes potential issues in a `columnList` node after a\n * `blockContainer`/`column` node is (re)moved from it:\n *\n * - Removes all empty `column` nodes. A `column` node is empty if it has only\n * a single empty block.\n * - If all but one `column` nodes are empty, replaces the `columnList` with\n * the content of the non-empty `column`.\n * - If all `column` nodes are empty, removes the `columnList` entirely.\n * @param tr The `Transaction` to add the changes to.\n * @param columnListPos\n * @returns The position just before the `columnList` node.\n */\nexport function fixColumnList(tr: Transaction, columnListPos: number) {\n removeEmptyColumns(tr, columnListPos);\n\n const $columnListPos = tr.doc.resolve(columnListPos);\n const columnList = $columnListPos.nodeAfter;\n if (!columnList || columnList.type.name !== \"columnList\") {\n throw new Error(\n \"Invalid columnListPos: does not point to columnList node.\",\n );\n }\n\n if (columnList.childCount > 2) {\n // Do nothing if the `columnList` has more than two non-empty `column`s. In\n // the case that the `columnList` has exactly two columns, we may need to\n // still remove it, as it's possible that one or both columns are empty.\n // This is because after `removeEmptyColumns` is called, if the\n // `columnList` has fewer than two `column`s, ProseMirror will re-add empty\n // `column`s until there are two total, in order to fit the schema.\n return;\n }\n\n if (columnList.childCount < 2) {\n // Throw an error if the `columnList` has fewer than two columns. After\n // `removeEmptyColumns` is called, if the `columnList` has fewer than two\n // `column`s, ProseMirror will re-add empty `column`s until there are two\n // total, in order to fit the schema. So if there are fewer than two here,\n // either the schema, or ProseMirror's internals, must have changed.\n throw new Error(\"Invalid columnList: contains fewer than two children.\");\n }\n\n const firstColumnBeforePos = columnListPos + 1;\n const $firstColumnBeforePos = tr.doc.resolve(firstColumnBeforePos);\n const firstColumn = $firstColumnBeforePos.nodeAfter;\n\n const lastColumnAfterPos = columnListPos + columnList.nodeSize - 1;\n const $lastColumnAfterPos = tr.doc.resolve(lastColumnAfterPos);\n const lastColumn = $lastColumnAfterPos.nodeBefore;\n\n if (!firstColumn || !lastColumn) {\n throw new Error(\"Invalid columnList: does not contain children.\");\n }\n\n const firstColumnEmpty = isEmptyColumn(firstColumn);\n const lastColumnEmpty = isEmptyColumn(lastColumn);\n\n if (firstColumnEmpty && lastColumnEmpty) {\n // Removes `columnList`\n tr.delete(columnListPos, columnListPos + columnList.nodeSize);\n\n return;\n }\n\n if (firstColumnEmpty) {\n tr.step(\n new ReplaceAroundStep(\n // Replaces `columnList`.\n columnListPos,\n columnListPos + columnList.nodeSize,\n // Replaces with content of last `column`.\n lastColumnAfterPos - lastColumn.nodeSize + 1,\n lastColumnAfterPos - 1,\n // Doesn't append anything.\n Slice.empty,\n 0,\n false,\n ),\n );\n\n return;\n }\n\n if (lastColumnEmpty) {\n tr.step(\n new ReplaceAroundStep(\n // Replaces `columnList`.\n columnListPos,\n columnListPos + columnList.nodeSize,\n // Replaces with content of first `column`.\n firstColumnBeforePos + 1,\n firstColumnBeforePos + firstColumn.nodeSize - 1,\n // Doesn't append anything.\n Slice.empty,\n 0,\n false,\n ),\n );\n\n return;\n }\n}\n","import { type Node } from \"prosemirror-model\";\nimport { type Transaction } from \"prosemirror-state\";\nimport type { Block, PartialBlock } from \"../../../../blocks/defaultBlocks.js\";\nimport type {\n BlockIdentifier,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../../schema/index.js\";\nimport { blockToNode } from \"../../../nodeConversions/blockToNode.js\";\nimport { nodeToBlock } from \"../../../nodeConversions/nodeToBlock.js\";\nimport { getPmSchema } from \"../../../pmUtil.js\";\nimport { fixColumnList } from \"./util/fixColumnList.js\";\n\nexport function removeAndInsertBlocks<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n tr: Transaction,\n blocksToRemove: BlockIdentifier[],\n blocksToInsert: PartialBlock<BSchema, I, S>[],\n): {\n insertedBlocks: Block<BSchema, I, S>[];\n removedBlocks: Block<BSchema, I, S>[];\n} {\n const pmSchema = getPmSchema(tr);\n // Converts the `PartialBlock`s to ProseMirror nodes to insert them into the\n // document.\n const nodesToInsert: Node[] = blocksToInsert.map((block) =>\n blockToNode(block, pmSchema),\n );\n\n const idsOfBlocksToRemove = new Set<string>(\n blocksToRemove.map((block) =>\n typeof block === \"string\" ? block : block.id,\n ),\n );\n const removedBlocks: Block<BSchema, I, S>[] = [];\n const columnListPositions = new Set<number>();\n\n const idOfFirstBlock =\n typeof blocksToRemove[0] === \"string\"\n ? blocksToRemove[0]\n : blocksToRemove[0].id;\n let removedSize = 0;\n\n tr.doc.descendants((node, pos) => {\n // Skips traversing nodes after all target blocks have been removed.\n if (idsOfBlocksToRemove.size === 0) {\n return false;\n }\n\n // Keeps traversing nodes if block with target ID has not been found.\n if (\n !node.type.isInGroup(\"bnBlock\") ||\n !idsOfBlocksToRemove.has(node.attrs.id)\n ) {\n return true;\n }\n\n // Saves the block that is being deleted.\n removedBlocks.push(nodeToBlock(node, pmSchema));\n idsOfBlocksToRemove.delete(node.attrs.id);\n\n if (blocksToInsert.length > 0 && node.attrs.id === idOfFirstBlock) {\n const oldDocSize = tr.doc.nodeSize;\n tr.insert(pos, nodesToInsert);\n const newDocSize = tr.doc.nodeSize;\n\n removedSize += oldDocSize - newDocSize;\n }\n\n const oldDocSize = tr.doc.nodeSize;\n\n const $pos = tr.doc.resolve(pos - removedSize);\n\n if ($pos.node().type.name === \"column\") {\n columnListPositions.add($pos.before(-1));\n } else if ($pos.node().type.name === \"columnList\") {\n columnListPositions.add($pos.before());\n }\n\n if (\n $pos.node().type.name === \"blockGroup\" &&\n $pos.node($pos.depth - 1).type.name !== \"doc\" &&\n $pos.node().childCount === 1\n ) {\n // Checks if the block is the only child of a parent `blockGroup` node.\n // In this case, we need to delete the parent `blockGroup` node instead\n // of just the `blockContainer`.\n tr.delete($pos.before(), $pos.after());\n } else {\n tr.delete(pos - removedSize, pos - removedSize + node.nodeSize);\n }\n\n const newDocSize = tr.doc.nodeSize;\n removedSize += oldDocSize - newDocSize;\n\n return false;\n });\n\n // Throws an error if not all blocks could be found.\n if (idsOfBlocksToRemove.size > 0) {\n const notFoundIds = [...idsOfBlocksToRemove].join(\"\\n\");\n\n throw Error(\n \"Blocks with the following IDs could not be found in the editor: \" +\n notFoundIds,\n );\n }\n\n columnListPositions.forEach((pos) => fixColumnList(tr, pos));\n\n // Converts the nodes created from `blocksToInsert` into full `Block`s.\n const insertedBlocks = nodesToInsert.map((node) =>\n nodeToBlock(node, pmSchema),\n ) as Block<BSchema, I, S>[];\n\n return { insertedBlocks, removedBlocks };\n}\n","import { DOMSerializer, Fragment, Node } from \"prosemirror-model\";\n\nimport { PartialBlock } from \"../../../../blocks/defaultBlocks.js\";\nimport type { BlockNoteEditor } from \"../../../../editor/BlockNoteEditor.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../../schema/index.js\";\nimport { UnreachableCaseError } from \"../../../../util/typescript.js\";\nimport {\n inlineContentToNodes,\n tableContentToNodes,\n} from \"../../../nodeConversions/blockToNode.js\";\n\nimport { nodeToCustomInlineContent } from \"../../../nodeConversions/nodeToBlock.js\";\nexport function serializeInlineContentInternalHTML<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<any, I, S>,\n blockContent: PartialBlock<BSchema, I, S>[\"content\"],\n serializer: DOMSerializer,\n blockType?: string,\n options?: { document?: Document },\n) {\n let nodes: Node[];\n\n // TODO: reuse function from nodeconversions?\n if (!blockContent) {\n throw new Error(\"blockContent is required\");\n } else if (typeof blockContent === \"string\") {\n nodes = inlineContentToNodes([blockContent], editor.pmSchema, blockType);\n } else if (Array.isArray(blockContent)) {\n nodes = inlineContentToNodes(blockContent, editor.pmSchema, blockType);\n } else if (blockContent.type === \"tableContent\") {\n nodes = tableContentToNodes(blockContent, editor.pmSchema);\n } else {\n throw new UnreachableCaseError(blockContent.type);\n }\n\n // Check if any of the nodes are custom inline content with toExternalHTML\n const doc = options?.document ?? document;\n const fragment = doc.createDocumentFragment();\n\n for (const node of nodes) {\n // Check if this is a custom inline content node with toExternalHTML\n if (\n node.type.name !== \"text\" &&\n editor.schema.inlineContentSchema[node.type.name]\n ) {\n const inlineContentImplementation =\n editor.schema.inlineContentSpecs[node.type.name].implementation;\n\n if (inlineContentImplementation) {\n // Convert the node to inline content format\n const inlineContent = nodeToCustomInlineContent(\n node,\n editor.schema.inlineContentSchema,\n editor.schema.styleSchema,\n );\n\n // Use the custom toExternalHTML method\n const output = inlineContentImplementation.render.call(\n {\n renderType: \"dom\",\n props: undefined,\n },\n inlineContent as any,\n () => {\n // No-op\n },\n editor as any,\n );\n\n if (output) {\n fragment.appendChild(output.dom);\n\n // If contentDOM exists, render the inline content into it\n if (output.contentDOM) {\n const contentFragment = serializer.serializeFragment(\n node.content,\n options,\n );\n output.contentDOM.dataset.editable = \"\";\n output.contentDOM.appendChild(contentFragment);\n }\n continue;\n }\n }\n } else if (node.type.name === \"text\") {\n // We serialize text nodes manually as we need to serialize the styles/\n // marks using `styleSpec.implementation.render`. When left up to\n // ProseMirror, it'll use `toDOM` which is incorrect.\n let dom: globalThis.Node | Text = document.createTextNode(\n node.textContent,\n );\n // Reverse the order of marks to maintain the correct priority.\n for (const mark of node.marks.toReversed()) {\n if (mark.type.name in editor.schema.styleSpecs) {\n const newDom = editor.schema.styleSpecs[\n mark.type.name\n ].implementation.render(mark.attrs[\"stringValue\"], editor);\n newDom.contentDOM!.appendChild(dom);\n dom = newDom.dom;\n } else {\n const domOutputSpec = mark.type.spec.toDOM!(mark, true);\n const newDom = DOMSerializer.renderSpec(document, domOutputSpec);\n newDom.contentDOM!.appendChild(dom);\n dom = newDom.dom;\n }\n }\n\n fragment.appendChild(dom);\n } else {\n // Fall back to default serialization for this node\n const nodeFragment = serializer.serializeFragment(\n Fragment.from([node]),\n options,\n );\n fragment.appendChild(nodeFragment);\n }\n }\n\n return fragment;\n}\n\nfunction serializeBlock<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, I, S>,\n block: PartialBlock<BSchema, I, S>,\n serializer: DOMSerializer,\n options?: { document?: Document },\n) {\n const BC_NODE = editor.pmSchema.nodes[\"blockContainer\"];\n\n // set default props in case we were passed a partial block\n const props = block.props || {};\n for (const [name, spec] of Object.entries(\n editor.schema.blockSchema[block.type as any].propSchema,\n )) {\n if (!(name in props) && spec.default !== undefined) {\n (props as any)[name] = spec.default;\n }\n }\n const children = block.children || [];\n\n const impl = editor.blockImplementations[block.type as any].implementation;\n const ret = impl.render.call(\n {\n renderType: \"dom\",\n props: undefined,\n },\n { ...block, props, children } as any,\n editor as any,\n );\n\n if (ret.contentDOM && block.content) {\n const ic = serializeInlineContentInternalHTML(\n editor,\n block.content as any, // TODO\n serializer,\n block.type,\n options,\n );\n ret.contentDOM.appendChild(ic);\n }\n\n const pmType = editor.pmSchema.nodes[block.type as any];\n\n if (pmType.isInGroup(\"bnBlock\")) {\n if (block.children && block.children.length > 0) {\n const fragment = serializeBlocks(\n editor,\n block.children,\n serializer,\n options,\n );\n\n ret.contentDOM?.append(fragment);\n }\n return ret.dom;\n }\n\n // wrap the block in a blockContainer\n const bc = BC_NODE.spec?.toDOM?.(\n BC_NODE.create({\n id: block.id,\n ...props,\n }),\n ) as {\n dom: HTMLElement;\n contentDOM?: HTMLElement;\n };\n\n bc.contentDOM?.appendChild(ret.dom);\n\n if (block.children && block.children.length > 0) {\n bc.contentDOM?.appendChild(\n serializeBlocksInternalHTML(editor, block.children, serializer, options),\n );\n }\n return bc.dom;\n}\n\nfunction serializeBlocks<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, I, S>,\n blocks: PartialBlock<BSchema, I, S>[],\n serializer: DOMSerializer,\n options?: { document?: Document },\n) {\n const doc = options?.document ?? document;\n const fragment = doc.createDocumentFragment();\n\n for (const block of blocks) {\n const blockDOM = serializeBlock(editor, block, serializer, options);\n fragment.appendChild(blockDOM);\n }\n\n return fragment;\n}\n\nexport const serializeBlocksInternalHTML = <\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, I, S>,\n blocks: PartialBlock<BSchema, I, S>[],\n serializer: DOMSerializer,\n options?: { document?: Document },\n) => {\n const BG_NODE = editor.pmSchema.nodes[\"blockGroup\"];\n\n const bg = BG_NODE.spec!.toDOM!(BG_NODE.create({})) as {\n dom: HTMLElement;\n contentDOM?: HTMLElement;\n };\n\n const fragment = serializeBlocks(editor, blocks, serializer, options);\n\n bg.contentDOM?.appendChild(fragment);\n\n return bg.dom;\n};\n","import { DOMSerializer, Schema } from \"prosemirror-model\";\nimport { PartialBlock } from \"../../../blocks/defaultBlocks.js\";\nimport type { BlockNoteEditor } from \"../../../editor/BlockNoteEditor.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { serializeBlocksInternalHTML } from \"./util/serializeBlocksInternalHTML.js\";\n// Used to serialize BlockNote blocks and ProseMirror nodes to HTML without\n// losing data. Blocks are exported using the `toInternalHTML` method in their\n// `blockSpec`.\n//\n// The HTML created by this serializer is the same as what's rendered by the\n// editor to the DOM. This means that it retains the same structure as the\n// editor, including the `blockGroup` and `blockContainer` wrappers. This also\n// means that it can be converted back to the original blocks without any data\n// loss.\nexport const createInternalHTMLSerializer = <\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n schema: Schema,\n editor: BlockNoteEditor<BSchema, I, S>,\n) => {\n const serializer = DOMSerializer.fromSchema(schema);\n\n return {\n serializeBlocks: (\n blocks: PartialBlock<BSchema, I, S>[],\n options: { document?: Document },\n ) => {\n return serializeBlocksInternalHTML(editor, blocks, serializer, options)\n .outerHTML;\n },\n };\n};\n","import {\n NodeSelection,\n Selection,\n TextSelection,\n Transaction,\n} from \"prosemirror-state\";\nimport { CellSelection } from \"prosemirror-tables\";\n\nimport { Block } from \"../../../../blocks/defaultBlocks.js\";\nimport type { BlockNoteEditor } from \"../../../../editor/BlockNoteEditor\";\nimport { BlockIdentifier } from \"../../../../schema/index.js\";\nimport { getNearestBlockPos } from \"../../../getBlockInfoFromPos.js\";\nimport { getNodeById } from \"../../../nodeUtil.js\";\n\ntype BlockSelectionData = (\n | {\n type: \"text\";\n headBlockId: string;\n anchorOffset: number;\n headOffset: number;\n }\n | {\n type: \"node\";\n }\n | {\n type: \"cell\";\n anchorCellOffset: number;\n headCellOffset: number;\n }\n) & {\n anchorBlockId: string;\n};\n\n/**\n * `getBlockSelectionData` and `updateBlockSelectionFromData` are used to save\n * and restore the selection within a block, when the block is moved. This is\n * done by first saving the offsets of the anchor and head from the before\n * positions of their surrounding blocks, as well as the IDs of those blocks. We\n * can then recreate the selection by finding the blocks with those IDs, getting\n * their before positions, and adding the offsets to those positions.\n * @param editor The BlockNote editor instance to get the selection data from.\n */\nfunction getBlockSelectionData(\n editor: BlockNoteEditor<any, any, any>,\n): BlockSelectionData {\n return editor.transact((tr) => {\n const anchorBlockPosInfo = getNearestBlockPos(tr.doc, tr.selection.anchor);\n\n if (tr.selection instanceof CellSelection) {\n return {\n type: \"cell\" as const,\n anchorBlockId: anchorBlockPosInfo.node.attrs.id,\n anchorCellOffset:\n tr.selection.$anchorCell.pos - anchorBlockPosInfo.posBeforeNode,\n headCellOffset:\n tr.selection.$headCell.pos - anchorBlockPosInfo.posBeforeNode,\n };\n } else if (tr.selection instanceof NodeSelection) {\n return {\n type: \"node\" as const,\n anchorBlockId: anchorBlockPosInfo.node.attrs.id,\n };\n } else {\n const headBlockPosInfo = getNearestBlockPos(tr.doc, tr.selection.head);\n\n return {\n type: \"text\" as const,\n anchorBlockId: anchorBlockPosInfo.node.attrs.id,\n headBlockId: headBlockPosInfo.node.attrs.id,\n anchorOffset: tr.selection.anchor - anchorBlockPosInfo.posBeforeNode,\n headOffset: tr.selection.head - headBlockPosInfo.posBeforeNode,\n };\n }\n });\n}\n\n/**\n * `getBlockSelectionData` and `updateBlockSelectionFromData` are used to save\n * and restore the selection within a block, when the block is moved. This is\n * done by first saving the offsets of the anchor and head from the before\n * positions of their surrounding blocks, as well as the IDs of those blocks. We\n * can then recreate the selection by finding the blocks with those IDs, getting\n * their before positions, and adding the offsets to those positions.\n * @param tr The transaction to update the selection in.\n * @param data The selection data to update the selection with (generated by\n * `getBlockSelectionData`).\n */\nfunction updateBlockSelectionFromData(\n tr: Transaction,\n data: BlockSelectionData,\n) {\n const anchorBlockPos = getNodeById(data.anchorBlockId, tr.doc)?.posBeforeNode;\n if (anchorBlockPos === undefined) {\n throw new Error(\n `Could not find block with ID ${data.anchorBlockId} to update selection`,\n );\n }\n\n let selection: Selection;\n if (data.type === \"cell\") {\n selection = CellSelection.create(\n tr.doc,\n anchorBlockPos + data.anchorCellOffset,\n anchorBlockPos + data.headCellOffset,\n );\n } else if (data.type === \"node\") {\n selection = NodeSelection.create(tr.doc, anchorBlockPos + 1);\n } else {\n const headBlockPos = getNodeById(data.headBlockId, tr.doc)?.posBeforeNode;\n if (headBlockPos === undefined) {\n throw new Error(\n `Could not find block with ID ${data.headBlockId} to update selection`,\n );\n }\n\n selection = TextSelection.create(\n tr.doc,\n anchorBlockPos + data.anchorOffset,\n headBlockPos + data.headOffset,\n );\n }\n\n tr.setSelection(selection);\n}\n\n/**\n * Replaces any `columnList` blocks with the children of their columns. This is\n * done here instead of in `getSelection` as we still need to remove the entire\n * `columnList` node but only insert the `blockContainer` nodes inside it.\n * @param blocks The blocks to flatten.\n */\nfunction flattenColumns(\n blocks: Block<any, any, any>[],\n): Block<any, any, any>[] {\n return blocks\n .map((block) => {\n if (block.type === \"columnList\") {\n return block.children\n .map((column) => flattenColumns(column.children))\n .flat();\n }\n\n return {\n ...block,\n children: flattenColumns(block.children),\n };\n })\n .flat();\n}\n\n/**\n * Removes the selected blocks from the editor, then inserts them before/after a\n * reference block. Also updates the selection to match the original selection\n * using `getBlockSelectionData` and `updateBlockSelectionFromData`.\n * @param editor The BlockNote editor instance to move the blocks in.\n * @param referenceBlock The reference block to insert the selected blocks\n * before/after.\n * @param placement Whether to insert the selected blocks before or after the\n * reference block.\n */\nexport function moveSelectedBlocksAndSelection(\n editor: BlockNoteEditor<any, any, any>,\n referenceBlock: BlockIdentifier,\n placement: \"before\" | \"after\",\n) {\n // We want this to be a single step in the undo history\n editor.transact((tr) => {\n const blocks = editor.getSelection()?.blocks || [\n editor.getTextCursorPosition().block,\n ];\n const selectionData = getBlockSelectionData(editor);\n\n editor.removeBlocks(blocks);\n editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement);\n\n updateBlockSelectionFromData(tr, selectionData);\n });\n}\n\n// Checks if a block is in a valid place after being moved. This check is\n// primitive at the moment and only returns false if the block's parent is a\n// `columnList` block. This is because regular blocks cannot be direct children\n// of `columnList` blocks.\nfunction checkPlacementIsValid(parentBlock?: Block<any, any, any>): boolean {\n return !parentBlock || parentBlock.type !== \"columnList\";\n}\n\n// Gets the placement for moving a block up. This has 3 cases:\n// 1. If the block has a previous sibling without children, the placement is\n// before it.\n// 2. If the block has a previous sibling with children, the placement is after\n// the last child.\n// 3. If the block has no previous sibling, but is nested, the placement is\n// before its parent.\n// If the placement is invalid, the function is called recursively until a valid\n// placement is found. Returns undefined if no valid placement is found, meaning\n// the block is already at the top of the document.\nfunction getMoveUpPlacement(\n editor: BlockNoteEditor<any, any, any>,\n prevBlock?: Block<any, any, any>,\n parentBlock?: Block<any, any, any>,\n):\n | { referenceBlock: BlockIdentifier; placement: \"before\" | \"after\" }\n | undefined {\n let referenceBlock: Block<any, any, any> | undefined;\n let placement: \"before\" | \"after\" | undefined;\n\n if (!prevBlock) {\n if (parentBlock) {\n referenceBlock = parentBlock;\n placement = \"before\";\n }\n } else if (prevBlock.children.length > 0) {\n referenceBlock = prevBlock.children[prevBlock.children.length - 1];\n placement = \"after\";\n } else {\n referenceBlock = prevBlock;\n placement = \"before\";\n }\n\n // Case when the block is already at the top of the document.\n if (!referenceBlock || !placement) {\n return undefined;\n }\n\n const referenceBlockParent = editor.getParentBlock(referenceBlock);\n if (!checkPlacementIsValid(referenceBlockParent)) {\n return getMoveUpPlacement(\n editor,\n placement === \"after\"\n ? referenceBlock\n : editor.getPrevBlock(referenceBlock),\n referenceBlockParent,\n );\n }\n\n return { referenceBlock, placement };\n}\n\n// Gets the placement for moving a block down. This has 3 cases:\n// 1. If the block has a next sibling without children, the placement is after\n// it.\n// 2. If the block has a next sibling with children, the placement is before the\n// first child.\n// 3. If the block has no next sibling, but is nested, the placement is\n// after its parent.\n// If the placement is invalid, the function is called recursively until a valid\n// placement is found. Returns undefined if no valid placement is found, meaning\n// the block is already at the bottom of the document.\nfunction getMoveDownPlacement(\n editor: BlockNoteEditor<any, any, any>,\n nextBlock?: Block<any, any, any>,\n parentBlock?: Block<any, any, any>,\n):\n | { referenceBlock: BlockIdentifier; placement: \"before\" | \"after\" }\n | undefined {\n let referenceBlock: Block<any, any, any> | undefined;\n let placement: \"before\" | \"after\" | undefined;\n\n if (!nextBlock) {\n if (parentBlock) {\n referenceBlock = parentBlock;\n placement = \"after\";\n }\n } else if (nextBlock.children.length > 0) {\n referenceBlock = nextBlock.children[0];\n placement = \"before\";\n } else {\n referenceBlock = nextBlock;\n placement = \"after\";\n }\n\n // Case when the block is already at the bottom of the document.\n if (!referenceBlock || !placement) {\n return undefined;\n }\n\n const referenceBlockParent = editor.getParentBlock(referenceBlock);\n if (!checkPlacementIsValid(referenceBlockParent)) {\n return getMoveDownPlacement(\n editor,\n placement === \"before\"\n ? referenceBlock\n : editor.getNextBlock(referenceBlock),\n referenceBlockParent,\n );\n }\n\n return { referenceBlock, placement };\n}\n\nexport function moveBlocksUp(editor: BlockNoteEditor<any, any, any>) {\n editor.transact(() => {\n const selection = editor.getSelection();\n const block = selection?.blocks[0] || editor.getTextCursorPosition().block;\n\n const moveUpPlacement = getMoveUpPlacement(\n editor,\n editor.getPrevBlock(block),\n editor.getParentBlock(block),\n );\n\n if (!moveUpPlacement) {\n return;\n }\n\n moveSelectedBlocksAndSelection(\n editor,\n moveUpPlacement.referenceBlock,\n moveUpPlacement.placement,\n );\n });\n}\n\nexport function moveBlocksDown(editor: BlockNoteEditor<any, any, any>) {\n editor.transact(() => {\n const selection = editor.getSelection();\n const block =\n selection?.blocks[selection?.blocks.length - 1] ||\n editor.getTextCursorPosition().block;\n\n const moveDownPlacement = getMoveDownPlacement(\n editor,\n editor.getNextBlock(block),\n editor.getParentBlock(block),\n );\n\n if (!moveDownPlacement) {\n return;\n }\n\n moveSelectedBlocksAndSelection(\n editor,\n moveDownPlacement.referenceBlock,\n moveDownPlacement.placement,\n );\n });\n}\n","import { Fragment, NodeType, Slice } from \"prosemirror-model\";\nimport { Transaction } from \"prosemirror-state\";\nimport { ReplaceAroundStep } from \"prosemirror-transform\";\n\nimport { BlockNoteEditor } from \"../../../../editor/BlockNoteEditor.js\";\nimport { getBlockInfoFromTransaction } from \"../../../getBlockInfoFromPos.js\";\n\n// TODO: Unit tests\n/**\n * This is a modified version of https://github.com/ProseMirror/prosemirror-schema-list/blob/569c2770cbb8092d8f11ea53ecf78cb7a4e8f15a/src/schema-list.ts#L232\n *\n * The original function derives too many information from the parentnode and itemtype\n */\nfunction sinkListItem(\n tr: Transaction,\n itemType: NodeType,\n groupType: NodeType,\n) {\n const { $from, $to } = tr.selection;\n const range = $from.blockRange(\n $to,\n (node) =>\n node.childCount > 0 &&\n (node.type.name === \"blockGroup\" || node.type.name === \"column\"), // change necessary to not look at first item child type\n );\n if (!range) {\n return false;\n }\n const startIndex = range.startIndex;\n if (startIndex === 0) {\n return false;\n }\n const parent = range.parent;\n const nodeBefore = parent.child(startIndex - 1);\n if (nodeBefore.type !== itemType) {\n return false;\n }\n const nestedBefore =\n nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type\n const inner = Fragment.from(nestedBefore ? itemType.create() : null);\n const slice = new Slice(\n Fragment.from(\n itemType.create(null, Fragment.from(groupType.create(null, inner))), // change necessary to create \"groupType\" instead of parent.type\n ),\n nestedBefore ? 3 : 1,\n 0,\n );\n\n const before = range.start;\n const after = range.end;\n\n tr.step(\n new ReplaceAroundStep(\n before - (nestedBefore ? 3 : 1),\n after,\n before,\n after,\n slice,\n 1,\n true,\n ),\n ).scrollIntoView();\n\n return true;\n}\n\nexport function nestBlock(editor: BlockNoteEditor<any, any, any>) {\n return editor.transact((tr) => {\n return sinkListItem(\n tr,\n editor.pmSchema.nodes[\"blockContainer\"],\n editor.pmSchema.nodes[\"blockGroup\"],\n );\n });\n}\n\nexport function unnestBlock(editor: BlockNoteEditor<any, any, any>) {\n editor._tiptapEditor.commands.liftListItem(\"blockContainer\");\n}\n\nexport function canNestBlock(editor: BlockNoteEditor<any, any, any>) {\n return editor.transact((tr) => {\n const { bnBlock: blockContainer } = getBlockInfoFromTransaction(tr);\n\n return tr.doc.resolve(blockContainer.beforePos).nodeBefore !== null;\n });\n}\n\nexport function canUnnestBlock(editor: BlockNoteEditor<any, any, any>) {\n return editor.transact((tr) => {\n const { bnBlock: blockContainer } = getBlockInfoFromTransaction(tr);\n\n return tr.doc.resolve(blockContainer.beforePos).depth > 1;\n });\n}\n","import type { Node } from \"prosemirror-model\";\nimport type { Block } from \"../../../blocks/defaultBlocks.js\";\nimport type {\n BlockIdentifier,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { nodeToBlock } from \"../../nodeConversions/nodeToBlock.js\";\nimport { getNodeById } from \"../../nodeUtil.js\";\nimport { getPmSchema } from \"../../pmUtil.js\";\n\nexport function getBlock<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n doc: Node,\n blockIdentifier: BlockIdentifier,\n): Block<BSchema, I, S> | undefined {\n const id =\n typeof blockIdentifier === \"string\" ? blockIdentifier : blockIdentifier.id;\n const pmSchema = getPmSchema(doc);\n\n const posInfo = getNodeById(id, doc);\n if (!posInfo) {\n return undefined;\n }\n\n return nodeToBlock(posInfo.node, pmSchema);\n}\n\nexport function getPrevBlock<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n doc: Node,\n blockIdentifier: BlockIdentifier,\n): Block<BSchema, I, S> | undefined {\n const id =\n typeof blockIdentifier === \"string\" ? blockIdentifier : blockIdentifier.id;\n\n const posInfo = getNodeById(id, doc);\n const pmSchema = getPmSchema(doc);\n if (!posInfo) {\n return undefined;\n }\n\n const $posBeforeNode = doc.resolve(posInfo.posBeforeNode);\n const nodeToConvert = $posBeforeNode.nodeBefore;\n if (!nodeToConvert) {\n return undefined;\n }\n\n return nodeToBlock(nodeToConvert, pmSchema);\n}\n\nexport function getNextBlock<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n doc: Node,\n blockIdentifier: BlockIdentifier,\n): Block<BSchema, I, S> | undefined {\n const id =\n typeof blockIdentifier === \"string\" ? blockIdentifier : blockIdentifier.id;\n const posInfo = getNodeById(id, doc);\n const pmSchema = getPmSchema(doc);\n if (!posInfo) {\n return undefined;\n }\n\n const $posAfterNode = doc.resolve(\n posInfo.posBeforeNode + posInfo.node.nodeSize,\n );\n const nodeToConvert = $posAfterNode.nodeAfter;\n if (!nodeToConvert) {\n return undefined;\n }\n\n return nodeToBlock(nodeToConvert, pmSchema);\n}\n\nexport function getParentBlock<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n doc: Node,\n blockIdentifier: BlockIdentifier,\n): Block<BSchema, I, S> | undefined {\n const id =\n typeof blockIdentifier === \"string\" ? blockIdentifier : blockIdentifier.id;\n const pmSchema = getPmSchema(doc);\n const posInfo = getNodeById(id, doc);\n if (!posInfo) {\n return undefined;\n }\n\n const $posBeforeNode = doc.resolve(posInfo.posBeforeNode);\n const parentNode = $posBeforeNode.node();\n const grandparentNode = $posBeforeNode.node(-1);\n const nodeToConvert =\n grandparentNode.type.name !== \"doc\"\n ? parentNode.type.name === \"blockGroup\"\n ? grandparentNode\n : parentNode\n : undefined;\n if (!nodeToConvert) {\n return undefined;\n }\n\n return nodeToBlock(nodeToConvert, pmSchema);\n}\n","import { insertBlocks } from \"../../api/blockManipulation/commands/insertBlocks/insertBlocks.js\";\nimport {\n moveBlocksDown,\n moveBlocksUp,\n} from \"../../api/blockManipulation/commands/moveBlocks/moveBlocks.js\";\nimport {\n canNestBlock,\n canUnnestBlock,\n nestBlock,\n unnestBlock,\n} from \"../../api/blockManipulation/commands/nestBlock/nestBlock.js\";\nimport { removeAndInsertBlocks } from \"../../api/blockManipulation/commands/replaceBlocks/replaceBlocks.js\";\nimport { updateBlock } from \"../../api/blockManipulation/commands/updateBlock/updateBlock.js\";\nimport {\n getBlock,\n getNextBlock,\n getParentBlock,\n getPrevBlock,\n} from \"../../api/blockManipulation/getBlock/getBlock.js\";\nimport { docToBlocks } from \"../../api/nodeConversions/nodeToBlock.js\";\nimport {\n Block,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n PartialBlock,\n} from \"../../blocks/defaultBlocks.js\";\nimport {\n BlockIdentifier,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../schema/index.js\";\nimport { BlockNoteEditor } from \"../BlockNoteEditor.js\";\n\nexport class BlockManager<\n BSchema extends BlockSchema = DefaultBlockSchema,\n ISchema extends InlineContentSchema = DefaultInlineContentSchema,\n SSchema extends StyleSchema = DefaultStyleSchema,\n> {\n constructor(private editor: BlockNoteEditor<BSchema, ISchema, SSchema>) {}\n\n /**\n * Gets a snapshot of all top-level (non-nested) blocks in the editor.\n * @returns A snapshot of all top-level (non-nested) blocks in the editor.\n */\n public get document(): Block<BSchema, ISchema, SSchema>[] {\n return this.editor.transact((tr) => {\n return docToBlocks(tr.doc, this.editor.pmSchema);\n });\n }\n\n /**\n * Gets a snapshot of an