@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
1 lines • 506 kB
Source Map (JSON)
{"version":3,"file":"src-DM3syMdf.cjs","names":[],"sources":["../src/schema/blocks/types.ts","../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/user/userColors.ts","../src/util/EventEmitter.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/util/normalizeWhitespace.ts","../src/api/parsers/html/parseHTML.ts","../src/api/parsers/markdown/markdownToHtml.ts","../src/api/parsers/markdown/parseMarkdown.ts","../src/editor/managers/ExportManager.ts","../src/api/blockManipulation/selections/textCursorPosition.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/TextAlignment/TextAlignmentExtension.ts","../src/extensions/tiptap-extensions/TextColor/TextColorExtension.ts","../src/extensions/tiptap-extensions/Link/helpers/tlds.ts","../src/extensions/tiptap-extensions/Link/helpers/linkDetector.ts","../src/extensions/tiptap-extensions/Link/helpers/whitespace.ts","../src/extensions/tiptap-extensions/Link/helpers/autolink.ts","../src/extensions/tiptap-extensions/Link/helpers/clickHandler.ts","../src/extensions/tiptap-extensions/Link/helpers/pasteHandler.ts","../src/extensions/tiptap-extensions/Link/link.ts","../src/pm-nodes/BlockContainer.ts","../src/pm-nodes/BlockGroup.ts","../src/pm-nodes/Doc.ts","../src/editor/managers/ExtensionManager/extensions.ts","../src/editor/managers/ExtensionManager/index.ts","../src/util/expandToWords.ts","../src/api/blockManipulation/selections/selection.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/ExportImage.ts","../src/exporter/mapping.ts","../src/util/combineByGroup.ts"],"sourcesContent":["/** Define the main block types **/\n// import { Extension, Node } from \"@tiptap/core\";\nimport type { Node, NodeViewRendererProps } from \"@tiptap/core\";\nimport type {\n Fragment,\n Node as ProsemirrorNode,\n Schema,\n} from \"prosemirror-model\";\nimport type { ViewMutationRecord } from \"prosemirror-view\";\nimport type { BlockNoteEditor } from \"../../editor/BlockNoteEditor.js\";\nimport type {\n Extension,\n ExtensionFactoryInstance,\n} from \"../../editor/BlockNoteExtension.js\";\nimport type {\n InlineContent,\n InlineContentSchema,\n PartialInlineContent,\n StyledText,\n} from \"../inlineContent/types.js\";\nimport type { PropSchema, Props } from \"../propTypes.js\";\nimport type { StyleSchema } from \"../styles/types.js\";\n\nexport type BlockNoteDOMElement =\n | \"editor\"\n | \"block\"\n | \"blockGroup\"\n | \"blockContent\"\n | \"inlineContent\";\n\nexport type BlockNoteDOMAttributes = Partial<{\n [DOMElement in BlockNoteDOMElement]: Record<string, string>;\n}>;\n\nexport interface BlockConfigMeta<\n TName extends string = string,\n TProps extends PropSchema = PropSchema,\n> {\n /**\n * Defines which keyboard shortcut should be used to insert a hard break into the block's inline content.\n * @default \"shift+enter\"\n */\n hardBreakShortcut?: \"shift+enter\" | \"enter\" | \"none\";\n\n /**\n * Whether the block is selectable\n */\n selectable?: boolean;\n\n /**\n * The accept mime types for the file block\n */\n fileBlockAccept?: string[];\n\n /**\n * Whether the block is a {@link https://prosemirror.net/docs/ref/#model.NodeSpec.code} block\n */\n code?: boolean;\n\n /**\n * Whether the block is a {@link https://prosemirror.net/docs/ref/#model.NodeSpec.defining} block\n */\n defining?: boolean;\n\n /**\n * Whether the block is a {@link https://prosemirror.net/docs/ref/#model.NodeSpec.isolating} block\n */\n isolating?: boolean;\n\n /**\n * Enables syntax highlighting of the contents of the block with the result of this callback\n */\n highlight?(block: { type: TName; props: Props<TProps> }): string | undefined;\n\n /**\n * Marks the block as rendering a preview with an editable source popup, driven\n * by the editor-wide `SourceBlockWithPreviewExtension`. When `true`, the\n * block's source is hidden behind its preview and edited via the popup.\n */\n hasPreview?: boolean;\n}\n\n/**\n * BlockConfig contains the \"schema\" info about a Block type\n * i.e. what props it supports, what content it supports, etc.\n */\nexport interface BlockConfig<\n T extends string = string,\n PS extends PropSchema = PropSchema,\n C extends \"inline\" | \"none\" | \"table\" | \"plain\" =\n | \"inline\"\n | \"none\"\n | \"table\"\n | \"plain\",\n> {\n /**\n * The type of the block (unique identifier within a schema)\n */\n type: T;\n /**\n * The properties that the block supports\n * @todo will be zod schema in the future\n */\n readonly propSchema: PS;\n /**\n * The content that the block supports\n */\n content: C;\n // TODO: how do you represent things that have nested content?\n // e.g. tables, alerts (with title & content)\n}\n\n/**\n * BlockConfigOrCreator is a union type of BlockConfig and a function that returns a BlockConfig.\n * This is used to create block configs that can be passed to the createBlockSpec function.\n */\nexport type BlockConfigOrCreator<\n TName extends string = string,\n TProps extends PropSchema = PropSchema,\n TContent extends \"inline\" | \"none\" | \"plain\" = \"inline\" | \"none\" | \"plain\",\n TOptions extends Record<string, any> | undefined =\n | Record<string, any>\n | undefined,\n> =\n | BlockConfig<TName, TProps, TContent>\n | (TOptions extends undefined\n ? () => BlockConfig<TName, TProps, TContent>\n : (options: Partial<TOptions>) => BlockConfig<TName, TProps, TContent>);\n\n/**\n * ExtractBlockConfigFromConfigOrCreator is a helper type that extracts the BlockConfig type from a BlockConfigOrCreator.\n */\nexport type ExtractBlockConfigFromConfigOrCreator<\n ConfigOrCreator extends\n | BlockConfig<string, PropSchema, \"inline\" | \"none\" | \"plain\">\n | ((\n ...args: any[]\n ) => BlockConfig<string, PropSchema, \"inline\" | \"none\" | \"plain\">),\n> = ConfigOrCreator extends (...args: any[]) => infer Config\n ? Config\n : ConfigOrCreator;\n\n// restrict content to \"inline\" and \"none\" only\nexport type CustomBlockConfig<\n T extends string = string,\n PS extends PropSchema = PropSchema,\n C extends \"inline\" | \"none\" | \"plain\" = \"inline\" | \"none\" | \"plain\",\n> = BlockConfig<T, PS, C>;\n\n// A Spec contains both the Config and Implementation\nexport type BlockSpec<\n T extends string = string,\n PS extends PropSchema = PropSchema,\n C extends \"inline\" | \"none\" | \"table\" | \"plain\" =\n | \"inline\"\n | \"none\"\n | \"table\"\n | \"plain\",\n> = {\n config: BlockConfig<T, PS, C>;\n implementation: BlockImplementation<T, PS, C>;\n extensions?: (Extension | ExtensionFactoryInstance)[];\n};\n\n/**\n * BlockSpecOrCreator is a union type of BlockSpec and a function that returns a BlockSpec.\n * This is used to create block specs that can be passed to the createBlockSpec function.\n */\nexport type BlockSpecOrCreator<\n T extends string = string,\n PS extends PropSchema = PropSchema,\n C extends \"inline\" | \"none\" | \"table\" | \"plain\" =\n | \"inline\"\n | \"none\"\n | \"table\"\n | \"plain\",\n TOptions extends Record<string, any> | undefined =\n | Record<string, any>\n | undefined,\n> =\n | BlockSpec<T, PS, C>\n | (TOptions extends undefined\n ? () => BlockSpec<T, PS, C>\n : (options: Partial<TOptions>) => BlockSpec<T, PS, C>);\n\n/**\n * ExtractBlockSpecFromSpecOrCreator is a helper type that extracts the BlockSpec type from a BlockSpecOrCreator.\n */\nexport type ExtractBlockSpecFromSpecOrCreator<\n SpecOrCreator extends\n | BlockSpec<string, PropSchema, \"inline\" | \"none\" | \"plain\">\n | ((\n ...args: any[]\n ) => BlockSpec<string, PropSchema, \"inline\" | \"none\" | \"plain\">),\n> = SpecOrCreator extends (...args: any[]) => infer Spec ? Spec : SpecOrCreator;\n\n/**\n * This allows de-coupling the types that we display to users versus the types we expose internally.\n *\n * This prevents issues with type-inference across parameters that Typescript cannot handle.\n * Specifically, the blocks shape cannot be properly inferred to a specific type like we expose to the user.\n */\nexport type LooseBlockSpec<\n T extends string = string,\n PS extends PropSchema = PropSchema,\n C extends \"inline\" | \"none\" | \"table\" | \"plain\" =\n | \"inline\"\n | \"none\"\n | \"table\"\n | \"plain\",\n> = {\n config: BlockConfig<T, PS, C>;\n implementation: Omit<\n BlockImplementation<T, PS, C>,\n \"render\" | \"toExternalHTML\"\n > & {\n // purposefully stub the types for render and toExternalHTML since they reference the block\n render: (\n /**\n * The custom block to render\n */\n block: any,\n /**\n * The BlockNote editor instance\n */\n editor: BlockNoteEditor<any>,\n ) => {\n dom: HTMLElement | DocumentFragment;\n contentDOM?: HTMLElement;\n ignoreMutation?: (mutation: ViewMutationRecord) => boolean;\n update?: (node: ProsemirrorNode) => boolean;\n destroy?: () => void;\n };\n toExternalHTML?: (\n block: any,\n editor: BlockNoteEditor<any>,\n context: {\n nestingLevel: number;\n },\n ) =>\n | {\n dom: HTMLElement | DocumentFragment;\n contentDOM?: HTMLElement;\n childrenDOM?: HTMLElement;\n }\n | undefined;\n\n node: Node;\n };\n extensions?: (Extension | ExtensionFactoryInstance)[];\n};\n\n// Utility type. For a given object block schema, ensures that the key of each\n// block spec matches the name of the TipTap node in it.\ntype NamesMatch<Blocks extends Record<string, BlockConfig>> = Blocks extends {\n [Type in keyof Blocks]: Type extends string\n ? Blocks[Type] extends { type: Type }\n ? Blocks[Type]\n : never\n : never;\n}\n ? Blocks\n : never;\n\n// A Schema contains all the types (Configs) supported in an editor\n// The keys are the \"type\" of a block\nexport type BlockSchema = NamesMatch<Record<string, BlockConfig>>;\n\nexport type BlockSpecs = {\n [k in string]: {\n config: BlockSpec<k>[\"config\"];\n implementation: Omit<\n BlockSpec<k>[\"implementation\"],\n \"render\" | \"toExternalHTML\"\n > & {\n // purposefully stub the types for render and toExternalHTML since they reference the block\n render: (\n /**\n * The custom block to render\n */\n block: any,\n /**\n * The BlockNote editor instance\n */\n editor: BlockNoteEditor<any>,\n ) => {\n dom: HTMLElement | DocumentFragment;\n contentDOM?: HTMLElement;\n ignoreMutation?: (mutation: ViewMutationRecord) => boolean;\n update?: (node: ProsemirrorNode) => boolean;\n destroy?: () => void;\n };\n toExternalHTML?: (\n block: any,\n editor: BlockNoteEditor<any>,\n context: {\n nestingLevel: number;\n },\n ) =>\n | {\n dom: HTMLElement | DocumentFragment;\n contentDOM?: HTMLElement;\n childrenDOM?: HTMLElement;\n }\n | undefined;\n };\n extensions?: BlockSpec<k>[\"extensions\"];\n };\n};\n\nexport type BlockImplementations = Record<\n string,\n BlockImplementation<any, any>\n>;\n\nexport type BlockSchemaFromSpecs<BS extends BlockSpecs> = {\n [K in keyof BS]: BS[K][\"config\"];\n};\n\nexport type BlockSpecsFromSchema<BS extends BlockSchema> = {\n [K in keyof BS]: {\n config: BlockConfig<BS[K][\"type\"], BS[K][\"propSchema\"], BS[K][\"content\"]>;\n implementation: BlockImplementation<\n BS[K][\"type\"],\n BS[K][\"propSchema\"],\n BS[K][\"content\"]\n >;\n extensions?: (Extension | ExtensionFactoryInstance)[];\n };\n};\n\nexport type BlockSchemaWithBlock<T extends string, C extends BlockConfig> = {\n [k in T]: C;\n};\n\nexport type TableCellProps = {\n backgroundColor: string;\n textColor: string;\n textAlignment: \"left\" | \"center\" | \"right\" | \"justify\";\n colspan?: number;\n rowspan?: number;\n};\n\nexport type TableCell<\n I extends InlineContentSchema,\n S extends StyleSchema = StyleSchema,\n> = {\n type: \"tableCell\";\n props: TableCellProps;\n content: InlineContent<I, S>[];\n};\n\nexport type TableContent<\n I extends InlineContentSchema,\n S extends StyleSchema = StyleSchema,\n> = {\n type: \"tableContent\";\n columnWidths: (number | undefined)[];\n headerRows?: number;\n headerCols?: number;\n rows: {\n cells: InlineContent<I, S>[][] | TableCell<I, S>[];\n }[];\n};\n\n// The content of a block with \"plain\" content (e.g. a code block): unstyled\n// text, represented as StyledText items whose `styles` is always empty.\nexport type PlainContent = (StyledText<{}> & {\n styles: Record<string, never>;\n})[];\n\n// Partial form of PlainContent: also accepts bare strings (both as the whole\n// content and as array items), which are normalized on write.\nexport type PartialPlainContent =\n | string\n | (string | (StyledText<{}> & { styles: Record<string, never> }))[];\n\n/**\n * The text of a block's `\"plain\"` content (e.g. a code block's source code).\n * Accepts the partial form too: block render/export paths can receive\n * `PartialBlock`s (e.g. the HTML serializers take them directly), where\n * plain content may still be the bare-string sugar.\n */\nexport function plainContentToString(\n content: PlainContent | PartialPlainContent,\n): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .map((item) => (typeof item === \"string\" ? item : item.text))\n .join(\"\");\n}\n\n// A BlockConfig has all the information to get the type of a Block (which is a specific instance of the BlockConfig.\n// i.e.: paragraphConfig: BlockConfig defines what a \"paragraph\" is / supports, and BlockFromConfigNoChildren<paragraphConfig> is the shape of a specific paragraph block.\n// (for internal use)\nexport type BlockFromConfigNoChildren<\n B extends BlockConfig,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = {\n id: string;\n type: B[\"type\"];\n props: Props<B[\"propSchema\"]>;\n content: B[\"content\"] extends \"inline\"\n ? InlineContent<I, S>[]\n : B[\"content\"] extends \"table\"\n ? TableContent<I, S>\n : B[\"content\"] extends \"plain\"\n ? PlainContent\n : B[\"content\"] extends \"none\"\n ? undefined\n : never;\n};\n\nexport type BlockFromConfig<\n B extends BlockConfig,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = BlockFromConfigNoChildren<B, I, S> & {\n children: BlockNoDefaults<BlockSchema, I, S>[];\n};\n\n// Converts each block spec into a Block object without children. We later merge\n// them into a union type and add a children property to create the Block and\n// PartialBlock objects we use in the external API.\ntype BlocksWithoutChildren<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = {\n [BType in keyof BSchema]: BlockFromConfigNoChildren<BSchema[BType], I, S>;\n};\n\n// Converts each block spec into a Block object without children, merges them\n// into a union type, and adds a children property\nexport type BlockNoDefaults<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = BlocksWithoutChildren<BSchema, I, S>[keyof BSchema] & {\n children: BlockNoDefaults<BSchema, I, S>[];\n};\n\nexport type SpecificBlock<\n BSchema extends BlockSchema,\n BType extends keyof BSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = BlocksWithoutChildren<BSchema, I, S>[BType] & {\n children: BlockNoDefaults<BSchema, I, S>[];\n};\n\n/** CODE FOR PARTIAL BLOCKS, analogous to above\n *\n * Partial blocks are convenience-wrappers to make it easier to\n *create/update blocks in the editor.\n *\n */\n\nexport type PartialTableCell<\n I extends InlineContentSchema,\n S extends StyleSchema = StyleSchema,\n> = {\n type: \"tableCell\";\n props?: Partial<TableCellProps>;\n content?: PartialInlineContent<I, S>;\n};\n\nexport type PartialTableContent<\n I extends InlineContentSchema,\n S extends StyleSchema = StyleSchema,\n> = {\n type: \"tableContent\";\n columnWidths?: (number | undefined)[];\n headerRows?: number;\n headerCols?: number;\n rows: {\n cells: PartialInlineContent<I, S>[] | PartialTableCell<I, S>[];\n }[];\n};\n\ntype PartialBlockFromConfigNoChildren<\n B extends BlockConfig,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = {\n id?: string;\n type?: B[\"type\"];\n props?: Partial<Props<B[\"propSchema\"]>>;\n content?: B[\"content\"] extends \"inline\"\n ? PartialInlineContent<I, S>\n : B[\"content\"] extends \"table\"\n ? PartialTableContent<I, S>\n : B[\"content\"] extends \"plain\"\n ? PartialPlainContent\n : B[\"content\"] extends \"none\"\n ? undefined\n : never;\n};\n\ntype PartialBlocksWithoutChildren<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = {\n [BType in keyof BSchema]: PartialBlockFromConfigNoChildren<\n BSchema[BType],\n I,\n S\n >;\n};\n\nexport type PartialBlockNoDefaults<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = PartialBlocksWithoutChildren<\n BSchema,\n I,\n S\n>[keyof PartialBlocksWithoutChildren<BSchema, I, S>] &\n Partial<{\n children: PartialBlockNoDefaults<BSchema, I, S>[];\n }>;\n\nexport type SpecificPartialBlock<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n BType extends keyof BSchema,\n S extends StyleSchema,\n> = PartialBlocksWithoutChildren<BSchema, I, S>[BType] & {\n children?: BlockNoDefaults<BSchema, I, S>[];\n};\n\nexport type PartialBlockFromConfig<\n B extends BlockConfig,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> = PartialBlockFromConfigNoChildren<B, I, S> & {\n children?: BlockNoDefaults<BlockSchema, I, S>[];\n};\n\nexport type BlockIdentifier = { id: string } | string;\n\nexport type BlockImplementation<\n TName extends string = string,\n TProps extends PropSchema = PropSchema,\n TContent extends \"inline\" | \"none\" | \"table\" | \"plain\" =\n | \"inline\"\n | \"none\"\n | \"table\"\n | \"plain\",\n> = {\n /**\n * Metadata\n */\n meta?: BlockConfigMeta<TName, TProps>;\n /**\n * A function that converts the block into a DOM element\n */\n render: (\n this:\n | Record<string, never>\n | ({\n blockContentDOMAttributes: Record<string, string>;\n propSchema?: TProps;\n } & (\n | {\n renderType: \"nodeView\";\n props: NodeViewRendererProps;\n }\n | {\n renderType: \"dom\";\n props: undefined;\n }\n )),\n /**\n * The custom block to render\n */\n block: BlockFromConfig<BlockConfig<TName, TProps, TContent>, any, any>,\n /**\n * The BlockNote editor instance\n */\n editor: BlockNoteEditor<\n Record<TName, BlockConfig<TName, TProps, TContent>>\n >,\n ) => {\n dom: HTMLElement | DocumentFragment;\n contentDOM?: HTMLElement;\n ignoreMutation?: (mutation: ViewMutationRecord) => boolean;\n /**\n * Called by ProseMirror when this block's node is updated (e.g. its content\n * or props change). Return `true` to handle the update in place - keeping\n * the existing DOM - or `false` to have the node view recreated via\n * `render`. When omitted, ProseMirror keeps the node view and reconciles its\n * `contentDOM` in place as long as the node type stays the same.\n *\n * Useful for blocks whose `render` builds custom DOM that needs to stay in\n * sync with the node (e.g. a code block rendering a preview of its content).\n */\n update?: (node: ProsemirrorNode) => boolean;\n destroy?: () => void;\n };\n\n /**\n * Exports block to external HTML. If not defined, the output will be the same\n * as `render(...).dom`.\n */\n toExternalHTML?: (\n this: Partial<{\n blockContentDOMAttributes: Record<string, string>;\n propSchema: TProps;\n }>,\n block: BlockFromConfig<BlockConfig<TName, TProps, TContent>, any, any>,\n editor: BlockNoteEditor<\n Record<TName, BlockConfig<TName, TProps, TContent>>\n >,\n context: {\n nestingLevel: number;\n },\n ) =>\n | {\n dom: HTMLElement | DocumentFragment;\n contentDOM?: HTMLElement;\n childrenDOM?: HTMLElement;\n }\n | undefined;\n\n /**\n * Parses an external HTML element into a block of this type when it returns the block props object, otherwise undefined\n */\n parse?: (el: HTMLElement) => Partial<Props<TProps>> | undefined;\n\n /**\n * The blocks that this block should run before.\n * This is used to determine the order in which blocks are parsed\n */\n runsBefore?: string[];\n\n /**\n * Advanced parsing function that controls how content within the block is parsed.\n * This is not recommended to use, and is only useful for advanced use cases.\n */\n parseContent?: (options: {\n el: HTMLElement;\n schema: Schema;\n }) => Fragment | undefined;\n};\n\n/**\n * BlockImplementationOrCreator is a union type of BlockImplementation and a function that returns a BlockImplementation.\n * This is used to create block implementations that can be passed to the createBlockSpec function.\n */\nexport type BlockImplementationOrCreator<\n ConfigOrCreator extends BlockConfigOrCreator = BlockConfigOrCreator,\n TOptions extends Record<string, any> | undefined =\n | Record<string, any>\n | undefined,\n Config extends ExtractBlockConfigFromConfigOrCreator<ConfigOrCreator> =\n ExtractBlockConfigFromConfigOrCreator<ConfigOrCreator>,\n> =\n | BlockImplementation<Config[\"type\"], Config[\"propSchema\"], Config[\"content\"]>\n | (TOptions extends undefined\n ? () => BlockImplementation<\n Config[\"type\"],\n Config[\"propSchema\"],\n Config[\"content\"]\n >\n : (\n options: Partial<TOptions>,\n ) => BlockImplementation<\n Config[\"type\"],\n Config[\"propSchema\"],\n Config[\"content\"]\n >);\n\n/**\n * ExtractBlockImplementationFromImplementationOrCreator is a helper type that extracts the BlockImplementation type from a BlockImplementationOrCreator.\n */\nexport type ExtractBlockImplementationFromImplementationOrCreator<\n ImplementationOrCreator extends\n | BlockImplementation<string, PropSchema, \"inline\" | \"none\" | \"plain\">\n | ((\n ...args: any[]\n ) => BlockImplementation<\n string,\n PropSchema,\n \"inline\" | \"none\" | \"plain\"\n >),\n> = ImplementationOrCreator extends (...args: any[]) => infer Implementation\n ? Implementation\n : ImplementationOrCreator;\n\n// restrict content to \"inline\" and \"none\" only\nexport type CustomBlockImplementation<\n T extends string = string,\n PS extends PropSchema = PropSchema,\n C extends \"inline\" | \"none\" | \"plain\" = \"inline\" | \"none\" | \"plain\",\n> = BlockImplementation<T, PS, C>;\n","import { Node } from \"@tiptap/core\";\n\nimport {\n DOMParser,\n Fragment,\n Node as ProsemirrorNode,\n Schema,\n TagParseRule,\n} 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 { ignoreNonContentMutations } from \"../nodeViewMutations.js\";\nimport { propsToAttributes } from \"../blocks/internal.js\";\nimport { nonFormattingMarks } from \"../markGroups.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 code?: boolean;\n /**\n * When {@link code} is `true`, this can syntax highlight the contents of the\n * inline content with the result of this callback.\n */\n // Method syntax (rather than an arrow-function property) so its parameter is\n // checked bivariantly, keeping a specific implementation assignable to the\n // generic spec record type.\n highlight?(\n inlineContent: Pick<InlineContentFromConfig<T, S>, \"type\" | \"props\">,\n ): string | undefined;\n /**\n * Marks the inline content as rendering a preview with an editable source\n * popup, driven by the editor-wide\n * `SourceInlineContentWithPreviewExtension`.\n */\n hasPreview?: 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 * Advanced parsing function that controls how the content within the inline\n * content is parsed. This is not recommended to use, and is only useful for\n * advanced use cases. Only applies to inline content with `content: \"styled\"`.\n * Return `undefined` to fall through to the default inline content parsing.\n */\n parseContent?: (options: {\n el: HTMLElement;\n schema: Schema;\n }) => Fragment | 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 * The ProseMirror node backing this inline content.\n */\n node: ProsemirrorNode,\n /**\n * Returns this inline content's position in the document. When rendered\n * outside the editor (i.e. serialized to HTML), this is a no-op that returns\n * `undefined`.\n */\n getPos: () => number | undefined,\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\n// Resolves the element whose children hold the inline content's editable\n// content, i.e. the `[data-editable]` element (or the element itself if it is /\n// contains none).\nfunction getEditableElement(element: HTMLElement) {\n if (element.matches(\"[data-editable]\")) {\n return element;\n }\n\n return element.querySelector<HTMLElement>(\"[data-editable]\") || element;\n}\n\n// Parses an element's children as inline content.\nfunction parseInlineContent(el: HTMLElement, schema: Schema) {\n return DOMParser.fromSchema(schema).parse(el, {\n topNode: schema.nodes.paragraph.create(),\n preserveWhitespace: true,\n }).content;\n}\n\n// Flattens parsed inline content into text nodes only. \"plain\" inline content\n// holds text only, so non-text inline nodes are flattened: line breaks become\n// newline characters and other nodes (e.g. mentions) are kept as their text.\nfunction flattenToText(content: Fragment, schema: Schema) {\n const textNodes: ProsemirrorNode[] = [];\n content.forEach((child) => {\n if (child.isText) {\n textNodes.push(child);\n } else {\n const text =\n child.type === schema.linebreakReplacement ? \"\\n\" : child.textContent;\n if (text) {\n textNodes.push(schema.text(text, child.marks));\n }\n }\n });\n\n return Fragment.fromArray(textNodes);\n}\n\nexport function getInlineContentParseRules<C extends CustomInlineContentConfig>(\n config: C,\n customParseFunction?: CustomInlineContentImplementation<C, any>[\"parse\"],\n customParseContentFunction?: CustomInlineContentImplementation<\n C,\n any\n >[\"parseContent\"],\n) {\n // When a custom `parseContent` function is provided (and this inline content\n // actually holds content), it controls how content within the inline content\n // is parsed. This applies to _both_ parse rules below, as content copied from\n // within the editor is tagged with `data-inline-content-type` (matched by the\n // first rule), while content pasted from outside is matched by the custom\n // `parse` function (the second rule). `resolveContentElement` locates the\n // element whose children to parse as a fallback when `parseContent` returns\n // `undefined`.\n // \"plain\" inline content always needs `getContent` so its parsed content is\n // flattened to text (`<br>`/line breaks become newline characters), regardless\n // of whether a custom `parseContent` is provided — mirroring \"plain\" blocks.\n // \"styled\" inline content only needs it to run a custom `parseContent`.\n const getContent =\n config.content === \"plain\" ||\n (customParseContentFunction && config.content === \"styled\")\n ? (resolveContentElement: (el: HTMLElement) => HTMLElement) =>\n (node: HTMLElement, schema: Schema) => {\n const result = customParseContentFunction?.({ el: node, schema });\n\n // `parseContent` may return `undefined` to fall through to the\n // default inline content parsing.\n if (result !== undefined) {\n return config.content === \"plain\"\n ? flattenToText(result, schema)\n : result;\n }\n\n const parsed = parseInlineContent(\n resolveContentElement(node),\n schema,\n );\n return config.content === \"plain\"\n ? flattenToText(parsed, schema)\n : parsed;\n }\n : undefined;\n\n const rules: TagParseRule[] = [\n {\n tag: `[data-inline-content-type=\"${config.type}\"]`,\n contentElement: (element) => getEditableElement(element as HTMLElement),\n getContent: getContent\n ? (node, schema) =>\n getContent(getEditableElement)(node as HTMLElement, schema)\n : undefined,\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 // Because we do the parsing ourselves, we want to preserve whitespace for\n // content we've parsed.\n preserveWhitespace: getContent ? true : undefined,\n getContent: getContent\n ? (node, schema) => getContent((el) => el)(node as HTMLElement, schema)\n : undefined,\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 !== \"none\",\n atom: inlineContentConfig.content === \"none\",\n code: inlineContentImplementation.meta?.code,\n content:\n inlineContentConfig.content === \"styled\"\n ? \"inline*\"\n : inlineContentConfig.content === \"plain\"\n ? \"text*\"\n : \"\",\n // \"plain\" inline content holds unstyled text, so it disallows formatting\n // marks (mirroring \"plain\" blocks). It still allows the non-formatting marks\n // (comments and suggestions/diffs), which annotate content without changing\n // it and are ignored by the content model. `nonFormattingMarks` resolves the\n // group only when at least one such mark is registered, so a plain inline\n // content in an editor without any of them doesn't reference an empty\n // (unknown) mark group.\n marks() {\n return inlineContentConfig.content === \"plain\"\n ? nonFormattingMarks(this.editor)\n : undefined;\n },\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 inlineContentImplementation.parseContent,\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 node,\n () => undefined,\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 node,\n getPos,\n );\n\n const nodeView = addInlineContentAttributes(\n output,\n inlineContentConfig.type,\n node.attrs as Props<T[\"propSchema\"]>,\n inlineContentConfig.propSchema,\n );\n\n // Ignores DOM mutations that don't affect the inline content, so that\n // browser extensions which rewrite the DOM (e.g. Dark Reader) can't\n // trigger an infinite re-render loop that freezes the tab.\n ignoreNonContentMutations(nodeView);\n\n return nodeView;\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 // Rendered outside the editor (serialization), so there's no live node\n // view - derive the node from the content and stub out `getPos`.\n const node = inlineContentToNodes(\n [inlineContent] as any,\n editor.pmSchema,\n )[0];\n\n const output = inlineContentImplementation.render(\n inlineContent,\n updateInlineContent,\n editor,\n node,\n () => undefined,\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 const node = blockToNode(block, pmSchema);\n node.check(); // `blockToNode` is lenient; validate before mutating the doc\n return node;\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, tr.doc),\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 { getNodeId } from \"../../../getBlockInfoFromPos.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 options: {\n fixColumns?: boolean;\n } = {},\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 const node = blockToNode(block, pmSchema);\n node.check(); // `blockToNode` is lenient; validate before mutating the doc\n return node;\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 (!node.type.isInGroup(\"bnBlock\")) {\n return true;\n }\n\n const nodeId = getNodeId(node, tr.doc);\n\n if (!idsOfBlocksToRemove.has(nodeId)) {\n return true;\n }\n\n // Saves the block that is being deleted.\n removedBlocks.push(nodeToBlock(node, tr.doc));\n idsOfBlocksToRemove.delete(nodeId);\n\n if (blocksToInsert.length > 0 && nodeId === 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 // Collapses empty columns/columnLists. Callers where the removal isn't a\n // deletion can opt out - e.g. `moveBlocks` re-inserts the blocks elsewhere\n // and deliberately leaves emptied columns as-is.\n if (options.fixColumns !== false) {\n columnListPositions.forEach((pos) => fixColumnList(tr, pos));\n }\n\n // Converts the nodes created from `blocksToInsert` into full `Block`s.\n const insertedBlocks = nodesToInsert.map((node) =>\n nodeToBlock(node, tr.doc),\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\";\n\nimport { PartialBlock } from \"../../../blocks/defaultBlocks.js\";\nimport { EMPTY_CELL_WIDTH } from \"../../../blocks/index.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\n// This is normally handled using decorations in the\n// `NumberedListIndexingDecorationPlugin`. This does not run when exporting, so\n// we have to add the necessary HTML attributes ourselves.\nconst addIndexToNumberedListItems = (element: HTMLElement) => {\n const numberedListItems = element.querySelectorAll(\n '[data-content-type=\"numberedListItem\"]',\n );\n numberedListItems.forEach((numberedListItem) => {\n const prevNumberedListItem = numberedListItem\n .closest(\".bn-block-outer\")\n ?.previousElementSibling?.querySelector(\n '[data-content-type=\"numberedListItem\"]',\n );\n\n if (!prevNumberedListItem) {\n numberedListItem.setAttribute(\n \"data-index\",\n numberedListItem.getAttribute(\"data-start\") || \"1\",\n );\n } else {\n const prevNumberedListItemIndex =\n prevNumberedListItem.getAttribute(\"data-index\");\n numberedListItem.setAttribute(\n \"data-index\",\n (parseInt(prevNumberedListItemIndex || \"0\") + 1).toString(),\n );\n }\n });\n\n return element;\n};\n\n// Makes the checkboxes in check list items read-only, as the HTML should be\n// static and therefore read-only when rendered.\nconst makeCheckListItemsReadOnly = (element: HTMLElement) => {\n const checkboxes: NodeListOf<HTMLInputElement> = element.querySelectorAll(\n '[data-content-type=\"checkListItem\"] input',\n );\n checkboxes.forEach((checkbox) => {\n checkbox.disabled = true;\n });\n\n return element;\n};\n\n// Forces toggle blocks (toggle headings, toggle list items) to be expanded.\n// This is because event listeners for the toggle button are lost when\n// serializing HTML elements to a string, so the button no longer works if the\n// HTML string is rendered out.\nconst forceToggleBlocksShow = (element: HTMLElement) => {\n const hiddenToggleWrappers = element.querySelectorAll(\n '.bn-toggle-wrapper[data-show-children=\"false\"]',\n );\n hiddenToggleWrappers.forEach((toggleWrapper) => {\n toggleWrapper.setAttribute(\"data-show-children\", \"true\");\n });\n\n return element;\n};\n\n// Adds minimum cell widths, which would normally be done by the\n// `columnResizing` extension. This extension doesn't run when exporting to\n// HTML, so we have to add this manually.\nconst addTableMinCellWidths = (element: HTMLElement) => {\n const tables = element.querySelectorAll('[data-content-type=\"table\"] table');\n tables.forEach((table) => {\n table.setAttribute(\n \"style\",\n `--default-cell-min-width: ${EMPTY_CELL_WIDTH}px;`,\n );\n table.setAttribute(\"data-show-children\", \"true\");\n });\n\n return element;\n};\n\n// Adds table wrapping elements, which would normally be done by the\n// `columnResizing` extension. This extension doesn't run when exporting to\n// HTML, so we have to add this manually. This adds the correct padding to\n// tables.\nconst addTableWrappers = (element: HTMLElement) => {\n const tables = element.querySelectorAll('[data-content-type=\"table\"] table');\n tables.forEach((table) => {\n const tableWrapper = document.createElement(\"div\");\n tableWrapper.className = \"tableWrapper\";\n const tableWrapperInner = document.createElement(\"div\");\n tableWrapperInner.className = \"tableWrapper-inner\";\n\n tableWrapper.appendChild(tableWrapperInner);\n table.parentElement?.appendChild(tableWrapper);\n tableWrapper.appendChild(table);\n });\n\n return element;\n};\n\n// Adds trailing breaks to blocks with empty inline content. This is normally\n// done by ProseMirror, but only when rendering an actual editor. Without them,\n// empty inline content has a height of 0.\nconst addTrailingBreakToEmptyInlineContent = (element: HTMLElement) => {\n const emptyInlineContent = element.querySelectorAll(\n \".bn-inline-content:empty\",\n );\n emptyInlineContent.forEach((inlineContent) => {\n // We actually use a `span` instead of a `br` to avoid potential false\n // positives when parsing.\n const trailingBreak = document.createElement(\"span\");\n trailingBreak.className = \"ProseMirror-trailingBreak\";\n trailingBreak.setAttribute(\"style\", \"display: inline-block;\");\n\n inlineContent.appendChild(trailingBreak);\n });\n\n return element;\n};\n\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 // Set of transforms to run on the output HTML element after serializing\n // blocks. These are used to add HTML elements, attributes, or class names\n // which would normally be done by extensions and plugins. Since these don't\n // run when converting blocks to HTML, tranforms are used to mock their\n // functionality so that the rendered HTML looks identical to that of a live\n // editor.\n const transforms: ((element: HTMLElement) => HTMLElement)[] = [\n addIndexToNumberedListItems,\n makeCheckListItemsReadOnly,\n forceToggleBlocksShow,\n addTableMinCellWidths,\n addTableWrappers,\n addTrailingBreakToEmptyInlineContent,\n ];\n\n return {\n serializeBlocks: (\n blocks: PartialBlock<BSchema, I, S>[],\n options: { document?: Document },\n ) => {\n let element = serializeBlocksInternalHTML(\n editor,\n blocks,\n serializer,\n options,\n );\n\n for (const transform of transforms) {\n element = transform(element);\n }\n\n return element.outerHTML;\n },\n };\n};\n","import { digestString } from \"lib0/hash/fnv1a\";\nimport type { UserStore } from \"./UserStore.js\";\n\n/**\n * Deterministic hash of a string to an unsigned 32-bit integer.\n */\nconst hashStr = (s: string): number => {\n let hash = 0;\n for (let i = 0; i < s.length; i++) {\n hash = Math.imul(31, hash) + s.charCodeAt(i);\n }\n return Math.abs(hash);\n};\n\n/** Fallback palette used when a user has no resolved color of their own. */\nexport const userColorPalette: Array<{ light: string; dark: string }> = [\n { light: \"#fff0c2\", dark: \"#8a6d1a\" },\n { light: \"#fcc9c3\", dark: \"#8a2e24\" },\n { light: \"#d4e8eb\", dark: \"#4a7178\" },\n { light: \"#c2eeff\", dark: \"#1a6e8a\" },\n { light: \"#bef3ff\", dark: \"#0a7a8a\" },\n];\n\n/** The deterministic {@link userColorPalette} entry for a single user id. */\nexport const fallbackColorForUserId = (\n id: string,\n): { light: string; dark: string } =>\n userColorPalette[hashStr(id) % userColorPalette.length];\n\n/**\n * The (first) user's resolved color from the {@link UserStore}, or their\n * {@link fallbackColorForUserId} palette entry. Used where a concrete color\n * string is needed (the portaled hover tooltip); marks themselves use the\n * cascaded {@link userColorVarNames} properties instead.\n */\nexport const colorsForUserIds = (\n userStore: UserStore,\n userIds: readonly string[] | undefined | null,\n): { light: string; dark: string } => {\n if (!userIds || userIds.length === 0) {\n return userColorPalette[0];\n }\n const firstId = userIds[0];\n const user = userStore.getUser(firstId);\n if (user?.color && user.colorLight) {\n return { light: user.colorLight, dark: user.color };\n }\n return fallbackColorForUserId(firstId);\n};\n\n/**\n * Reduce a user id to a fixed-width `[0-9a-f]` token safe to embed in a CSS\n * custom-property name. Uses the (non-cryptographic) FNV-1a 32-bit hash; a\n * collision only means two authors share a highlight color.\n */\nexport const cssVarUserId = (id: string): string =>\n digestString(id).toString(16).padStart(8, \"0\");\n\n/**\n * The `--user-color-<key>-{light,dark}` custom-property names for a user. Set on\n * the editor root by `AttributionExtension`, read by the mark wrapper via\n * `var(..., <fallback>)`.\n */\nexport const userColorVarNames = (\n id: string,\n): { light: string; dark: string } => {\n const key = cssVarUserId(id);\n return {\n light: `--user-color-${key}-light`,\n dark: `--user-color-${key}-dark`,\n };\n};\n","// from https://raw.githubusercontent.com/ueberdosis/tiptap/develop/packages/core/src/EventEmitter.ts (MIT)\n\ntype StringKeyOf<T> = Extract<keyof T, string>;\ntype CallbackType<\n T extends Record<string, any>,\n EventName extends StringKeyOf<T>,\n> = T[EventName] extends any[] ? T[EventName] : [T[EventName]];\ntype CallbackFunction<\n T extends Record<string, any>,\n EventName extends StringKeyOf<T>,\n> = (...props: CallbackType<T, EventName>) => any;\n\nexport class EventEmitter<T extends Record<string, any>> {\n // eslint-disable-next-line @typescript-eslint/ban-types\n private callbacks: { [key: string]: Function[] } = {};\n\n public on<EventName extends StringKeyOf<T>>(\n event: EventName,\n fn: CallbackFunction<T, EventName>,\n ) {\n if (!this.callbacks[event]) {\n this.callbacks[event] = [];\n }\n\n this.callbacks[event].push(fn);\n\n return () => this.off(event, fn);\n }\n\n protected emit<EventName extends StringKeyOf<T>>(\n event: EventName,\n ...args: CallbackType<T, EventName>\n ) {\n const callbacks = this.callbacks[event];\n\n if (callbacks) {\n callbacks.forEach((callback) => callback.apply(this, args));\n }\n }\n\n public off<EventName extends StringKeyOf<T>>(\n event: EventName,\n fn?: CallbackFunction<T, EventName>,\n ) {\n const callbacks = this.callbacks[event];\n\n if (callbacks) {\n if (fn) {\n this.callbacks[event] = callbacks.filter((callback) => callback !== fn);\n } else {\n delete this.callbacks[event];\n }\n }\n }\n\n protected removeAllListeners(): void {\n this.callbacks = {};\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 {\n getBlockInfoAtNearest,\n getNodeId,\n} from \"../../../getBlockInfoFromPos.js\";\nimport { getNodeById } from \"../../../nodeUtil.js\";\nimport { insertBlocks } from \"../insertBlocks/insertBlocks.js\";\nimport { removeAndInsertBlocks } from \"../replaceBlocks/replaceBlocks.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 = getBlockInfoAtNearest(tr, tr.selection.anchor);\n\n const anchorBlockId = getNodeId(anchorBlockPosInfo.bnBlock.node, tr.doc);\n\n if (tr.selection instanceof CellSelection) {\n return {\n type: \"cell\" as const,\n anchorBlockId,\n anchorCellOffset:\n tr.selection.$anchorCell.pos - anchorBlockPosInfo.bnBlock.beforePos,\n headCellOffset:\n tr.selection.$headCell.pos - anchorBlockPosInfo.bnBlock.beforePos,\n };\n } else if (tr.selection instanceof NodeSelection) {\n return {\n type: \"node\" as const,\n anchorBlockId,\n };\n } else {\n const headBlockPosInfo = getBlockInfoAtNearest(tr, tr.selection.head);\n\n return {\n type: \"text\" as const,\n anchorBlockId,\n headBlockId: getNodeId(headBlockPosInfo.bnBlock.node, tr.doc),\n anchorOffset:\n tr.selection.anchor - anchorBlockPosInfo.bnBlock.beforePos,\n headOffset: tr.selection.head - headBlockPosInfo.bnBlock.beforePos,\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// Replaces top-level `column` blocks with their children, as a `column` is not\n// a valid block outside a `columnList`. Other blocks are returned as-is.\nfunction flattenColumns(\n blocks: Block<any, any, any>[],\n): Block<any, any, any>[] {\n return blocks.flatMap((block) =>\n block.type === \"column\" ? block.children : [block],\n );\n}\n\n/**\n * Removes the given blocks from the editor, then inserts them before/after a\n * reference block.\n * @param editor The BlockNote editor instance to move the blocks in.\n * @param blocks The blocks to move.\n * @param referenceBlock The reference block to insert the blocks before/after.\n * @param placement Whether to insert the blocks before or after the reference\n * block.\n */\nexport function moveBlocks(\n editor: BlockNoteEditor<any, any, any>,\n blocks: Block<any, any, any>[],\n referenceBlock: BlockIdentifier,\n placement: \"before\" | \"after\",\n) {\n editor.transact((tr) => {\n // Don't fix columns/columnLists in the removal step. Since a move is a\n // rearrangement rather than a deletion, columns that it empties out are\n // deliberately left as-is instead of being collapsed - this keeps moves\n // free of side effects (and reversible by moving back), and matches\n // dragging a block out of a column, which doesn't collapse it either.\n // Fixing them mid-move also broke the following case:\n // <column>\n // <paragraph></paragraph>\n // <paragraph>Paragraph</paragraph>\n // </column>\n // When the non-empty block is moved up, the column is seen as empty and\n // collapsed in the removal step, so the following insertion fails.\n removeAndInsertBlocks(tr, blocks, [], { fixColumns: false });\n insertBlocks<any, any, any>(\n tr,\n flattenColumns(blocks),\n referenceBlock,\n placement,\n );\n });\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 moveBlocks(editor, 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(\n editor: BlockNoteEditor<any, any, any>,\n blockIdentifier?: BlockIdentifier,\n) {\n editor.transact(() => {\n let sourceBlock: Block<any, any, any> | undefined;\n if (blockIdentifier) {\n sourceBlock = editor.getBlock(blockIdentifier);\n if (!sourceBlock) {\n return;\n }\n } else {\n const selection = editor.getSelection();\n sourceBlock =\n selection?.blocks[0] || editor.getTextCursorPosition().block;\n }\n\n const moveUpPlacement = getMoveUpPlacement(\n editor,\n editor.getPrevBlock(sourceBlock),\n editor.getParentBlock(sourceBlock),\n );\n\n if (!moveUpPlacement) {\n return;\n }\n\n if (blockIdentifier) {\n moveBlocks(\n editor,\n [sourceBlock],\n moveUpPlacement.referenceBlock,\n moveUpPlacement.placement,\n );\n } else {\n moveSelectedBlocksAndSelection(\n editor,\n moveUpPlacement.referenceBlock,\n moveUpPlacement.placement,\n );\n }\n });\n}\n\nexport function moveBlocksDown(\n editor: BlockNoteEditor<any, any, any>,\n blockIdentifier?: BlockIdentifier,\n) {\n editor.transact(() => {\n let sourceBlock: Block<any, any, any> | undefined;\n if (blockIdentifier) {\n sourceBlock = editor.getBlock(blockIdentifier);\n if (!sourceBlock) {\n return;\n }\n } else {\n const selection = editor.getSelection();\n sourceBlock =\n selection?.blocks[selection?.blocks.length - 1] ||\n editor.getTextCursorPosition().block;\n }\n\n const moveDownPlacement = getMoveDownPlacement(\n editor,\n editor.getNextBlock(sourceBlock),\n editor.getParentBlock(sourceBlock),\n );\n\n if (!moveDownPlacement) {\n return;\n }\n\n if (blockIdentifier) {\n moveBlocks(\n editor,\n [sourceBlock],\n moveDownPlacement.referenceBlock,\n moveDownPlacement.placement,\n );\n } else {\n moveSelectedBlocksAndSelection(\n editor,\n moveDownPlacement.referenceBlock,\n moveDownPlacement.placement,\n );\n }\n });\n}\n","import { Fragment, NodeRange, NodeType, Slice } from \"prosemirror-model\";\nimport { Transaction } from \"prosemirror-state\";\nimport { canJoin, liftTarget, ReplaceAroundStep } from \"prosemirror-transform\";\n\nimport { BlockNoteEditor } from \"../../../../editor/BlockNoteEditor.js\";\nimport { getBlockInfoFromSelection } from \"../../../getBlockInfoFromPos.js\";\n\n/**\n * Modified version of prosemirror-schema-list's sinkItem.\n * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts\n *\n * Changes from the original:\n * 1. Range predicate checks node.type instead of firstChild.type\n * 2. nestedBefore checks groupType instead of parent.type\n * 3. Slice creates groupType instead of parent.type\n * 4. Operates on Transaction directly instead of state+dispatch\n */\nfunction sinkItem(tr: Transaction, itemType: NodeType, groupType: NodeType) {\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 1\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 2\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 3\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 sinkItem(\n tr,\n editor.pmSchema.nodes[\"blockContainer\"],\n editor.pmSchema.nodes[\"blockGroup\"],\n );\n });\n}\n\n/**\n * Modified version of prosemirror-schema-list's liftToOuterList.\n * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts\n *\n * Changes from the original:\n * 1. Operates on Transaction directly instead of state+dispatch (TipTap compat)\n * 2. When the lifted block already has children (a groupType child), uses deeper\n * openStart/offset so siblings merge into the existing group instead of\n * creating a second one (which would violate blockContainer's schema)\n * 3. Uses groupType.create() instead of range.parent.copy() (same as sinkItem)\n */\nfunction liftToOuterList(\n tr: Transaction,\n itemType: NodeType,\n groupType: NodeType, // change 3\n range: NodeRange,\n) {\n const end = range.end;\n const endOfList = range.$to.end(range.depth);\n\n if (end < endOfList) {\n // There are siblings after the lifted items, which must become\n // children of the last item\n const blockBeingLifted = range.parent.child(range.endIndex - 1);\n const nestedAfter =\n blockBeingLifted.lastChild &&\n blockBeingLifted.lastChild.type === groupType; // change 2\n\n tr.step(\n new ReplaceAroundStep(\n end - (nestedAfter ? 2 : 1), // change 2: go deeper when merging into existing children\n endOfList,\n end,\n endOfList,\n new Slice(\n Fragment.from(\n itemType.create(null, groupType.create()), // change 3\n ),\n nestedAfter ? 2 : 1, // change 2: open deeper when merging into existing children\n 0,\n ),\n nestedAfter ? 0 : 1, // change 2: Slice.insertAt offsets by openStart, so 0+2=2 lands inside existing bg\n true,\n ),\n );\n range = new NodeRange(\n tr.doc.resolve(range.$from.pos),\n tr.doc.resolve(endOfList),\n range.depth,\n );\n }\n\n const target = liftTarget(range);\n if (target == null) {\n return false;\n }\n\n tr.lift(range, target);\n\n const $after = tr.doc.resolve(tr.mapping.map(end, -1) - 1);\n if (\n canJoin(tr.doc, $after.pos) &&\n $after.nodeBefore!.type === $after.nodeAfter!.type\n ) {\n tr.join($after.pos);\n }\n\n tr.scrollIntoView();\n return true;\n}\n\n/**\n * Modified version of prosemirror-schema-list's liftListItem.\n * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts\n *\n * Changes from the original:\n * 1. Range predicate checks node.type instead of firstChild.type (same as sinkItem)\n * 2. Passes groupType to liftToOuterList\n * 3. Operates on Transaction directly instead of state+dispatch\n * 4. Skips liftOutOfList (root-level blocks can't be unnested in BlockNote)\n */\nexport function liftItem(\n tr: Transaction,\n itemType: NodeType,\n groupType: NodeType, // change 2\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 1\n );\n if (!range) {\n return false;\n }\n\n if ($from.node(range.depth - 1).type === itemType) {\n // Inside a parent node\n return liftToOuterList(tr, itemType, groupType, range); // change 2\n }\n\n // This is the \"liftOutOfList\" path — lifting out of a list entirely.\n // Not applicable to BlockNote (root-level blocks can't be unnested). // change 4\n return false;\n}\n\nexport function unnestBlock(editor: BlockNoteEditor<any, any, any>) {\n return editor.transact((tr) =>\n liftItem(\n tr,\n editor.pmSchema.nodes[\"blockContainer\"],\n editor.pmSchema.nodes[\"blockGroup\"],\n ),\n );\n}\n\nexport function canNestBlock(editor: BlockNoteEditor<any, any, any>) {\n return editor.transact((tr) => {\n const { bnBlock: blockContainer } = getBlockInfoFromSelection(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 } = getBlockInfoFromSelection(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\";\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\n const posInfo = getNodeById(id, doc);\n if (!posInfo) {\n return undefined;\n }\n\n return nodeToBlock(posInfo.node, doc);\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 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, doc);\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 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, doc);\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 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, doc);\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);\n });\n }\n\n /**\n * Gets a snapshot of an existing block from the editor.\n * @param blockIdentifier The identifier of an existing block that should be\n * retrieved.\n * @returns The block that matches the identifier, or `undefined` if no\n * matching block was found.\n */\n public getBlock(\n blockIdentifier: BlockIdentifier,\n ): Block<BSchema, ISchema, SSchema> | undefined {\n return this.editor.transact((tr) => getBlock(tr.doc, blockIdentifier));\n }\n\n /**\n * Gets a snapshot of the previous sibling of an existing block from the\n * editor.\n * @param blockIdentifier The identifier of an existing block for which the\n * previous sibling should be retrieved.\n * @returns The previous sibling of the block that matches the identifier.\n * `undefined` if no matching block was found, or it's the first child/block\n * in the document.\n */\n public getPrevBlock(\n blockIdentifier: BlockIdentifier,\n ): Block<BSchema, ISchema, SSchema> | undefined {\n return this.editor.transact((tr) => getPrevBlock(tr.doc, blockIdentifier));\n }\n\n /**\n * Gets a snapshot of the next sibling of an existing block from the editor.\n * @param blockIdentifier The identifier of an existing block for which the\n * next sibling should be retrieved.\n * @returns The next sibling of the block that matches the identifier.\n * `undefined` if no matching block was found, or it's the last child/block in\n * the document.\n */\n public getNextBlock(\n blockIdentifier: BlockIdentifier,\n ): Block<BSchema, ISchema, SSchema> | undefined {\n return this.editor.transact((tr) => getNextBlock(tr.doc, blockIdentifier));\n }\n\n /**\n * Gets a snapshot of the parent of an existing block from the editor.\n * @param blockIdentifier The identifier of an existing block for which the\n * parent should be retrieved.\n * @returns The parent of the block that matches the identifier. `undefined`\n * if no matching block was found, or the block isn't nested.\n */\n public getParentBlock(\n blockIdentifier: BlockIdentifier,\n ): Block<BSchema, ISchema, SSchema> | undefined {\n return this.editor.transact((tr) =>\n getParentBlock(tr.doc, blockIdentifier),\n );\n }\n\n /**\n * Traverses all blocks in the editor depth-first, and executes a callback for each.\n * @param callback The callback to execute for each block. Returning `false` stops the traversal.\n * @param reverse Whether the blocks should be traversed in reverse order.\n */\n public forEachBlock(\n callback: (block: Block<BSchema, ISchema, SSchema>) => boolean,\n reverse = false,\n ): void {\n const blocks = this.document.slice();\n\n if (reverse) {\n blocks.reverse();\n }\n\n function traverseBlockArray(\n blockArray: Block<BSchema, ISchema, SSchema>[],\n ): boolean {\n for (const block of blockArray) {\n if (callback(block) === false) {\n return false;\n }\n\n const children = reverse\n ? block.children.slice().reverse()\n : block.children;\n\n if (!traverseBlockArray(children)) {\n return false;\n }\n }\n\n return true;\n }\n\n traverseBlockArray(blocks);\n }\n\n /**\n * Inserts new blocks into the editor. If a block's `id` is undefined, BlockNote generates one automatically. Throws an\n * error if the reference block could not be found.\n * @param blocksToInsert An array of partial blocks that should be inserted.\n * @param referenceBlock An identifier for an existing block, at which the new blocks should be inserted.\n * @param placement Whether the blocks should be inserted just before, just after, or nested inside the\n * `referenceBlock`.\n */\n public insertBlocks(\n blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[],\n referenceBlock: BlockIdentifier,\n placement: \"before\" | \"after\" = \"before\",\n ) {\n return this.editor.transact((tr) =>\n insertBlocks(tr, blocksToInsert, referenceBlock, placement),\n );\n }\n\n /**\n * Updates an existing block in the editor. Since updatedBlock is a PartialBlock object, some fields might not be\n * defined. These undefined fields are kept as-is from the existing block. Throws an error if the block to update could\n * not be found.\n * @param blockToUpdate The block that should be updated.\n * @param update A partial block which defines how the existing block should be changed.\n */\n public updateBlock(\n blockToUpdate: BlockIdentifier,\n update: PartialBlock<BSchema, ISchema, SSchema>,\n ) {\n return this.editor.transact((tr) => updateBlock(tr, blockToUpdate, update));\n }\n\n /**\n * Removes existing blocks from the editor. Throws an error if any of the blocks could not be found.\n * @param blocksToRemove An array of identifiers for existing blocks that should be removed.\n */\n public removeBlocks(blocksToRemove: BlockIdentifier[]) {\n return this.editor.transact(\n (tr) => removeAndInsertBlocks(tr, blocksToRemove, []).removedBlocks,\n );\n }\n\n /**\n * Replaces existing blocks in the editor with new blocks. If the blocks that should be removed are not adjacent or\n * are at different nesting levels, `blocksToInsert` will be inserted at the position of the first block in\n * `blocksToRemove`. Throws an error if any of the blocks to remove could not be found.\n * @param blocksToRemove An array of blocks that should be replaced.\n * @param blocksToInsert An array of partial blocks to replace the old ones with.\n */\n public replaceBlocks(\n blocksToRemove: BlockIdentifier[],\n blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[],\n ) {\n return this.editor.transact((tr) =>\n removeAndInsertBlocks(tr, blocksToRemove, blocksToInsert),\n );\n }\n\n /**\n * Checks if the block containing the text cursor can be nested.\n */\n public canNestBlock() {\n return canNestBlock(this.editor);\n }\n\n /**\n * Nests the block containing the text cursor into the block above it.\n */\n public nestBlock() {\n nestBlock(this.editor);\n }\n\n /**\n * Checks if the block containing the text cursor is nested.\n */\n public canUnnestBlock() {\n return canUnnestBlock(this.editor);\n }\n\n /**\n * Lifts the block containing the text cursor out of its parent.\n */\n public unnestBlock() {\n unnestBlock(this.editor);\n }\n\n /**\n * Moves the selected blocks up. If the previous block has children, moves\n * them to the end of its children. If there is no previous block, but the\n * current blocks share a common parent, moves them out of & before it. If a\n * `blockIdentifier` is provided, that block is moved instead of the\n * selection, and the selection is left unchanged.\n */\n public moveBlocksUp(blockIdentifier?: BlockIdentifier) {\n return moveBlocksUp(this.editor, blockIdentifier);\n }\n\n /**\n * Moves the selected blocks down. If the next block has children, moves\n * them to the start of its children. If there is no next block, but the\n * current blocks share a common parent, moves them out of & after it. If a\n * `blockIdentifier` is provided, that block is moved instead of the\n * selection, and the selection is left unchanged.\n */\n public moveBlocksDown(blockIdentifier?: BlockIdentifier) {\n return moveBlocksDown(this.editor, blockIdentifier);\n }\n}\n","import type { BlockNoteEditor } from \"../BlockNoteEditor.js\";\nimport {\n getBlocksChangedByTransaction,\n type BlocksChanged,\n} from \"../../api/getBlocksChangedByTransaction.js\";\nimport { Transaction } from \"prosemirror-state\";\nimport { EventEmitter } from \"../../util/EventEmitter.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../schema/index.js\";\n\n/**\n * A function that can be used to unsubscribe from an event.\n */\nexport type Unsubscribe = () => void;\n\n/**\n * EventManager is a class which manages the events of the editor\n */\nexport class EventManager<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n> extends EventEmitter<{\n onChange: [\n ctx: {\n editor: BlockNoteEditor<BSchema, I, S>;\n transaction: Transaction;\n appendedTransactions: Transaction[];\n },\n ];\n onSelectionChange: [\n ctx: { editor: BlockNoteEditor<BSchema, I, S>; transaction: Transaction },\n ];\n onMount: [ctx: { editor: BlockNoteEditor<BSchema, I, S> }];\n onUnmount: [ctx: { editor: BlockNoteEditor<BSchema, I, S> }];\n}> {\n constructor(private editor: BlockNoteEditor<BSchema, I, S>) {\n super();\n // We register tiptap events only once the editor is finished initializing\n // otherwise we would be trying to register events on a tiptap editor which does not exist yet\n editor.on(\"create\", () => {\n editor._tiptapEditor.on(\n \"update\",\n ({ transaction, appendedTransactions }) => {\n this.emit(\"onChange\", { editor, transaction, appendedTransactions });\n },\n );\n editor._tiptapEditor.on(\"selectionUpdate\", ({ transaction }) => {\n this.emit(\"onSelectionChange\", { editor, transaction });\n });\n editor._tiptapEditor.on(\"mount\", () => {\n this.emit(\"onMount\", { editor });\n });\n editor._tiptapEditor.on(\"unmount\", () => {\n this.emit(\"onUnmount\", { editor });\n });\n });\n }\n\n /**\n * Register a callback that will be called when the editor changes.\n */\n public onChange(\n callback: (\n editor: BlockNoteEditor<BSchema, I, S>,\n ctx: {\n getChanges(): BlocksChanged<BSchema, I, S>;\n },\n ) => void,\n /**\n * If true, the callback will be triggered when the changes are caused by a remote user\n * @default true\n */\n includeUpdatesFromRemote = true,\n ): Unsubscribe {\n const cb = ({\n transaction,\n appendedTransactions,\n }: {\n transaction: Transaction;\n appendedTransactions: Transaction[];\n }) => {\n if (!includeUpdatesFromRemote && isRemoteTransaction(transaction)) {\n // don't trigger the callback if the changes are caused by a remote user\n return;\n }\n callback(this.editor, {\n getChanges() {\n return getBlocksChangedByTransaction<BSchema, I, S>(\n transaction,\n appendedTransactions,\n );\n },\n });\n };\n this.on(\"onChange\", cb);\n\n return () => {\n this.off(\"onChange\", cb);\n };\n }\n\n /**\n * Register a callback that will be called when the selection changes.\n */\n public onSelectionChange(\n callback: (editor: BlockNoteEditor<BSchema, I, S>) => void,\n /**\n * If true, the callback will be triggered when the selection changes due to a yjs sync (i.e.: other user was typing)\n */\n includeSelectionChangedByRemote = false,\n ): Unsubscribe {\n const cb = (e: { transaction: Transaction }) => {\n if (\n !includeSelectionChangedByRemote &&\n isRemoteTransaction(e.transaction)\n ) {\n // don't trigger the callback if the selection changed because of a remote user\n return;\n }\n callback(this.editor);\n };\n\n this.on(\"onSelectionChange\", cb);\n\n return () => {\n this.off(\"onSelectionChange\", cb);\n };\n }\n\n /**\n * Register a callback that will be called when the editor is mounted.\n */\n public onMount(\n callback: (ctx: { editor: BlockNoteEditor<BSchema, I, S> }) => void,\n ): Unsubscribe {\n this.on(\"onMount\", callback);\n\n return () => {\n this.off(\"onMount\", callback);\n };\n }\n\n /**\n * Register a callback that will be called when the editor is unmounted.\n */\n public onUnmount(\n callback: (ctx: { editor: BlockNoteEditor<BSchema, I, S> }) => void,\n ): Unsubscribe {\n this.on(\"onUnmount\", callback);\n\n return () => {\n this.off(\"onUnmount\", callback);\n };\n }\n}\n\nfunction isRemoteTransaction(transaction: Transaction): boolean {\n return !!transaction.getMeta(\"y-sync$\");\n}\n","function getChildIndex(node: Element) {\n return Array.prototype.indexOf.call(node.parentElement!.childNodes, node);\n}\n\nfunction isWhitespaceNode(node: Node) {\n return node.nodeType === 3 && !/\\S/.test(node.nodeValue || \"\");\n}\n\n/**\n * Step 1, Turns:\n *\n * <ul>\n * <li>item</li>\n * <li>\n * <ul>\n * <li>...</li>\n * <li>...</li>\n * </ul>\n * </li>\n *\n * Into:\n * <ul>\n * <li>item</li>\n * <ul>\n * <li>...</li>\n * <li>...</li>\n * </ul>\n * </ul>\n *\n */\nfunction liftNestedListsToParent(element: HTMLElement) {\n element.querySelectorAll(\"li > ul, li > ol\").forEach((list) => {\n const index = getChildIndex(list);\n const parentListItem = list.parentElement!;\n const siblingsAfter = Array.from(parentListItem.childNodes).slice(\n index + 1,\n );\n list.remove();\n siblingsAfter.forEach((sibling) => {\n sibling.remove();\n });\n\n parentListItem.insertAdjacentElement(\"afterend\", list);\n\n siblingsAfter.reverse().forEach((sibling) => {\n if (isWhitespaceNode(sibling)) {\n return;\n }\n const siblingContainer = document.createElement(\"li\");\n siblingContainer.append(sibling);\n list.insertAdjacentElement(\"afterend\", siblingContainer);\n });\n if (parentListItem.childNodes.length === 0) {\n parentListItem.remove();\n }\n });\n}\n\n/**\n * Step 2, Turns (output of liftNestedListsToParent):\n *\n * <li>item</li>\n * <ul>\n * <li>...</li>\n * <li>...</li>\n * </ul>\n *\n * Into:\n * <div>\n * <li>item</li>\n * <div data-node-type=\"blockGroup\">\n * <ul>\n * <li>...</li>\n * <li>...</li>\n * </ul>\n * </div>\n * </div>\n *\n * This resulting format is parsed\n */\nfunction createGroups(element: HTMLElement) {\n element.querySelectorAll(\"li + ul, li + ol\").forEach((list) => {\n const listItem = list.previousElementSibling as HTMLElement;\n const blockContainer = document.createElement(\"div\");\n\n listItem.insertAdjacentElement(\"afterend\", blockContainer);\n blockContainer.append(listItem);\n\n const blockGroup = document.createElement(\"div\");\n blockGroup.setAttribute(\"data-node-type\", \"blockGroup\");\n blockContainer.append(blockGroup);\n\n while (\n blockContainer.nextElementSibling?.nodeName === \"UL\" ||\n blockContainer.nextElementSibling?.nodeName === \"OL\"\n ) {\n blockGroup.append(blockContainer.nextElementSibling);\n }\n });\n}\n\n// prevent XSS, similar to https://github.com/ProseMirror/prosemirror-view/blob/1251b2b412656a2a06263e4187574beb43651273/src/clipboard.ts#L204\n// https://github.com/TypeCellOS/BlockNote/issues/601\nlet _detachedDoc: Document | null = null;\nfunction detachedDoc() {\n return (\n _detachedDoc ||\n (_detachedDoc = document.implementation.createHTMLDocument(\"title\"))\n );\n}\n\nexport function nestedListsToBlockNoteStructure(\n elementOrHTML: HTMLElement | string,\n) {\n if (typeof elementOrHTML === \"string\") {\n const element = detachedDoc().createElement(\"div\");\n element.innerHTML = elementOrHTML;\n elementOrHTML = element;\n }\n liftNestedListsToParent(elementOrHTML);\n createGroups(elementOrHTML);\n return elementOrHTML;\n}\n","/**\n * Checks if the given HTML element contains markers indicating it was\n * generated by Notion. Notion uses `\\n` in text nodes to represent hard\n * breaks, which is non-standard but intentional.\n *\n * Detected by the `<!-- notionvc: UUID -->` comment that Notion places\n * on the clipboard.\n */\nfunction isNotionHTML(element: HTMLElement): boolean {\n const walker = element.ownerDocument.createTreeWalker(\n element,\n // NodeFilter.SHOW_COMMENT\n 128,\n );\n\n let node: Node | null;\n while ((node = walker.nextNode())) {\n if (/^\\s*notionvc:/.test(node.nodeValue || \"\")) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Normalizes whitespace in text nodes by collapsing runs of whitespace\n * (including newlines) to single spaces, matching CSS white-space:normal\n * behavior.\n *\n * This is needed because ProseMirror's DOMParser, when `linebreakReplacement`\n * is set in the schema (as BlockNote does for hard breaks), converts `\\n`\n * characters in text nodes to hard break nodes instead of collapsing them.\n * This causes HTML source line wrapping (e.g. from MS Word) to create\n * visible line breaks in the editor.\n *\n * Skipped for sources like Notion that intentionally use `\\n` in text nodes\n * to represent hard breaks instead of `<br>` tags.\n *\n * Skips `<pre>` and `<code>` elements where whitespace should be preserved.\n */\nfunction normalizeTextNodeWhitespace(element: HTMLElement) {\n const preserveWSTags = new Set([\"PRE\", \"CODE\"]);\n const walker = element.ownerDocument.createTreeWalker(\n element,\n // NodeFilter.SHOW_TEXT\n 4,\n {\n acceptNode(node) {\n // Skip text nodes inside pre/code elements\n let parent = node.parentElement;\n while (parent && parent !== element) {\n if (preserveWSTags.has(parent.tagName)) {\n // NodeFilter.FILTER_REJECT\n return 2;\n }\n parent = parent.parentElement;\n }\n // NodeFilter.FILTER_ACCEPT\n return 1;\n },\n },\n );\n\n const textNodes: Text[] = [];\n let node: Node | null;\n while ((node = walker.nextNode())) {\n textNodes.push(node as Text);\n }\n\n for (const textNode of textNodes) {\n if (textNode.nodeValue && /[\\r\\n]/.test(textNode.nodeValue)) {\n textNode.nodeValue = textNode.nodeValue.replace(/[ \\t\\r\\n\\f]+/g, \" \");\n }\n }\n}\n\n/**\n * Normalizes whitespace in HTML text nodes to match standard CSS\n * white-space:normal behavior. Skipped for Notion HTML which intentionally\n * uses `\\n` for hard breaks.\n */\nexport function preprocessHTMLWhitespace(element: HTMLElement) {\n if (!isNotionHTML(element)) {\n normalizeTextNodeWhitespace(element);\n }\n}\n","import { DOMParser, Schema } from \"prosemirror-model\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\n\nimport { Block } from \"../../../blocks/defaultBlocks.js\";\nimport { EMPTY_BLOCK_PLACEHOLDER } from \"../../exporters/html/util/serializeBlocksExternalHTML.js\";\nimport { nodeToBlock } from \"../../nodeConversions/nodeToBlock.js\";\nimport { nestedListsToBlockNoteStructure } from \"./util/nestedLists.js\";\nimport { preprocessHTMLWhitespace } from \"./util/normalizeWhitespace.js\";\n\n/**\n * Removes the placeholder character that the external HTML exporter inserts\n * into empty inline-content blocks (see `EMPTY_BLOCK_PLACEHOLDER`). The\n * placeholder keeps such blocks from being dropped while the HTML is parsed;\n * stripping it here lets the block round trip back to genuinely empty content.\n */\nfunction stripEmptyBlockPlaceholder(content: any[]): any[] {\n const stripped: any[] = [];\n\n for (const item of content) {\n if (item.type === \"text\") {\n const text = item.text.split(EMPTY_BLOCK_PLACEHOLDER).join(\"\");\n if (text.length > 0) {\n stripped.push({ ...item, text });\n }\n } else if (Array.isArray(item.content)) {\n // Links and custom inline content that hold nested styled text.\n stripped.push({\n ...item,\n content: stripEmptyBlockPlaceholder(item.content),\n });\n } else {\n stripped.push(item);\n }\n }\n\n return stripped;\n}\n\nfunction stripEmptyBlockPlaceholderFromBlocks(blocks: Block<any, any, any>[]) {\n for (const block of blocks) {\n if (Array.isArray(block.content)) {\n (block as any).content = stripEmptyBlockPlaceholder(block.content);\n }\n if (block.children.length > 0) {\n stripEmptyBlockPlaceholderFromBlocks(block.children);\n }\n }\n}\n\nexport function HTMLToBlocks<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(html: string, pmSchema: Schema): Block<BSchema, I, S>[] {\n const htmlNode = nestedListsToBlockNoteStructure(html);\n preprocessHTMLWhitespace(htmlNode);\n const parser = DOMParser.fromSchema(pmSchema);\n\n // Other approach might be to use\n // const doc = pmSchema.nodes[\"doc\"].createAndFill()!;\n // and context: doc.resolve(3),\n\n const parentNode = parser.parse(htmlNode, {\n topNode: pmSchema.nodes[\"blockGroup\"].create(),\n });\n\n const blocks: Block<BSchema, I, S>[] = [];\n\n for (let i = 0; i < parentNode.childCount; i++) {\n blocks.push(nodeToBlock(parentNode.child(i), parentNode));\n }\n\n stripEmptyBlockPlaceholderFromBlocks(blocks);\n\n return blocks;\n}\n","import { isVideoUrl } from \"../../../util/string.js\";\n\n/**\n * Custom markdown-to-HTML converter for BlockNote.\n * Replaces the unified/remark/rehype pipeline with a direct, minimal implementation\n * that handles exactly the markdown features BlockNote needs.\n */\n\n// ─── HTML Escaping ───────────────────────────────────────────────────────────\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction isAlphanumeric(char: string | undefined): boolean {\n if (!char) {\n return false;\n }\n return /\\w/.test(char);\n}\n\n/**\n * Returns true when an underscore delimiter at position `i` is \"intraword\",\n * meaning the characters on both sides are alphanumeric (e.g. `snake_case`).\n * In that case the underscore should NOT be treated as emphasis per CommonMark.\n */\nfunction isIntraword(text: string, i: number, delimLen: number): boolean {\n const before = i > 0 ? text[i - 1] : undefined;\n const after = i + delimLen < text.length ? text[i + delimLen] : undefined;\n return isAlphanumeric(before) && isAlphanumeric(after);\n}\n\n// ─── Inline Parser ───────────────────────────────────────────────────────────\n\ntype InlineTokenizer = (\n text: string,\n i: number,\n) => { html: string; end: number } | null;\n\nfunction tryBackslashEscape(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (text[i] !== \"\\\\\" || i + 1 >= text.length) {\n return null;\n }\n const next = text[i + 1];\n // Hard line break: backslash at end of line\n if (next === \"\\n\") {\n return { html: \"<br>\\n\", end: i + 2 };\n }\n // Escapable characters\n if (\"\\\\`*_{}[]()#+-.!~|>\".includes(next)) {\n return { html: escapeHtml(next), end: i + 2 };\n }\n return null;\n}\n\nfunction tryInlineCode(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (text[i] !== \"`\") {\n return null;\n }\n return parseInlineCode(text, i);\n}\n\nfunction tryImage(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (text[i] !== \"!\" || text[i + 1] !== \"[\") {\n return null;\n }\n return parseImage(text, i);\n}\n\nfunction tryLink(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (text[i] !== \"[\") {\n return null;\n }\n return parseLink(text, i);\n}\n\nfunction tryStrikethrough(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (text[i] !== \"~\" || text[i + 1] !== \"~\") {\n return null;\n }\n return parseDelimited(text, i, \"~~\", \"<del>\", \"</del>\");\n}\n\nfunction tryBoldItalic(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (\n (text[i] === \"*\" && text[i + 1] === \"*\" && text[i + 2] === \"*\") ||\n (text[i] === \"_\" &&\n text[i + 1] === \"_\" &&\n text[i + 2] === \"_\" &&\n !isIntraword(text, i, 3))\n ) {\n const delimiter = text.substring(i, i + 3);\n return parseDelimited(text, i, delimiter, \"<strong><em>\", \"</em></strong>\");\n }\n return null;\n}\n\nfunction tryBold(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (\n (text[i] === \"*\" && text[i + 1] === \"*\") ||\n (text[i] === \"_\" && text[i + 1] === \"_\" && !isIntraword(text, i, 2))\n ) {\n const delimiter = text.substring(i, i + 2);\n return parseDelimited(text, i, delimiter, \"<strong>\", \"</strong>\");\n }\n return null;\n}\n\nfunction tryItalic(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (text[i] === \"*\" || (text[i] === \"_\" && !isIntraword(text, i, 1))) {\n return parseDelimited(text, i, text[i], \"<em>\", \"</em>\");\n }\n return null;\n}\n\nfunction trySoftBreak(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (text[i] === \"\\n\") {\n return { html: \"<br>\\n\", end: i + 1 };\n }\n return null;\n}\n\n// Inline raw HTML: pass through tags, comments, CDATA, processing\n// instructions, and declarations verbatim so authors can mix HTML into\n// markdown (e.g. `text <em>foo</em> more`). Anything that doesn't match\n// these shapes falls through and gets HTML-escaped as plain text.\nconst INLINE_HTML_TAG_RE =\n /^<\\/?[a-zA-Z][a-zA-Z0-9-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'=<>`]+))?)*\\s*\\/?>/;\nconst HTML_COMMENT_RE = /^<!--[\\s\\S]*?-->/;\nconst HTML_CDATA_RE = /^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/;\nconst HTML_PI_RE = /^<\\?[\\s\\S]*?\\?>/;\nconst HTML_DECL_RE = /^<![A-Za-z][\\s\\S]*?>/;\n\nfunction tryInlineHtml(\n text: string,\n i: number,\n): { html: string; end: number } | null {\n if (text[i] !== \"<\") {\n return null;\n }\n const rest = text.substring(i);\n for (const re of [\n HTML_COMMENT_RE,\n HTML_CDATA_RE,\n HTML_PI_RE,\n HTML_DECL_RE,\n INLINE_HTML_TAG_RE,\n ]) {\n const m = rest.match(re);\n if (m) {\n return { html: m[0], end: i + m[0].length };\n }\n }\n return null;\n}\n\n/** Characters that can start an inline syntax token. */\nconst SPECIAL_CHARS = new Set(\"\\\\`![~*_\\n<\");\n\n/**\n * Ordered array of inline tokenizers, tried in priority order.\n * The first match wins.\n */\nconst inlineTokenizers: InlineTokenizer[] = [\n tryBackslashEscape,\n tryInlineCode,\n tryImage,\n tryLink,\n tryStrikethrough,\n tryBoldItalic, // *** / ___\n tryBold, // ** / __\n tryItalic, // * / _\n tryInlineHtml,\n trySoftBreak,\n];\n\n/**\n * Parse inline markdown syntax and return HTML.\n * Handles: bold, italic, bold+italic, strikethrough, inline code,\n * links, images (with video detection), hard line breaks, backslash escapes.\n */\nfunction parseInline(text: string): string {\n let result = \"\";\n let i = 0;\n\n while (i < text.length) {\n // Hard line break: 2+ trailing spaces immediately before a newline.\n // (The other hard-break form, backslash + newline, is handled by\n // tryBackslashEscape.) Strip the trailing spaces from the accumulated\n // result before emitting the <br>.\n if (\n text[i] === \"\\n\" &&\n i >= 2 &&\n text[i - 1] === \" \" &&\n text[i - 2] === \" \"\n ) {\n result = result.replace(/ +$/, \"\");\n result += \"<br>\\n\";\n i++;\n continue;\n }\n\n // Try each tokenizer in priority order\n let matched = false;\n if (SPECIAL_CHARS.has(text[i])) {\n for (const tokenizer of inlineTokenizers) {\n const r = tokenizer(text, i);\n if (r) {\n result += r.html;\n i = r.end;\n matched = true;\n break;\n }\n }\n }\n\n if (!matched) {\n // Batch consecutive plain-text characters and escape once\n const runStart = i;\n i++;\n while (i < text.length && !SPECIAL_CHARS.has(text[i])) {\n i++;\n }\n result += escapeHtml(text.substring(runStart, i));\n }\n }\n\n return result;\n}\n\nfunction parseInlineCode(\n text: string,\n start: number,\n): { html: string; end: number } | null {\n // Count opening backticks\n let openCount = 0;\n let i = start;\n while (i < text.length && text[i] === \"`\") {\n openCount++;\n i++;\n }\n\n // Find matching closing backticks\n let j = i;\n while (j < text.length) {\n if (text[j] === \"`\") {\n let closeCount = 0;\n const closeStart = j;\n while (j < text.length && text[j] === \"`\") {\n closeCount++;\n j++;\n }\n if (closeCount === openCount) {\n let code = text.substring(i, closeStart);\n // Per CommonMark: line endings inside a code span are converted to\n // single spaces, then if the result starts AND ends with a space and\n // is not all-spaces, one leading + trailing space is stripped (so\n // `` ` `foo` ` `` is `<code>`foo`</code>`).\n code = code.replace(/\\n/g, \" \");\n if (\n code.length >= 2 &&\n code[0] === \" \" &&\n code[code.length - 1] === \" \" &&\n /[^ ]/.test(code)\n ) {\n code = code.substring(1, code.length - 1);\n }\n return {\n html: `<code>${escapeHtml(code)}</code>`,\n end: j,\n };\n }\n } else {\n j++;\n }\n }\n return null;\n}\n\nfunction parseImage(\n text: string,\n start: number,\n): { html: string; end: number } | null {\n //  or \n // Use balanced bracket matching to handle nested/escaped brackets in alt text\n const altEnd = findClosingBracket(text, start + 1);\n if (altEnd === -1) {\n return null;\n }\n const altStart = start + 2; // after ![\n\n if (text[altEnd + 1] !== \"(\") {\n return null;\n }\n\n const urlStart = altEnd + 2;\n const parenEnd = findClosingParen(text, urlStart - 1);\n if (parenEnd === -1) {\n return null;\n }\n\n const alt = text.substring(altStart, altEnd);\n const { url, title } = parseDestinationAndTitle(\n text.substring(urlStart, parenEnd),\n );\n\n if (isVideoUrl(url)) {\n // Use the alt text as the video's display name (falling back to the\n // title) so a video link written with the standard `` form\n // round-trips into BlockNote's video block. Captioned videos go through\n // raw `<figure>` HTML instead, see htmlToMarkdown.serializeMediaFigure.\n const name = alt || title;\n return {\n html: `<video src=\"${escapeHtml(url)}\"${name ? ` data-name=\"${escapeHtml(name)}\"` : \"\"} data-url=\"${escapeHtml(url)}\" controls></video>`,\n end: parenEnd + 1,\n };\n }\n\n const titleAttr = title !== undefined ? ` title=\"${escapeHtml(title)}\"` : \"\";\n return {\n html: `<img src=\"${escapeHtml(url)}\" alt=\"${escapeHtml(alt)}\"${titleAttr}>`,\n end: parenEnd + 1,\n };\n}\n\nfunction parseLink(\n text: string,\n start: number,\n): { html: string; end: number } | null {\n // [text](url)\n const textStart = start + 1;\n const textEnd = findClosingBracket(text, start);\n if (textEnd === -1) {\n return null;\n }\n\n if (text[textEnd + 1] !== \"(\") {\n return null;\n }\n\n const urlStart = textEnd + 2;\n const parenEnd = findClosingParen(text, textEnd + 1);\n if (parenEnd === -1) {\n return null;\n }\n\n const linkText = text.substring(textStart, textEnd);\n const { url, title } = parseDestinationAndTitle(\n text.substring(urlStart, parenEnd),\n );\n\n const titleAttr = title !== undefined ? ` title=\"${escapeHtml(title)}\"` : \"\";\n return {\n html: `<a href=\"${escapeHtml(url)}\"${titleAttr}>${parseInline(linkText)}</a>`,\n end: parenEnd + 1,\n };\n}\n\nfunction findClosingBracket(text: string, openPos: number): number {\n let depth = 0;\n for (let i = openPos; i < text.length; i++) {\n if (text[i] === \"\\\\\" && i + 1 < text.length) {\n i++; // skip escaped\n continue;\n }\n if (text[i] === \"[\") {\n depth++;\n }\n if (text[i] === \"]\") {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n }\n return -1;\n}\n\nfunction findClosingParen(text: string, openPos: number): number {\n let depth = 0;\n for (let i = openPos; i < text.length; i++) {\n if (text[i] === \"\\\\\" && i + 1 < text.length) {\n i++;\n continue;\n }\n if (text[i] === \"(\") {\n depth++;\n }\n if (text[i] === \")\") {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n }\n return -1;\n}\n\n/**\n * Parse the inside of `(...)` from a link/image (the URL and optional title).\n * Handles three URL forms:\n * - bare: `/uri` or `/uri \"title\"`\n * - angle-bracket: `<url>` or `<url> \"title\"` (brackets are stripped)\n * And three title-quote forms: `\"...\"`, `'...'`, `(...)`.\n */\nfunction parseDestinationAndTitle(raw: string): {\n url: string;\n title?: string;\n} {\n raw = raw.trim();\n let url: string;\n let rest: string;\n\n if (raw.startsWith(\"<\")) {\n const close = raw.indexOf(\">\");\n if (close === -1) {\n // Unmatched `<` — treat the whole thing as the URL minus the `<`.\n url = raw.substring(1);\n rest = \"\";\n } else {\n url = raw.substring(1, close);\n rest = raw.substring(close + 1).trim();\n }\n } else {\n // Split at first unescaped whitespace.\n let split = raw.length;\n for (let i = 0; i < raw.length; i++) {\n if (raw[i] === \"\\\\\" && i + 1 < raw.length) {\n i++;\n continue;\n }\n if (raw[i] === \" \" || raw[i] === \"\\t\" || raw[i] === \"\\n\") {\n split = i;\n break;\n }\n }\n url = raw.substring(0, split);\n rest = raw.substring(split).trim();\n }\n\n let title: string | undefined;\n if (rest.length > 0) {\n const titleMatch = rest.match(/^\"([^\"]*)\"$|^'([^']*)'$|^\\(([^)]*)\\)$/);\n if (titleMatch) {\n title = titleMatch[1] ?? titleMatch[2] ?? titleMatch[3];\n }\n }\n\n return { url, title };\n}\n\nfunction parseDelimited(\n text: string,\n start: number,\n delimiter: string,\n openTag: string,\n closeTag: string,\n): { html: string; end: number } | null {\n const len = delimiter.length;\n const afterOpen = start + len;\n\n if (afterOpen >= text.length) {\n return null;\n }\n\n // Opening delimiter must not be followed by whitespace\n if (text[afterOpen] === \" \" || text[afterOpen] === \"\\t\") {\n return null;\n }\n\n // Find closing delimiter\n let j = afterOpen;\n while (j < text.length) {\n // Skip escaped characters\n if (text[j] === \"\\\\\" && j + 1 < text.length) {\n j += 2;\n continue;\n }\n\n if (text.substring(j, j + len) === delimiter) {\n // Closing delimiter must not be preceded by whitespace\n if (text[j - 1] === \" \" || text[j - 1] === \"\\t\") {\n j++;\n continue;\n }\n\n // For single-char delimiters, don't accept closer if it's part of a\n // multi-char run (e.g., don't treat the * in ** as italic closer)\n if (\n len === 1 &&\n ((j > 0 &&\n text[j - 1] === delimiter[0] &&\n !(j >= 2 && text[j - 2] === \"\\\\\")) ||\n (j + len < text.length && text[j + len] === delimiter[0]))\n ) {\n j++;\n continue;\n }\n\n const inner = text.substring(afterOpen, j);\n if (inner.length === 0) {\n j++;\n continue;\n }\n\n return {\n html: openTag + parseInline(inner) + closeTag,\n end: j + len,\n };\n }\n j++;\n }\n\n return null;\n}\n\n// ─── Block-Level Types ───────────────────────────────────────────────────────\n\ninterface BlockToken {\n type: string;\n}\n\ninterface HeadingToken extends BlockToken {\n type: \"heading\";\n level: number;\n content: string;\n}\n\ninterface ParagraphToken extends BlockToken {\n type: \"paragraph\";\n content: string;\n}\n\ninterface CodeBlockToken extends BlockToken {\n type: \"codeBlock\";\n language: string;\n code: string;\n}\n\ninterface BlockquoteToken extends BlockToken {\n type: \"blockquote\";\n content: string;\n}\n\ninterface HorizontalRuleToken extends BlockToken {\n type: \"hr\";\n}\n\ninterface ListItemToken extends BlockToken {\n type: \"listItem\";\n listType: \"bullet\" | \"ordered\" | \"task\";\n indent: number;\n content: string;\n start?: number; // for ordered lists\n checked?: boolean; // for task lists\n childContent?: string; // recursively parsed content within this item\n}\n\ninterface TableToken extends BlockToken {\n type: \"table\";\n headers: string[];\n rows: string[][];\n alignments: (\"left\" | \"center\" | \"right\" | null)[];\n}\n\ninterface RawHtmlToken extends BlockToken {\n type: \"rawHtml\";\n content: string;\n}\n\ntype Token =\n | HeadingToken\n | ParagraphToken\n | CodeBlockToken\n | BlockquoteToken\n | HorizontalRuleToken\n | ListItemToken\n | TableToken\n | RawHtmlToken;\n\n/**\n * HTML block-level tag names (from the CommonMark type-6 list, plus `audio`\n * which BlockNote serializes as raw HTML since markdown has no shorthand\n * for it). When a line starts with `<` followed by one of these tag names,\n * the run of non-blank lines is emitted verbatim as raw HTML rather than\n * wrapped in a paragraph.\n */\nconst HTML_BLOCK_TAGS = new Set([\n \"address\",\n \"article\",\n \"aside\",\n \"audio\",\n \"base\",\n \"basefont\",\n \"blockquote\",\n \"body\",\n \"caption\",\n \"center\",\n \"col\",\n \"colgroup\",\n \"dd\",\n \"details\",\n \"dialog\",\n \"dir\",\n \"div\",\n \"dl\",\n \"dt\",\n \"fieldset\",\n \"figcaption\",\n \"figure\",\n \"footer\",\n \"form\",\n \"frame\",\n \"frameset\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"head\",\n \"header\",\n \"hr\",\n \"html\",\n \"iframe\",\n \"legend\",\n \"li\",\n \"link\",\n \"main\",\n \"menu\",\n \"menuitem\",\n \"nav\",\n \"noframes\",\n \"ol\",\n \"optgroup\",\n \"option\",\n \"p\",\n \"param\",\n \"section\",\n \"source\",\n \"summary\",\n \"table\",\n \"tbody\",\n \"td\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"title\",\n \"tr\",\n \"track\",\n \"ul\",\n]);\n\nfunction isHtmlBlockStart(line: string): boolean {\n // <!-- ..., <?..., <![CDATA[..., <!DOCTYPE, etc.\n if (/^ {0,3}<(!--|\\?|![A-Za-z]|!\\[CDATA\\[)/.test(line)) {\n return true;\n }\n const m = line.match(/^ {0,3}<\\/?([a-zA-Z][a-zA-Z0-9-]*)(?:\\s|\\/?>|$)/);\n if (!m) {\n return false;\n }\n return HTML_BLOCK_TAGS.has(m[1].toLowerCase());\n}\n\n// ─── Block-Level Tokenizer ──────────────────────────────────────────────────\n\nfunction tokenize(markdown: string): Token[] {\n const lines = markdown.split(\"\\n\");\n const tokens: Token[] = [];\n let i = 0;\n let prevLineWasBlank = true; // treat start of document as after blank\n\n while (i < lines.length) {\n const line = lines[i];\n\n // Blank line — skip\n if (line.trim() === \"\") {\n prevLineWasBlank = true;\n i++;\n continue;\n }\n\n // Fenced code block (0-3 leading spaces allowed per CommonMark)\n const fenceMatch = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);\n if (fenceMatch) {\n const fence = fenceMatch[1];\n const fenceChar = fence[0];\n const fenceLen = fence.length;\n const language = fenceMatch[2].trim();\n const codeLines: string[] = [];\n i++;\n while (i < lines.length) {\n const closingMatch = lines[i].match(\n new RegExp(`^ {0,3}${fenceChar}{${fenceLen},}\\\\s*$`),\n );\n if (closingMatch) {\n i++;\n break;\n }\n codeLines.push(lines[i]);\n i++;\n }\n tokens.push({\n type: \"codeBlock\",\n language: language || \"\",\n code: codeLines.join(\"\\n\"),\n });\n prevLineWasBlank = false;\n continue;\n }\n\n // ATX Heading.\n // - Closing `#` sequence requires a preceding space (so `### foo###`\n // keeps the trailing #s as text, while `### foo ###` strips them).\n // - Trailing whitespace is always stripped from the heading content.\n const headingMatch = line.match(/^(#{1,6})\\s+(.+?)(?:\\s+#+\\s*|\\s*)$/);\n if (headingMatch) {\n tokens.push({\n type: \"heading\",\n level: headingMatch[1].length,\n content: headingMatch[2],\n });\n prevLineWasBlank = false;\n i++;\n continue;\n }\n\n // Horizontal rule: ---, ***, ___ (3+ chars, optionally with spaces)\n if (/^(\\s{0,3})([-*_])\\s*(\\2\\s*){2,}$/.test(line)) {\n // Setext H2: --- immediately after a paragraph (no blank line between)\n const prevToken = tokens[tokens.length - 1];\n if (\n !prevLineWasBlank &&\n line.trim().match(/^-+$/) &&\n prevToken &&\n prevToken.type === \"paragraph\"\n ) {\n const para = prevToken as ParagraphToken;\n tokens[tokens.length - 1] = {\n type: \"heading\",\n level: 2,\n content: para.content,\n };\n prevLineWasBlank = false;\n i++;\n continue;\n }\n tokens.push({ type: \"hr\" });\n prevLineWasBlank = false;\n i++;\n continue;\n }\n\n // Setext heading detection: check if next line is === or ---\n if (i + 1 < lines.length) {\n const nextLine = lines[i + 1];\n if (/^={1,}\\s*$/.test(nextLine) && line.trim().length > 0) {\n tokens.push({\n type: \"heading\",\n level: 1,\n content: line.trim(),\n });\n prevLineWasBlank = false;\n i += 2;\n continue;\n }\n // Setext H2 --- handled in HR section above\n }\n\n // Table: detect by looking for separator row\n const tableResult = tryParseTable(lines, i);\n if (tableResult) {\n tokens.push(tableResult.token);\n i = tableResult.nextLine;\n prevLineWasBlank = false;\n continue;\n }\n\n // Blockquote\n if (/^\\s{0,3}>/.test(line)) {\n const quoteLines: string[] = [];\n while (i < lines.length && /^\\s{0,3}>/.test(lines[i])) {\n // Remove the > prefix\n quoteLines.push(lines[i].replace(/^\\s{0,3}>\\s?/, \"\"));\n i++;\n }\n // Lazy continuation: collect non-blank lines that don't start a new\n // block-level element (per CommonMark spec)\n while (i < lines.length) {\n const cur = lines[i];\n if (cur.trim() === \"\") {\n break;\n }\n // Stop on block-level markers\n if (/^\\s{0,3}>/.test(cur)) {\n break;\n } // new blockquote\n if (/^(#{1,6})\\s/.test(cur)) {\n break;\n } // heading\n if (/^(`{3,}|~{3,})/.test(cur)) {\n break;\n } // code fence\n if (/^(\\s{0,3})([-*_])\\s*(\\2\\s*){2,}$/.test(cur)) {\n break;\n } // hr\n if (/^\\s*([-*+]|\\d+[.)])\\s+/.test(cur)) {\n break;\n } // list item\n if (/^\\s*\\|(.+\\|)+\\s*$/.test(cur)) {\n break;\n } // table\n quoteLines.push(cur);\n i++;\n }\n tokens.push({\n type: \"blockquote\",\n content: quoteLines.join(\"\\n\"),\n });\n prevLineWasBlank = false;\n continue;\n }\n\n // List item (bullet, ordered, or task)\n const listItemMatch = line.match(\n /^(\\s*)([-*+]|\\d+[.)])(\\s+)(\\[[ xX]\\] )?(.*)$/,\n );\n if (listItemMatch) {\n const indent = listItemMatch[1].length;\n const marker = listItemMatch[2];\n const markerSpaces = listItemMatch[3];\n const checkbox = listItemMatch[4];\n const firstLineContent = listItemMatch[5];\n\n let listType: \"bullet\" | \"ordered\" | \"task\";\n let start: number | undefined;\n let checked: boolean | undefined;\n\n if (checkbox) {\n listType = \"task\";\n checked = checkbox.trim() !== \"[ ]\";\n } else if (/^\\d+[.)]$/.test(marker)) {\n listType = \"ordered\";\n start = parseInt(marker, 10);\n } else {\n listType = \"bullet\";\n }\n\n // Content indent = column where content actually starts\n const contentIndent =\n indent +\n marker.length +\n markerSpaces.length +\n (checkbox ? checkbox.length : 0);\n\n // Minimum indent for child content: anything indented past the marker\n // (sub-lists can start at indent > marker position)\n const minChildIndent = indent + 1;\n\n // Helper to check if a line belongs to this list item\n const belongsToItem = (lineStr: string): boolean => {\n if (lineStr.trim() === \"\") {\n return true;\n } // blank lines checked separately\n const lineInd = lineStr.match(/^\\s*/)![0].length;\n // Lines at contentIndent are continuation text\n if (lineInd >= contentIndent) {\n return true;\n }\n // Lines between marker and content column that start a sub-list\n if (\n lineInd >= minChildIndent &&\n lineStr.match(/^\\s*([-*+]|\\d+[.)])\\s+/)\n ) {\n return true;\n }\n return false;\n };\n\n // Consume ALL subsequent lines that belong to this list item\n i++;\n const subLines: string[] = [];\n while (i < lines.length) {\n const cur = lines[i];\n\n if (cur.trim() === \"\") {\n // Blank line: include if followed by content that belongs to this item\n let lookAhead = i + 1;\n while (lookAhead < lines.length && lines[lookAhead].trim() === \"\") {\n lookAhead++;\n }\n if (lookAhead < lines.length && belongsToItem(lines[lookAhead])) {\n subLines.push(\"\");\n i++;\n continue;\n }\n break;\n }\n\n if (!belongsToItem(cur)) {\n break;\n }\n\n // Strip indent: for lines at contentIndent+, strip contentIndent chars;\n // for sub-list lines between minChildIndent and contentIndent, strip minChildIndent\n const lineIndent = cur.match(/^\\s*/)![0].length;\n if (lineIndent >= contentIndent) {\n subLines.push(cur.substring(contentIndent));\n } else {\n // Sub-list item between minChildIndent and contentIndent\n subLines.push(cur.substring(minChildIndent));\n }\n i++;\n }\n\n // Build the list item token\n // If there are sub-lines, they become child content (recursively tokenized)\n // Don't trim — preserve relative indentation of sub-lines\n const childContent = subLines.join(\"\\n\").replace(/^\\n+|\\n+$/g, \"\");\n tokens.push({\n type: \"listItem\",\n listType,\n indent,\n content: firstLineContent.trim(),\n start,\n checked,\n childContent: childContent || undefined,\n });\n prevLineWasBlank = false;\n continue;\n }\n\n // Block-level raw HTML: a line starting with `<tag>` (block-level tag),\n // `<!-- ... -->`, `<?...?>`, `<!DOCTYPE ...>`, or `<![CDATA[...]]>`.\n // Lines are emitted verbatim until the next blank line.\n if (isHtmlBlockStart(line)) {\n const htmlLines: string[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n htmlLines.push(lines[i]);\n i++;\n }\n tokens.push({\n type: \"rawHtml\",\n content: htmlLines.join(\"\\n\"),\n });\n prevLineWasBlank = false;\n continue;\n }\n\n // Paragraph (default)\n const paraLines: string[] = [line];\n i++;\n while (i < lines.length) {\n const nextLine = lines[i];\n // Stop paragraph on blank line\n if (nextLine.trim() === \"\") {\n break;\n }\n // Stop on block-level element\n if (/^(#{1,6})\\s/.test(nextLine)) {\n break;\n }\n if (/^(`{3,}|~{3,})/.test(nextLine)) {\n break;\n }\n if (/^\\s{0,3}>/.test(nextLine)) {\n break;\n }\n if (/^(\\s{0,3})([-*_])\\s*(\\2\\s*){2,}$/.test(nextLine)) {\n break;\n }\n if (/^\\s*([-*+]|\\d+[.)])\\s+/.test(nextLine)) {\n break;\n }\n if (/^\\s*\\|(.+\\|)+\\s*$/.test(nextLine)) {\n break;\n }\n if (isHtmlBlockStart(nextLine)) {\n break;\n }\n // Check if next-next line is setext marker\n if (\n i + 1 < lines.length &&\n /^[=-]+\\s*$/.test(lines[i + 1]) &&\n nextLine.trim().length > 0\n ) {\n break;\n }\n paraLines.push(nextLine);\n i++;\n }\n // CommonMark allows up to 3 leading spaces of indent on paragraph lines.\n // Also strip trailing whitespace from the final line so a trailing\n // hard-break sequence (` \\n` at end of paragraph) doesn't leak as\n // literal trailing spaces in the rendered output.\n tokens.push({\n type: \"paragraph\",\n content: paraLines\n .map((l) => l.replace(/^ {1,3}/, \"\"))\n .join(\"\\n\")\n .replace(/[ \\t]+$/, \"\"),\n });\n prevLineWasBlank = false;\n }\n\n return tokens;\n}\n\nfunction tryParseTable(\n lines: string[],\n start: number,\n): { token: TableToken; nextLine: number } | null {\n // A table needs at least a header row and a separator row\n if (start + 1 >= lines.length) {\n return null;\n }\n\n const headerLine = lines[start];\n const separatorLine = lines[start + 1];\n\n // Check separator line format: | --- | --- | or --- | --- (outer pipes optional)\n // Must contain at least one pipe and only dashes, colons, pipes, and whitespace\n if (\n !separatorLine.includes(\"|\") ||\n !/^\\s*\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)*\\|?\\s*$/.test(separatorLine)\n ) {\n return null;\n }\n\n // Check header line has at least one pipe (required to distinguish from plain text)\n if (!headerLine.includes(\"|\")) {\n return null;\n }\n\n const headers = parsePipeCells(headerLine);\n const alignments = parseAlignments(separatorLine);\n\n const rows: string[][] = [];\n let i = start + 2;\n while (i < lines.length) {\n const line = lines[i];\n if (!line.includes(\"|\")) {\n break;\n }\n rows.push(parsePipeCells(line));\n i++;\n }\n\n return {\n token: {\n type: \"table\",\n headers,\n rows,\n alignments,\n },\n nextLine: i,\n };\n}\n\nfunction parsePipeCells(line: string): string[] {\n // Trim leading/trailing pipes and split\n const trimmed = line.trim();\n const withoutOuterPipes = trimmed.startsWith(\"|\")\n ? trimmed.substring(1)\n : trimmed;\n const content = withoutOuterPipes.endsWith(\"|\")\n ? withoutOuterPipes.substring(0, withoutOuterPipes.length - 1)\n : withoutOuterPipes;\n\n // Split by pipes, handling escaped pipes\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < content.length; i++) {\n if (\n content[i] === \"\\\\\" &&\n i + 1 < content.length &&\n content[i + 1] === \"|\"\n ) {\n current += \"|\";\n i++;\n } else if (content[i] === \"|\") {\n cells.push(current.trim());\n current = \"\";\n } else {\n current += content[i];\n }\n }\n cells.push(current.trim());\n\n return cells;\n}\n\nfunction parseAlignments(\n separatorLine: string,\n): (\"left\" | \"center\" | \"right\" | null)[] {\n const cells = parsePipeCells(separatorLine);\n return cells.map((cell) => {\n const trimmed = cell.trim();\n const left = trimmed.startsWith(\":\");\n const right = trimmed.endsWith(\":\");\n if (left && right) {\n return \"center\";\n }\n if (right) {\n return \"right\";\n }\n if (left) {\n return \"left\";\n }\n return null;\n });\n}\n\n// ─── HTML Emitter ────────────────────────────────────────────────────────────\n\nfunction tokensToHtml(tokens: Token[]): string {\n let html = \"\";\n let i = 0;\n\n while (i < tokens.length) {\n const token = tokens[i];\n\n switch (token.type) {\n case \"heading\": {\n const t = token as HeadingToken;\n html += `<h${t.level}>${parseInline(t.content)}</h${t.level}>`;\n i++;\n break;\n }\n\n case \"paragraph\": {\n const t = token as ParagraphToken;\n html += `<p>${parseInline(t.content)}</p>`;\n i++;\n break;\n }\n\n case \"codeBlock\": {\n const t = token as CodeBlockToken;\n const langAttr = t.language\n ? ` data-language=\"${escapeHtml(t.language)}\"`\n : \"\";\n html += `<pre><code${langAttr}>${escapeHtml(t.code)}</code></pre>`;\n i++;\n break;\n }\n\n case \"blockquote\": {\n const t = token as BlockquoteToken;\n // Recursively parse blockquote content as markdown\n const innerTokens = tokenize(t.content);\n const innerHtml = tokensToHtml(innerTokens);\n html += `<blockquote>${innerHtml}</blockquote>`;\n i++;\n break;\n }\n\n case \"hr\":\n html += `<hr>`;\n i++;\n break;\n\n case \"listItem\": {\n // Collect consecutive list items and build nested list structure\n const listHtml = emitListItems(tokens, i);\n html += listHtml.html;\n i = listHtml.nextIndex;\n break;\n }\n\n case \"table\": {\n const t = token as TableToken;\n html += emitTable(t);\n i++;\n break;\n }\n\n case \"rawHtml\": {\n const t = token as RawHtmlToken;\n html += t.content;\n i++;\n break;\n }\n\n default:\n i++;\n }\n }\n\n return html;\n}\n\nfunction emitListItems(\n tokens: Token[],\n startIdx: number,\n): { html: string; nextIndex: number } {\n let html = \"\";\n let i = startIdx;\n let currentListType: \"bullet\" | \"ordered\" | null = null;\n\n while (i < tokens.length && tokens[i].type === \"listItem\") {\n const item = tokens[i] as ListItemToken;\n const effectiveType = getEffectiveListType(item.listType);\n\n // Check if we need to switch list type\n if (currentListType !== null && currentListType !== effectiveType) {\n // Close current list, open new one\n html += `</${currentListType === \"ordered\" ? \"ol\" : \"ul\"}>`;\n currentListType = null;\n }\n\n // Open list if needed\n if (currentListType === null) {\n if (effectiveType === \"ordered\") {\n const startAttr =\n item.start !== undefined && item.start !== 1\n ? ` start=\"${item.start}\"`\n : \"\";\n html += `<ol${startAttr}>`;\n } else {\n html += `<ul>`;\n }\n currentListType = effectiveType;\n }\n\n // Emit list item\n if (item.listType === \"task\") {\n const checkedAttr = item.checked ? \" checked\" : \"\";\n html += `<li><input type=\"checkbox\" disabled${checkedAttr}><p>${parseInline(item.content)}</p>`;\n } else {\n html += `<li><p>${parseInline(item.content)}</p>`;\n }\n\n // Render child content (nested items, continuation paragraphs, etc.)\n if (item.childContent) {\n const childTokens = tokenize(item.childContent);\n html += tokensToHtml(childTokens);\n }\n\n html += `</li>`;\n i++;\n }\n\n // Close the list\n if (currentListType !== null) {\n html += `</${currentListType === \"ordered\" ? \"ol\" : \"ul\"}>`;\n }\n\n return { html, nextIndex: i };\n}\n\nfunction getEffectiveListType(\n listType: \"bullet\" | \"ordered\" | \"task\",\n): \"bullet\" | \"ordered\" {\n return listType === \"ordered\" ? \"ordered\" : \"bullet\";\n}\n\nfunction emitTable(table: TableToken): string {\n let html = \"<table>\";\n\n // BlockNote tables have no required header row, but the markdown table\n // syntax does. When we serialize a headerless BlockNote table to markdown\n // we emit an empty header row; on re-parse, treat that empty header as\n // \"no header\" so the round-trip is stable (issue #739).\n const headerIsEmpty = table.headers.every((h) => h.trim() === \"\");\n const colCount = table.headers.length;\n\n if (!headerIsEmpty) {\n html += \"<thead><tr>\";\n for (let c = 0; c < colCount; c++) {\n const align = table.alignments[c];\n const alignAttr = align ? ` align=\"${align}\"` : \"\";\n html += `<th${alignAttr}>${parseInline(table.headers[c])}</th>`;\n }\n html += \"</tr></thead>\";\n }\n\n if (table.rows.length > 0) {\n html += \"<tbody>\";\n for (const row of table.rows) {\n html += \"<tr>\";\n for (let c = 0; c < colCount; c++) {\n const cell = c < row.length ? row[c] : \"\";\n const align = table.alignments[c];\n const alignAttr = align ? ` align=\"${align}\"` : \"\";\n html += `<td${alignAttr}>${parseInline(cell)}</td>`;\n }\n html += \"</tr>\";\n }\n html += \"</tbody>\";\n }\n\n html += \"</table>\";\n return html;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Convert a markdown string to an HTML string.\n * This is a direct replacement for the unified/remark/rehype pipeline.\n */\nexport function markdownToHtml(markdown: string): string {\n const tokens = tokenize(markdown);\n return tokensToHtml(tokens);\n}\n","import { Schema } from \"prosemirror-model\";\n\nimport { Block } from \"../../../blocks/defaultBlocks.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { HTMLToBlocks } from \"../html/parseHTML.js\";\nimport { markdownToHtml } from \"./markdownToHtml.js\";\n\nexport function markdownToHTML(markdown: string): string {\n return markdownToHtml(markdown);\n}\n\nexport function markdownToBlocks<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(markdown: string, pmSchema: Schema): Block<BSchema, I, S>[] {\n const htmlString = markdownToHTML(markdown);\n\n return HTMLToBlocks(htmlString, pmSchema);\n}\n","import { createExternalHTMLExporter } from \"../../api/exporters/html/externalHTMLExporter.js\";\nimport { createInternalHTMLSerializer } from \"../../api/exporters/html/internalHTMLSerializer.js\";\nimport { blocksToMarkdown } from \"../../api/exporters/markdown/markdownExporter.js\";\nimport { HTMLToBlocks } from \"../../api/parsers/html/parseHTML.js\";\nimport {\n markdownToBlocks,\n markdownToHTML,\n} from \"../../api/parsers/markdown/parseMarkdown.js\";\nimport {\n Block,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n PartialBlock,\n} from \"../../blocks/defaultBlocks.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../schema/index.js\";\nimport { BlockNoteEditor } from \"../BlockNoteEditor.js\";\n\nexport class ExportManager<\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 * Exports blocks into a simplified HTML string. To better conform to HTML standards, children of blocks which aren't list\n * items are un-nested in the output HTML.\n *\n * @param blocks An array of blocks that should be serialized into HTML.\n * @returns The blocks, serialized as an HTML string.\n */\n public blocksToHTMLLossy(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.editor.document,\n ): string {\n const exporter = createExternalHTMLExporter(\n this.editor.pmSchema,\n this.editor,\n );\n return exporter.exportBlocks(blocks, {});\n }\n\n /**\n * Serializes blocks into an HTML string in the format that would normally be rendered by the editor.\n *\n * Use this method if you want to server-side render HTML (for example, a blog post that has been edited in BlockNote)\n * and serve it to users without loading the editor on the client (i.e.: displaying the blog post)\n *\n * @param blocks An array of blocks that should be serialized into HTML.\n * @returns The blocks, serialized as an HTML string.\n */\n public blocksToFullHTML(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.editor.document,\n ): string {\n const exporter = createInternalHTMLSerializer(\n this.editor.pmSchema,\n this.editor,\n );\n return exporter.serializeBlocks(blocks, {});\n }\n\n /**\n * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and\n * `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote\n * doesn't recognize an HTML element's tag, it will parse it as a paragraph or plain text.\n * @param html The HTML string to parse blocks from.\n * @returns The blocks parsed from the HTML string.\n */\n public tryParseHTMLToBlocks(\n html: string,\n ): Block<BSchema, ISchema, SSchema>[] {\n return HTMLToBlocks(html, this.editor.pmSchema);\n }\n\n /**\n * Serializes blocks into a Markdown string. The output is simplified as Markdown does not support all features of\n * BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed.\n * @param blocks An array of blocks that should be serialized into Markdown.\n * @returns The blocks, serialized as a Markdown string.\n */\n public blocksToMarkdownLossy(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.editor.document,\n ): string {\n return blocksToMarkdown(blocks, this.editor.pmSchema, this.editor, {});\n }\n\n /**\n * Creates a list of blocks from a Markdown string. Tries to create `Block` and `InlineNode` objects based on\n * Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it\n * as text.\n * @param markdown The Markdown string to parse blocks from.\n * @returns The blocks parsed from the Markdown string.\n */\n public tryParseMarkdownToBlocks(\n markdown: string,\n ): Block<BSchema, ISchema, SSchema>[] {\n return markdownToBlocks(markdown, this.editor.pmSchema);\n }\n\n /**\n * Paste HTML into the editor. Defaults to converting HTML to BlockNote HTML.\n * @param html The HTML to paste.\n * @param raw Whether to paste the HTML as is, or to convert it to BlockNote HTML.\n */\n public pasteHTML(html: string, raw = false) {\n let htmlToPaste = html;\n if (!raw) {\n const blocks = this.tryParseHTMLToBlocks(html);\n htmlToPaste = this.blocksToFullHTML(blocks);\n }\n if (!htmlToPaste) {\n return;\n }\n this.editor.prosemirrorView?.pasteHTML(htmlToPaste);\n }\n\n /**\n * Paste text into the editor. Defaults to interpreting text as markdown.\n * @param text The text to paste.\n */\n public pasteText(text: string) {\n return this.editor.prosemirrorView?.pasteText(text);\n }\n\n /**\n * Paste markdown into the editor.\n * @param markdown The markdown to paste.\n */\n public pasteMarkdown(markdown: string) {\n const html = markdownToHTML(markdown);\n return this.pasteHTML(html);\n }\n}\n","import type { Node } from \"prosemirror-model\";\nimport {\n NodeSelection,\n TextSelection,\n type Transaction,\n} from \"prosemirror-state\";\nimport type { TextCursorPosition } from \"../../../editor/cursorPositionTypes.js\";\nimport type {\n BlockIdentifier,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { UnreachableCaseError } from \"../../../util/typescript.js\";\nimport {\n getBlockInfo,\n getBlockInfoFromSelection,\n getNodeId,\n} from \"../../getBlockInfoFromPos.js\";\nimport { nodeToBlock } from \"../../nodeConversions/nodeToBlock.js\";\nimport { getNodeById } from \"../../nodeUtil.js\";\nimport { getBlockNoteSchema, getPmSchema } from \"../../pmUtil.js\";\n\nexport function getTextCursorPosition<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(tr: Transaction): TextCursorPosition<BSchema, I, S> {\n const { bnBlock } = getBlockInfoFromSelection(tr);\n\n const resolvedPos = tr.doc.resolve(bnBlock.beforePos);\n // Gets previous blockContainer node at the same nesting level, if the current node isn't the first child.\n const prevNode = resolvedPos.nodeBefore;\n\n // Gets next blockContainer node at the same nesting level, if the current node isn't the last child.\n const nextNode = tr.doc.resolve(bnBlock.afterPos).nodeAfter;\n\n // Gets parent blockContainer node, if the current node is nested.\n let parentNode: Node | undefined = undefined;\n if (resolvedPos.depth > 1) {\n // for nodes nested in bnBlocks\n parentNode = resolvedPos.node();\n if (!parentNode.type.isInGroup(\"bnBlock\")) {\n // for blockGroups, we need to go one level up\n parentNode = resolvedPos.node(resolvedPos.depth - 1);\n }\n }\n\n return {\n block: nodeToBlock(bnBlock.node, tr.doc),\n prevBlock: prevNode === null ? undefined : nodeToBlock(prevNode, tr.doc),\n nextBlock: nextNode === null ? undefined : nodeToBlock(nextNode, tr.doc),\n parentBlock:\n parentNode === undefined ? undefined : nodeToBlock(parentNode, tr.doc),\n };\n}\n\nexport function setTextCursorPosition(\n tr: Transaction,\n targetBlock: BlockIdentifier,\n placement: \"start\" | \"end\" = \"start\",\n) {\n const id = typeof targetBlock === \"string\" ? targetBlock : targetBlock.id;\n const pmSchema = getPmSchema(tr.doc);\n const schema = getBlockNoteSchema(pmSchema);\n\n const posInfo = getNodeById(id, tr.doc);\n if (!posInfo) {\n throw new Error(`Block with ID ${id} not found`);\n }\n\n const info = getBlockInfo(posInfo);\n\n const contentType: \"none\" | \"inline\" | \"table\" | \"plain\" =\n schema.blockSchema[info.blockNoteType]!.content;\n\n if (info.isBlockContainer) {\n const blockContent = info.blockContent;\n if (contentType === \"none\") {\n tr.setSelection(NodeSelection.create(tr.doc, blockContent.beforePos));\n return;\n }\n\n if (contentType === \"inline\" || contentType === \"plain\") {\n if (placement === \"start\") {\n tr.setSelection(\n TextSelection.create(tr.doc, blockContent.beforePos + 1),\n );\n } else {\n tr.setSelection(\n TextSelection.create(tr.doc, blockContent.afterPos - 1),\n );\n }\n } else if (contentType === \"table\") {\n if (placement === \"start\") {\n // Need to offset the position as we have to get through the `tableRow`\n // and `tableCell` nodes to get to the `tableParagraph` node we want to\n // set the selection in.\n tr.setSelection(\n TextSelection.create(tr.doc, blockContent.beforePos + 4),\n );\n } else {\n tr.setSelection(\n TextSelection.create(tr.doc, blockContent.afterPos - 4),\n );\n }\n } else {\n throw new UnreachableCaseError(contentType);\n }\n } else {\n const child =\n placement === \"start\"\n ? info.childContainer.node.firstChild!\n : info.childContainer.node.lastChild!;\n\n setTextCursorPosition(tr, getNodeId(child, tr.doc), placement);\n }\n}\n","export const acceptedMIMETypes = [\n \"vscode-editor-data\",\n \"blocknote/html\",\n \"text/markdown\",\n \"text/html\",\n \"text/plain\",\n \"Files\",\n] as const;\n","import { Block, PartialBlock } from \"../../../blocks/defaultBlocks.js\";\nimport type { BlockNoteEditor } from \"../../../editor/BlockNoteEditor\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { getBlockInfoAtNearest, getNodeId } from \"../../getBlockInfoFromPos.js\";\nimport { acceptedMIMETypes } from \"./acceptedMIMETypes.js\";\n\nfunction checkFileExtensionsMatch(\n fileExtension1: string,\n fileExtension2: string,\n) {\n if (!fileExtension1.startsWith(\".\") || !fileExtension2.startsWith(\".\")) {\n throw new Error(`The strings provided are not valid file extensions.`);\n }\n\n return fileExtension1 === fileExtension2;\n}\n\nfunction checkMIMETypesMatch(mimeType1: string, mimeType2: string) {\n const types1 = mimeType1.split(\"/\");\n const types2 = mimeType2.split(\"/\");\n\n if (types1.length !== 2) {\n throw new Error(`The string ${mimeType1} is not a valid MIME type.`);\n }\n if (types2.length !== 2) {\n throw new Error(`The string ${mimeType2} is not a valid MIME type.`);\n }\n\n if (types1[1] === \"*\" || types2[1] === \"*\") {\n return types1[0] === types2[0];\n }\n if (types1[0] === \"*\" || types2[0] === \"*\") {\n return types1[1] === types2[1];\n }\n\n return types1[0] === types2[0] && types1[1] === types2[1];\n}\n\nfunction insertOrUpdateBlock<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, I, S>,\n referenceBlock: Block<BSchema, I, S>,\n newBlock: PartialBlock<BSchema, I, S>,\n placement: \"before\" | \"after\" = \"after\",\n) {\n let insertedBlockId: string | undefined;\n\n if (\n Array.isArray(referenceBlock.content) &&\n referenceBlock.content.length === 0\n ) {\n insertedBlockId = editor.updateBlock(referenceBlock, newBlock).id;\n } else {\n insertedBlockId = editor.insertBlocks(\n [newBlock],\n referenceBlock,\n placement,\n )[0].id;\n }\n\n return insertedBlockId;\n}\n\nexport async function handleFileInsertion<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(event: DragEvent | ClipboardEvent, editor: BlockNoteEditor<BSchema, I, S>) {\n if (!editor.uploadFile) {\n // eslint-disable-next-line no-console\n console.warn(\n \"Attempted ot insert file, but uploadFile is not set in the BlockNote editor options\",\n );\n return;\n }\n\n const dataTransfer =\n \"dataTransfer\" in event ? event.dataTransfer : event.clipboardData;\n if (dataTransfer === null) {\n return;\n }\n\n let format: (typeof acceptedMIMETypes)[number] | null = null;\n for (const mimeType of acceptedMIMETypes) {\n if (dataTransfer.types.includes(mimeType)) {\n format = mimeType;\n break;\n }\n }\n if (format !== \"Files\") {\n return;\n }\n\n const items = dataTransfer.items;\n if (!items) {\n return;\n }\n\n event.preventDefault();\n\n for (let i = 0; i < items.length; i++) {\n // Gets file block corresponding to MIME type.\n let fileBlockType = \"file\";\n for (const blockSpec of Object.values(editor.schema.blockSpecs)) {\n for (const mimeType of blockSpec.implementation.meta?.fileBlockAccept ||\n []) {\n const isFileExtension = mimeType.startsWith(\".\");\n const file = items[i].getAsFile();\n\n if (file) {\n if (\n (!isFileExtension &&\n file.type &&\n checkMIMETypesMatch(items[i].type, mimeType)) ||\n (isFileExtension &&\n checkFileExtensionsMatch(\n \".\" + file.name.split(\".\").pop(),\n mimeType,\n ))\n ) {\n fileBlockType = blockSpec.config.type;\n break;\n }\n }\n }\n }\n\n const file = items[i].getAsFile();\n if (file) {\n const fileBlock = {\n type: fileBlockType,\n props: {\n name: file.name,\n },\n } as PartialBlock<BSchema, I, S>;\n\n let insertedBlockId: string | undefined = undefined;\n\n if (event.type === \"paste\") {\n const currentBlock = editor.getTextCursorPosition().block;\n insertedBlockId = insertOrUpdateBlock(editor, currentBlock, fileBlock);\n } else if (event.type === \"drop\") {\n const coords = {\n left: (event as DragEvent).clientX,\n top: (event as DragEvent).clientY,\n };\n\n const pos = editor.prosemirrorView.posAtCoords(coords);\n\n if (!pos) {\n return;\n }\n\n insertedBlockId = editor.transact((tr) => {\n const blockInfo = getBlockInfoAtNearest(tr, pos.pos);\n const id = getNodeId(blockInfo.bnBlock.node, tr.doc);\n // TODO technically data-id will always be the non-rewritten id, so there might be multiple in the document.\n // getNodeId might find the wrong one (aka point to a deleted node when it should be a non-deleted on)\n // This is acceptable right now, given that we don't expect edits on the document content\n const blockElement = editor.domElement?.querySelector(\n `[data-id=\"${id}\"]`,\n );\n\n const blockRect = blockElement?.getBoundingClientRect();\n\n const existingBlock = editor.getBlock(id);\n if (!existingBlock) {\n return;\n }\n\n return insertOrUpdateBlock(\n editor,\n existingBlock,\n fileBlock,\n blockRect && (blockRect.top + blockRect.bottom) / 2 > coords.top\n ? \"before\"\n : \"after\",\n );\n });\n } else {\n return;\n }\n\n if (!insertedBlockId) {\n return;\n }\n\n const updateData = await editor.uploadFile(file, insertedBlockId);\n\n const updatedFileBlock =\n typeof updateData === \"string\"\n ? ({\n props: {\n url: updateData,\n },\n } as PartialBlock<BSchema, I, S>)\n : { ...updateData };\n\n editor.updateBlock(insertedBlockId, updatedFileBlock);\n }\n }\n}\n","import { Extension } from \"@tiptap/core\";\nimport { Plugin } from \"prosemirror-state\";\n\nimport type { BlockNoteEditor } from \"../../../editor/BlockNoteEditor.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { acceptedMIMETypes } from \"./acceptedMIMETypes.js\";\nimport { handleFileInsertion } from \"./handleFileInsertion.js\";\n\nexport const createDropFileExtension = <\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, I, S>,\n) =>\n Extension.create<{ editor: BlockNoteEditor<BSchema, I, S> }, undefined>({\n name: \"dropFile\",\n addProseMirrorPlugins() {\n return [\n new Plugin({\n props: {\n handleDOMEvents: {\n drop(_view, event) {\n if (!editor.isEditable) {\n return;\n }\n\n let format: (typeof acceptedMIMETypes)[number] | null = null;\n for (const mimeType of acceptedMIMETypes) {\n if (event.dataTransfer!.types.includes(mimeType)) {\n format = mimeType;\n break;\n }\n }\n if (format === null) {\n return true;\n }\n\n if (format === \"Files\") {\n void handleFileInsertion(event, editor);\n return true;\n }\n\n return false;\n },\n },\n },\n }),\n ];\n },\n });\n","// Headings H1-H6.\nconst h1 = /(^|\\n) {0,3}#{1,6} {1,8}[^\\n]{1,64}\\r?\\n\\r?\\n\\s{0,32}\\S/;\n\n// Bold, italic, underline, strikethrough, highlight.\nconst bold =\n /(_|__|\\*|\\*\\*|~~|==|\\+\\+)(?!\\s)(?:[^\\s](?:.{0,62}[^\\s])?|\\S)(?=\\1)/;\n\n// Basic inline link (also captures images).\nconst link = /\\[[^\\]]{1,128}\\]\\(https?:\\/\\/\\S{1,999}\\)/;\n\n// Inline code.\nconst code = /(?:\\s|^)`(?!\\s)(?:[^\\s`](?:[^`]{0,46}[^\\s`])?|[^\\s`])`([^\\w]|$)/;\n\n// Unordered list.\nconst ul = /(?:^|\\n)\\s{0,5}-\\s{1}[^\\n]+\\n\\s{0,15}-\\s/;\n\n// Ordered list.\nconst ol = /(?:^|\\n)\\s{0,5}\\d+\\.\\s{1}[^\\n]+\\n\\s{0,15}\\d+\\.\\s/;\n\n// Horizontal rule.\nconst hr = /\\n{2} {0,3}-{2,48}\\n{2}/;\n\n// Fenced code block.\nconst fences =\n /(?:\\n|^)(```|~~~|\\$\\$)(?!`|~)[^\\s]{0,64} {0,64}[^\\n]{0,64}\\n[\\s\\S]{0,9999}?\\s*\\1 {0,64}(?:\\n+|$)/;\n\n// Classical underlined H1 and H2 headings.\nconst title = /(?:\\n|^)(?!\\s)\\w[^\\n]{0,64}\\r?\\n(-|=)\\1{0,64}\\n\\n\\s{0,64}(\\w|$)/;\n\n// Blockquote.\nconst blockquote =\n /(?:^|(\\r?\\n\\r?\\n))( {0,3}>[^\\n]{1,333}\\n){1,999}($|(\\r?\\n))/;\n\n// Table Header\nconst tableHeader = /^\\s*\\|(.+\\|)+\\s*$/m;\n\n// Table Divider\nconst tableDivider = /^\\s*\\|(\\s*[-:]+[-:]\\s*\\|)+\\s*$/m;\n\n// Table Row\nconst tableRow = /^\\s*\\|(.+\\|)+\\s*$/m;\n\n/**\n * Returns `true` if the source text might be a markdown document.\n *\n * @param src Source text to analyze.\n */\nexport const isMarkdown = (src: string): boolean =>\n h1.test(src) ||\n bold.test(src) ||\n link.test(src) ||\n code.test(src) ||\n ul.test(src) ||\n ol.test(src) ||\n hr.test(src) ||\n fences.test(src) ||\n title.test(src) ||\n blockquote.test(src) ||\n tableHeader.test(src) ||\n tableDivider.test(src) ||\n tableRow.test(src);\n","import { EditorView } from \"prosemirror-view\";\n\nexport function handleVSCodePaste(event: ClipboardEvent, view: EditorView) {\n const { schema } = view.state;\n\n if (!event.clipboardData) {\n return false;\n }\n\n const text = event.clipboardData!.getData(\"text/plain\");\n\n if (!text) {\n return false;\n }\n\n if (!schema.nodes.codeBlock) {\n return false;\n }\n\n const vscode = event.clipboardData!.getData(\"vscode-editor-data\");\n const vscodeData = vscode ? JSON.parse(vscode) : undefined;\n const language = vscodeData?.mode;\n\n if (!language) {\n return false;\n }\n\n // strip carriage return chars from text pasted as code\n // see: https://github.com/ProseMirror/prosemirror-view/commit/a50a6bcceb4ce52ac8fcc6162488d8875613aacd\n view.pasteHTML(\n `<pre><code class=\"language-${language}\">${text.replace(\n /\\r\\n?/g,\n \"\\n\",\n )}</code></pre>`,\n );\n\n return true;\n}\n","import { Extension } from \"@tiptap/core\";\nimport { Plugin } from \"prosemirror-state\";\n\nimport type {\n BlockNoteEditor,\n BlockNoteEditorOptions,\n} from \"../../../editor/BlockNoteEditor\";\nimport { isMarkdown } from \"../../parsers/markdown/detectMarkdown.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { acceptedMIMETypes } from \"./acceptedMIMETypes.js\";\nimport { handleFileInsertion } from \"./handleFileInsertion.js\";\nimport { handleVSCodePaste } from \"./handleVSCodePaste.js\";\n\nfunction defaultPasteHandler({\n event,\n editor,\n prioritizeMarkdownOverHTML,\n plainTextAsMarkdown,\n}: {\n event: ClipboardEvent;\n editor: BlockNoteEditor<any, any, any>;\n prioritizeMarkdownOverHTML: boolean;\n plainTextAsMarkdown: boolean;\n}) {\n // Special case for code blocks, as they do not support any rich text\n // formatting, so we force pasting plain text.\n const isInCodeBlock = editor.transact(\n (tr) =>\n tr.selection.$from.parent.type.spec.code &&\n tr.selection.$to.parent.type.spec.code,\n );\n\n if (isInCodeBlock) {\n const data = event.clipboardData?.getData(\"text/plain\");\n\n if (data) {\n editor.pasteText(data);\n\n return true;\n }\n }\n\n let format: (typeof acceptedMIMETypes)[number] | undefined;\n for (const mimeType of acceptedMIMETypes) {\n if (event.clipboardData!.types.includes(mimeType)) {\n format = mimeType;\n break;\n }\n }\n\n if (!format) {\n return true;\n }\n\n if (format === \"vscode-editor-data\") {\n // If VSCode clipboard data cannot be parsed as a code block, try parsing\n // `text/plain` as a fallback.\n if (handleVSCodePaste(event, editor.prosemirrorView)) {\n return true;\n }\n\n format = \"text/plain\";\n }\n\n if (format === \"Files\") {\n void handleFileInsertion(event, editor);\n return true;\n }\n\n const data = event.clipboardData!.getData(format);\n\n if (format === \"blocknote/html\") {\n // Is blocknote/html, so no need to convert it\n editor.pasteHTML(data, true);\n return true;\n }\n\n if (format === \"text/markdown\") {\n editor.pasteMarkdown(data);\n return true;\n }\n\n if (prioritizeMarkdownOverHTML) {\n // Use plain text instead of HTML if it looks like Markdown\n const plainText = event.clipboardData!.getData(\"text/plain\");\n\n if (isMarkdown(plainText)) {\n editor.pasteMarkdown(plainText);\n return true;\n }\n }\n\n if (format === \"text/html\") {\n editor.pasteHTML(data);\n return true;\n }\n\n if (plainTextAsMarkdown) {\n editor.pasteMarkdown(data);\n return true;\n }\n\n editor.pasteText(data);\n return true;\n}\n\nexport const createPasteFromClipboardExtension = <\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, I, S>,\n pasteHandler: Exclude<\n BlockNoteEditorOptions<any, any, any>[\"pasteHandler\"],\n undefined\n >,\n) =>\n Extension.create({\n name: \"pasteFromClipboard\",\n addProseMirrorPlugins() {\n return [\n new Plugin({\n props: {\n handleDOMEvents: {\n paste(_view, event) {\n event.preventDefault();\n\n if (!editor.isEditable) {\n return;\n }\n\n return pasteHandler({\n event,\n editor,\n defaultPasteHandler: ({\n prioritizeMarkdownOverHTML = true,\n plainTextAsMarkdown = true,\n } = {}) => {\n return defaultPasteHandler({\n event,\n editor,\n prioritizeMarkdownOverHTML,\n plainTextAsMarkdown,\n });\n },\n });\n },\n },\n },\n }),\n ];\n },\n });\n","import { Extension } from \"@tiptap/core\";\nimport { Fragment, Node } from \"prosemirror-model\";\nimport { NodeSelection, Plugin } from \"prosemirror-state\";\nimport { CellSelection } from \"prosemirror-tables\";\nimport type { EditorView } from \"prosemirror-view\";\n\nimport type { BlockNoteEditor } from \"../../../editor/BlockNoteEditor.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { createExternalHTMLExporter } from \"../../exporters/html/externalHTMLExporter.js\";\nimport { cleanHTMLToMarkdown } from \"../../exporters/markdown/markdownExporter.js\";\nimport { fragmentToBlocks } from \"../../nodeConversions/fragmentToBlocks.js\";\nimport {\n contentNodeToInlineContent,\n contentNodeToTableContent,\n} from \"../../nodeConversions/nodeToBlock.js\";\n\nfunction fragmentToExternalHTML<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n view: EditorView,\n selectedFragment: Fragment,\n editor: BlockNoteEditor<BSchema, I, S>,\n) {\n let isWithinBlockContent = false;\n const isWithinTable = view.state.selection instanceof CellSelection;\n\n if (!isWithinTable) {\n // Checks whether block ancestry should be included when creating external\n // HTML. If the selection is within a block content node, the block ancestry\n // is excluded as we only care about the inline content.\n const fragmentWithoutParents = view.state.doc.slice(\n view.state.selection.from,\n view.state.selection.to,\n false,\n ).content;\n\n const children = [];\n for (let i = 0; i < fragmentWithoutParents.childCount; i++) {\n children.push(fragmentWithoutParents.child(i));\n }\n\n isWithinBlockContent =\n children.find(\n (child) =>\n child.type.isInGroup(\"bnBlock\") ||\n child.type.name === \"blockGroup\" ||\n child.type.spec.group === \"blockContent\",\n ) === undefined;\n if (isWithinBlockContent) {\n selectedFragment = fragmentWithoutParents;\n }\n }\n\n let externalHTML: string;\n\n const externalHTMLExporter = createExternalHTMLExporter(\n view.state.schema,\n editor,\n );\n\n if (isWithinTable) {\n if (selectedFragment.firstChild?.type.name === \"table\") {\n // contentNodeToTableContent expects the fragment of the content of a table, not the table node itself\n // but cellselection.content() returns the table node itself if all cells and columns are selected\n selectedFragment = selectedFragment.firstChild.content;\n }\n\n // first convert selection to blocknote-style table content, and then\n // pass this to the exporter\n const ic = contentNodeToTableContent(\n selectedFragment as any,\n editor.schema.inlineContentSchema,\n editor.schema.styleSchema,\n );\n\n // Wrap in table to ensure correct parsing by spreadsheet applications\n externalHTML = `<table>${externalHTMLExporter.exportInlineContent(\n ic as any,\n {},\n )}</table>`;\n } else if (isWithinBlockContent) {\n // first convert selection to blocknote-style inline content, and then\n // pass this to the exporter\n const ic = contentNodeToInlineContent(\n selectedFragment as any,\n editor.schema.inlineContentSchema,\n editor.schema.styleSchema,\n );\n externalHTML = externalHTMLExporter.exportInlineContent(ic, {});\n } else {\n const blocks = fragmentToBlocks<BSchema, I, S>(selectedFragment);\n externalHTML = externalHTMLExporter.exportBlocks(blocks, {});\n }\n return externalHTML;\n}\n\nexport function selectedFragmentToHTML<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n view: EditorView,\n editor: BlockNoteEditor<BSchema, I, S>,\n): {\n clipboardHTML: string;\n externalHTML: string;\n markdown: string;\n} {\n // Checks if a `blockContent` node is being copied and expands\n // the selection to the parent `blockContainer` node. This is\n // for the use-case in which only a block without content is\n // selected, e.g. an image block.\n if (\n \"node\" in view.state.selection &&\n (view.state.selection.node as Node).type.spec.group === \"blockContent\"\n ) {\n editor.transact((tr) =>\n tr.setSelection(\n new NodeSelection(tr.doc.resolve(view.state.selection.from - 1)),\n ),\n );\n }\n\n // Uses default ProseMirror clipboard serialization.\n const clipboardHTML: string = view.serializeForClipboard(\n view.state.selection.content(),\n ).dom.innerHTML;\n\n const selectedFragment = view.state.selection.content().content;\n\n const externalHTML = fragmentToExternalHTML<BSchema, I, S>(\n view,\n selectedFragment,\n editor,\n );\n\n // Code blocks are treated differently for copying: text/plain is the raw\n // selected text instead of markdown.\n const { $from, $to } = view.state.selection;\n const parentBlockType = $from.parent.type.name;\n const parentBlockSpec = editor.blockImplementations[parentBlockType as any];\n const isPurelyInsideCodeBlock =\n $from.sameParent($to) &&\n parentBlockSpec?.implementation.meta?.code === true;\n\n const markdown = isPurelyInsideCodeBlock\n ? view.state.doc.textBetween($from.pos, $to.pos)\n : cleanHTMLToMarkdown(externalHTML);\n\n return { clipboardHTML, externalHTML, markdown };\n}\n\nconst checkIfSelectionInNonEditableBlock = (view: EditorView) => {\n // Use ProseMirror's internal selection state to check for empty selection.\n // window.getSelection() returns null or a collapsed selection inside Shadow\n // DOM (Firefox, Safari, and Chromium edge cases), causing this guard to\n // misfire and silently skip clipboard writes. view.state.selection is always\n // accurate regardless of DOM mode.\n if (view.state.selection.empty) {\n return true;\n }\n\n // Let browser handle event if it's within a non-editable\n // \"island\". This means it's in selectable content within a\n // non-editable block. We only need to check one node as it's\n // not possible for the browser selection to start in an\n // editable block and end in a non-editable one.\n const selection = window.getSelection();\n if (selection && !selection.isCollapsed) {\n let node = selection.focusNode;\n while (node) {\n if (\n node instanceof HTMLElement &&\n node.getAttribute(\"contenteditable\") === \"false\"\n ) {\n return true;\n }\n\n node = node.parentElement;\n }\n }\n\n return false;\n};\n\nconst copyToClipboard = <\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, I, S>,\n view: EditorView,\n event: ClipboardEvent,\n) => {\n // Stops the default browser copy behaviour.\n event.preventDefault();\n event.clipboardData!.clearData();\n\n const { clipboardHTML, externalHTML, markdown } = selectedFragmentToHTML(\n view,\n editor,\n );\n\n // TODO: Writing to other MIME types not working in Safari for\n // some reason.\n event.clipboardData!.setData(\"blocknote/html\", clipboardHTML);\n event.clipboardData!.setData(\"text/html\", externalHTML);\n event.clipboardData!.setData(\"text/plain\", markdown);\n};\n\nexport const createCopyToClipboardExtension = <\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, I, S>,\n) =>\n Extension.create<{ editor: BlockNoteEditor<BSchema, I, S> }, undefined>({\n name: \"copyToClipboard\",\n addProseMirrorPlugins() {\n return [\n new Plugin({\n props: {\n handleDOMEvents: {\n copy(view, event) {\n if (checkIfSelectionInNonEditableBlock(view)) {\n return true;\n }\n\n copyToClipboard(editor, view, event);\n // Prevent default PM handler to be called\n return true;\n },\n cut(view, event) {\n if (checkIfSelectionInNonEditableBlock(view)) {\n return true;\n }\n\n copyToClipboard(editor, view, event);\n if (view.editable) {\n view.dispatch(view.state.tr.deleteSelection());\n }\n // Prevent default PM handler to be called\n return true;\n },\n // This is for the use-case in which only a block without content\n // is selected, e.g. an image block, and dragged (not using the\n // drag handle).\n dragstart(view, event) {\n // Checks if a `NodeSelection` is active.\n if (!(\"node\" in view.state.selection)) {\n return;\n }\n\n // Checks if a `blockContent` node is being dragged.\n if (\n (view.state.selection.node as Node).type.spec.group !==\n \"blockContent\"\n ) {\n return;\n }\n\n // Expands the selection to the parent `blockContainer` node.\n editor.transact((tr) =>\n tr.setSelection(\n new NodeSelection(\n tr.doc.resolve(view.state.selection.from - 1),\n ),\n ),\n );\n\n // Stops the default browser drag start behaviour.\n event.preventDefault();\n event.dataTransfer!.clearData();\n\n const { clipboardHTML, externalHTML, markdown } =\n selectedFragmentToHTML(view, editor);\n\n // TODO: Writing to other MIME types not working in Safari for\n // some reason.\n event.dataTransfer!.setData(\"blocknote/html\", clipboardHTML);\n event.dataTransfer!.setData(\"text/html\", externalHTML);\n event.dataTransfer!.setData(\"text/plain\", markdown);\n\n // Prevent default PM handler to be called\n return true;\n },\n },\n },\n }),\n ];\n },\n });\n","import { Extension } from \"@tiptap/core\";\nimport { getBackgroundColorAttribute } from \"../../../blocks/defaultProps.js\";\n\nexport const BackgroundColorExtension = Extension.create({\n name: \"blockBackgroundColor\",\n\n addGlobalAttributes() {\n return [\n {\n types: [\"tableCell\", \"tableHeader\"],\n attributes: {\n backgroundColor: getBackgroundColorAttribute(),\n },\n },\n ];\n },\n});\n","// Stripped down version of the TipTap HardBreak extension:\n// https://github.com/ueberdosis/tiptap/blob/f3258d9ee5fb7979102fe63434f6ea4120507311/packages/extension-hard-break/src/hard-break.ts#L80\n// Changes:\n// - Removed options\n// - Removed keyboard shortcuts & moved them to the `KeyboardShortcutsExtension`\n// - Removed `setHardBreak` command (added a simpler version in the Shift+Enter\n// handler in `KeyboardShortcutsExtension`).\n// - Added priority\nimport { mergeAttributes, Node } from \"@tiptap/core\";\n\nexport const HardBreak = Node.create({\n name: \"hardBreak\",\n\n inline: true,\n\n group: \"inline\",\n\n selectable: false,\n\n linebreakReplacement: true,\n\n priority: 10,\n\n parseHTML() {\n return [{ tag: \"br\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\"br\", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];\n },\n\n renderText() {\n return \"\\n\";\n },\n});\n","import { Node } from \"prosemirror-model\";\nimport { EditorState } from \"prosemirror-state\";\n\nimport {\n BlockInfo,\n getBlockInfoFromResolvedPos,\n} from \"../../../getBlockInfoFromPos.js\";\n\n/**\n * Returns the block info from the parent block\n * or undefined if we're at the root\n */\nexport const getParentBlockInfo = (\n doc: Node,\n beforePos: number,\n): BlockInfo | undefined => {\n const $pos = doc.resolve(beforePos);\n const depth = $pos.depth - 1;\n\n if (depth < 1) {\n return undefined;\n }\n\n const parentBeforePos = $pos.before(depth);\n const parentNode = doc.resolve(parentBeforePos).nodeAfter;\n\n if (!parentNode) {\n return undefined;\n }\n\n if (!parentNode.type.spec.group?.includes(\"bnBlock\")) {\n return getParentBlockInfo(doc, parentBeforePos);\n }\n\n const parentBlockInfo = getBlockInfoFromResolvedPos(\n doc.resolve(parentBeforePos),\n );\n\n return parentBlockInfo;\n};\n\n/**\n * Returns the block info from the sibling block before (above) the given block,\n * or undefined if the given block is the first sibling.\n */\nexport const getPrevBlockInfo = (doc: Node, beforePos: number) => {\n const $pos = doc.resolve(beforePos);\n\n const indexInParent = $pos.index();\n\n if (indexInParent === 0) {\n return undefined;\n }\n\n const prevBlockBeforePos = $pos.posAtIndex(indexInParent - 1);\n\n const prevBlockInfo = getBlockInfoFromResolvedPos(\n doc.resolve(prevBlockBeforePos),\n );\n return prevBlockInfo;\n};\n\n/**\n * Returns the block info from the sibling block after (below) the given block,\n * or undefined if the given block is the last sibling.\n */\nexport const getNextBlockInfo = (doc: Node, beforePos: number) => {\n const $pos = doc.resolve(beforePos);\n\n const indexInParent = $pos.index();\n\n if (indexInParent === $pos.node().childCount - 1) {\n return undefined;\n }\n\n const nextBlockBeforePos = $pos.posAtIndex(indexInParent + 1);\n\n const nextBlockInfo = getBlockInfoFromResolvedPos(\n doc.resolve(nextBlockBeforePos),\n );\n return nextBlockInfo;\n};\n\n/**\n * If a block has children like this:\n * A\n * - B\n * - C\n * -- D\n *\n * Then the bottom nested block returned is D.\n */\nexport const getBottomNestedBlockInfo = (doc: Node, blockInfo: BlockInfo) => {\n while (blockInfo.childContainer) {\n const group = blockInfo.childContainer.node;\n\n const newPos = doc\n .resolve(blockInfo.childContainer.beforePos + 1)\n .posAtIndex(group.childCount - 1);\n blockInfo = getBlockInfoFromResolvedPos(doc.resolve(newPos));\n }\n\n return blockInfo;\n};\n\nconst canMerge = (prevBlockInfo: BlockInfo, nextBlockInfo: BlockInfo) => {\n return (\n prevBlockInfo.isBlockContainer &&\n prevBlockInfo.blockContent.node.type.spec.content === \"inline*\" &&\n prevBlockInfo.blockContent.node.childCount > 0 &&\n nextBlockInfo.isBlockContainer &&\n nextBlockInfo.blockContent.node.type.spec.content === \"inline*\"\n );\n};\n\nconst mergeBlocks = (\n state: EditorState,\n dispatch: ((args?: any) => any) | undefined,\n prevBlockInfo: BlockInfo,\n nextBlockInfo: BlockInfo,\n) => {\n // Un-nests all children of the next block.\n if (!nextBlockInfo.isBlockContainer) {\n throw new Error(\n `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`,\n );\n }\n\n // Removes a level of nesting all children of the next block by 1 level, if it contains both content and block\n // group nodes.\n if (nextBlockInfo.childContainer) {\n const childBlocksStart = state.doc.resolve(\n nextBlockInfo.childContainer.beforePos + 1,\n );\n const childBlocksEnd = state.doc.resolve(\n nextBlockInfo.childContainer.afterPos - 1,\n );\n const childBlocksRange = childBlocksStart.blockRange(childBlocksEnd);\n\n if (dispatch) {\n const pos = state.doc.resolve(nextBlockInfo.bnBlock.beforePos);\n state.tr.lift(childBlocksRange!, pos.depth);\n }\n }\n\n // Deletes the boundary between the two blocks. Can be thought of as\n // removing the closing tags of the first block and the opening tags of the\n // second one to stitch them together.\n if (dispatch) {\n if (!prevBlockInfo.isBlockContainer) {\n throw new Error(\n `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`,\n );\n }\n\n // TODO: test merging between a columnList and paragraph, between two columnLists, and v.v.\n dispatch(\n state.tr.delete(\n prevBlockInfo.blockContent.afterPos - 1,\n nextBlockInfo.blockContent.beforePos + 1,\n ),\n );\n }\n\n return true;\n};\n\nexport const mergeBlocksCommand =\n (posBetweenBlocks: number) =>\n ({\n state,\n dispatch,\n }: {\n state: EditorState;\n dispatch: ((args?: any) => any) | undefined;\n }) => {\n const $pos = state.doc.resolve(posBetweenBlocks);\n const nextBlockInfo = getBlockInfoFromResolvedPos($pos);\n\n const prevBlockInfo = getPrevBlockInfo(\n state.doc,\n nextBlockInfo.bnBlock.beforePos,\n );\n\n if (!prevBlockInfo) {\n return false;\n }\n\n const bottomNestedBlockInfo = getBottomNestedBlockInfo(\n state.doc,\n prevBlockInfo,\n );\n\n if (!canMerge(bottomNestedBlockInfo, nextBlockInfo)) {\n return false;\n }\n\n return mergeBlocks(state, dispatch, bottomNestedBlockInfo, nextBlockInfo);\n };\n","import { Extension } from \"@tiptap/core\";\nimport { Fragment, Node } from \"prosemirror-model\";\nimport { TextSelection } from \"prosemirror-state\";\n\nimport {\n getBottomNestedBlockInfo,\n getNextBlockInfo,\n getParentBlockInfo,\n getPrevBlockInfo,\n mergeBlocksCommand,\n} from \"../../../api/blockManipulation/commands/mergeBlocks/mergeBlocks.js\";\nimport {\n liftItem,\n nestBlock,\n unnestBlock,\n} from \"../../../api/blockManipulation/commands/nestBlock/nestBlock.js\";\nimport { fixColumnList } from \"../../../api/blockManipulation/commands/replaceBlocks/util/fixColumnList.js\";\nimport { splitBlockCommand } from \"../../../api/blockManipulation/commands/splitBlock/splitBlock.js\";\nimport { updateBlockCommand } from \"../../../api/blockManipulation/commands/updateBlock/updateBlock.js\";\nimport {\n getBlockInfoFromResolvedPos,\n getBlockInfoFromSelection,\n} from \"../../../api/getBlockInfoFromPos.js\";\nimport { BlockNoteEditor } from \"../../../editor/BlockNoteEditor.js\";\nimport { FilePanelExtension } from \"../../FilePanel/FilePanel.js\";\nimport { FormattingToolbarExtension } from \"../../FormattingToolbar/FormattingToolbar.js\";\n\nexport const KeyboardShortcutsExtension = Extension.create<{\n editor: BlockNoteEditor<any, any, any>;\n tabBehavior: \"prefer-navigate-ui\" | \"prefer-indent\";\n}>({\n priority: 50,\n\n // TODO: The shortcuts need a refactor. Do we want to use a command priority\n // design as there is now, or clump the logic into a single function?\n addKeyboardShortcuts() {\n // handleBackspace is partially adapted from https://github.com/ueberdosis/tiptap/blob/ed56337470efb4fd277128ab7ef792b37cfae992/packages/core/src/extensions/keymap.ts\n const handleBackspace = () =>\n this.editor.commands.first(({ chain, commands }) => [\n // Deletes the selection if it's not empty.\n () => commands.deleteSelection(),\n // Undoes an input rule if one was triggered in the last editor state change.\n () => commands.undoInputRule(),\n // Reverts block content type to a paragraph if the selection is at the start of the block.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const selectionAtBlockStart =\n state.selection.from === blockInfo.blockContent.beforePos + 1;\n const isParagraph =\n blockInfo.blockContent.node.type.name === \"paragraph\";\n\n if (selectionAtBlockStart && !isParagraph) {\n return commands.command(\n updateBlockCommand(blockInfo.bnBlock.beforePos, {\n type: \"paragraph\",\n props: {},\n }),\n );\n }\n\n return false;\n }),\n // Removes a level of nesting if the block is indented if the selection is at the start of the block.\n () =>\n commands.command(({ state, tr }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n const { blockContent } = blockInfo;\n\n const selectionAtBlockStart =\n state.selection.from === blockContent.beforePos + 1;\n\n if (selectionAtBlockStart) {\n return liftItem(\n tr,\n tr.doc.type.schema.nodes[\"blockContainer\"],\n tr.doc.type.schema.nodes[\"blockGroup\"],\n );\n }\n\n return false;\n }),\n // Merges block with the previous one if it isn't indented, and the selection is at the start of the\n // block. The target block for merging must contain inline content.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n const { bnBlock: blockContainer, blockContent } = blockInfo;\n\n const prevBlockInfo = getPrevBlockInfo(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n // If the previous block has no inline content, it can't be merged.\n // It's instead deleted, which is done later in the chan, so we\n // return early here.\n if (\n !prevBlockInfo ||\n !prevBlockInfo.isBlockContainer ||\n prevBlockInfo.blockContent.node.type.spec.content !== \"inline*\"\n ) {\n return false;\n }\n\n const selectionAtBlockStart =\n state.selection.from === blockContent.beforePos + 1;\n const selectionEmpty = state.selection.empty;\n\n const posBetweenBlocks = blockContainer.beforePos;\n\n if (selectionAtBlockStart && selectionEmpty) {\n return chain()\n .command(mergeBlocksCommand(posBetweenBlocks))\n .scrollIntoView()\n .run();\n }\n\n return false;\n }),\n // If the previous block is a columnList, moves the current block to\n // the end of the last column in it.\n () =>\n commands.command(({ state, tr, dispatch }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const selectionAtBlockStart =\n state.selection.from === blockInfo.blockContent.beforePos + 1;\n if (!selectionAtBlockStart) {\n return false;\n }\n\n const prevBlockInfo = getPrevBlockInfo(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n if (!prevBlockInfo || prevBlockInfo.isBlockContainer) {\n return false;\n }\n\n if (dispatch) {\n const columnAfterPos = prevBlockInfo.bnBlock.afterPos - 1;\n const $blockAfterPos = tr.doc.resolve(columnAfterPos - 1);\n\n tr.delete(\n blockInfo.bnBlock.beforePos,\n blockInfo.bnBlock.afterPos,\n );\n tr.insert($blockAfterPos.pos, blockInfo.bnBlock.node);\n tr.setSelection(\n TextSelection.near(tr.doc.resolve($blockAfterPos.pos + 1)),\n );\n\n return true;\n }\n\n return false;\n }),\n // If the block is the first in a column, moves it to the end of the\n // previous column. If there is no previous column, moves it above the\n // columnList.\n () =>\n commands.command(({ state, tr, dispatch }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const selectionAtBlockStart =\n tr.selection.from === blockInfo.blockContent.beforePos + 1;\n if (!selectionAtBlockStart) {\n return false;\n }\n\n const $pos = tr.doc.resolve(blockInfo.bnBlock.beforePos);\n\n const prevBlock = $pos.nodeBefore;\n if (prevBlock) {\n return false;\n }\n\n const parentBlock = $pos.node();\n if (parentBlock.type.name !== \"column\") {\n return false;\n }\n\n const $blockPos = tr.doc.resolve(blockInfo.bnBlock.beforePos);\n const $columnPos = tr.doc.resolve($blockPos.before());\n const columnListPos = $columnPos.before();\n\n if (dispatch) {\n tr.delete(\n blockInfo.bnBlock.beforePos,\n blockInfo.bnBlock.afterPos,\n );\n fixColumnList(tr, columnListPos);\n\n if ($columnPos.pos === columnListPos + 1) {\n tr.insert(columnListPos, blockInfo.bnBlock.node);\n tr.setSelection(\n TextSelection.near(tr.doc.resolve(columnListPos)),\n );\n } else {\n tr.insert($columnPos.pos - 1, blockInfo.bnBlock.node);\n tr.setSelection(\n TextSelection.near(tr.doc.resolve($columnPos.pos)),\n );\n }\n }\n\n return true;\n }),\n // Deletes the current block if it's an empty block with inline content,\n // and moves the selection to the previous block.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const blockEmpty =\n blockInfo.blockContent.node.childCount === 0 &&\n blockInfo.blockContent.node.type.spec.content === \"inline*\";\n\n if (blockEmpty) {\n const prevBlockInfo = getPrevBlockInfo(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n if (!prevBlockInfo) {\n return false;\n }\n const bottomNestedPrevBlockInfo = getBottomNestedBlockInfo(\n state.doc,\n prevBlockInfo,\n );\n if (!bottomNestedPrevBlockInfo.isBlockContainer) {\n return false;\n }\n if (\n !bottomNestedPrevBlockInfo ||\n !bottomNestedPrevBlockInfo.isBlockContainer\n ) {\n return false;\n }\n\n let chainedCommands = chain();\n\n // Moves the children the current block.\n if (blockInfo.childContainer) {\n chainedCommands.insertContentAt(\n blockInfo.bnBlock.afterPos,\n blockInfo.childContainer?.node.content,\n );\n }\n\n if (\n bottomNestedPrevBlockInfo.blockContent.node.type.spec\n .content === \"tableRow+\"\n ) {\n const tableBlockEndPos = blockInfo.bnBlock.beforePos - 1;\n const tableBlockContentEndPos = tableBlockEndPos - 1;\n const lastRowEndPos = tableBlockContentEndPos - 1;\n const lastCellEndPos = lastRowEndPos - 1;\n const lastCellParagraphEndPos = lastCellEndPos - 1;\n\n chainedCommands = chainedCommands.setTextSelection(\n lastCellParagraphEndPos,\n );\n } else if (\n bottomNestedPrevBlockInfo.blockContent.node.type.spec\n .content === \"\"\n ) {\n chainedCommands = chainedCommands.setNodeSelection(\n bottomNestedPrevBlockInfo.blockContent.beforePos,\n );\n } else {\n const blockContentEndPos =\n bottomNestedPrevBlockInfo.blockContent.afterPos - 1;\n\n chainedCommands =\n chainedCommands.setTextSelection(blockContentEndPos);\n }\n\n return chainedCommands\n .deleteRange({\n from: blockInfo.bnBlock.beforePos,\n to: blockInfo.bnBlock.afterPos,\n })\n .scrollIntoView()\n .run();\n }\n\n return false;\n }),\n // Deletes previous block if it contains no content and isn't a table,\n // when the selection is empty and at the start of the block. Moves the\n // current block into the deleted block's place.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const selectionAtBlockStart =\n state.selection.from === blockInfo.blockContent.beforePos + 1;\n const selectionEmpty = state.selection.empty;\n\n const prevBlockInfo = getPrevBlockInfo(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n\n if (prevBlockInfo && selectionAtBlockStart && selectionEmpty) {\n const bottomBlock = getBottomNestedBlockInfo(\n state.doc,\n prevBlockInfo,\n );\n\n if (!bottomBlock.isBlockContainer) {\n return false;\n }\n\n const prevBlockNotTableAndNoContent =\n bottomBlock.blockContent.node.type.spec.content === \"\" ||\n (bottomBlock.blockContent.node.type.spec.content ===\n \"inline*\" &&\n bottomBlock.blockContent.node.childCount === 0);\n\n if (prevBlockNotTableAndNoContent) {\n return chain()\n .cut(\n {\n from: blockInfo.bnBlock.beforePos,\n to: blockInfo.bnBlock.afterPos,\n },\n bottomBlock.bnBlock.afterPos,\n )\n .deleteRange({\n from: bottomBlock.bnBlock.beforePos,\n to: bottomBlock.bnBlock.afterPos,\n })\n .run();\n }\n }\n\n return false;\n }),\n ]);\n\n const handleDelete = () =>\n this.editor.commands.first(({ chain, commands }) => [\n // Deletes the selection if it's not empty.\n () => commands.deleteSelection(),\n // Deletes the first child block and un-nests its children, if the\n // selection is empty and at the end of the current block. If both the\n // parent and child blocks have inline content, the child block's\n // content is appended to the parent's. The child block's own children\n // are unindented before it's deleted.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer || !blockInfo.childContainer) {\n return false;\n }\n const { blockContent, childContainer } = blockInfo;\n\n const selectionAtBlockEnd =\n state.selection.from === blockContent.afterPos - 1;\n const selectionEmpty = state.selection.empty;\n\n const firstChildBlockInfo = getBlockInfoFromResolvedPos(\n state.doc.resolve(childContainer.beforePos + 1),\n );\n if (!firstChildBlockInfo.isBlockContainer) {\n return false;\n }\n\n if (selectionAtBlockEnd && selectionEmpty) {\n const firstChildBlockContent =\n firstChildBlockInfo.blockContent.node;\n const firstChildBlockHasInlineContent =\n firstChildBlockContent.type.spec.content === \"inline*\";\n const blockHasInlineContent =\n blockContent.node.type.spec.content === \"inline*\";\n\n return (\n chain()\n // Un-nests child block's children if necessary.\n .insertContentAt(\n firstChildBlockInfo.bnBlock.afterPos,\n firstChildBlockInfo.childContainer?.node.content ||\n Fragment.empty,\n )\n .deleteRange(\n // Deletes whole child container if there's only one child.\n childContainer.node.childCount === 1\n ? {\n from: childContainer.beforePos,\n to: childContainer.afterPos,\n }\n : {\n from: firstChildBlockInfo.bnBlock.beforePos,\n to: firstChildBlockInfo.bnBlock.afterPos,\n },\n )\n // Appends inline content from child block if possible.\n .insertContentAt(\n state.selection.from,\n firstChildBlockHasInlineContent && blockHasInlineContent\n ? firstChildBlockContent.content\n : null,\n )\n .setTextSelection(state.selection.from)\n .scrollIntoView()\n .run()\n );\n }\n\n return false;\n }),\n // Merges block with the next one (at the same nesting level or lower),\n // if one exists, the block has no children, and the selection is at the\n // end of the block.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n const { bnBlock: blockContainer, blockContent } = blockInfo;\n\n const nextBlockInfo = getNextBlockInfo(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n if (!nextBlockInfo || !nextBlockInfo.isBlockContainer) {\n return false;\n }\n\n const selectionAtBlockEnd =\n state.selection.from === blockContent.afterPos - 1;\n const selectionEmpty = state.selection.empty;\n\n const posBetweenBlocks = blockContainer.afterPos;\n\n if (selectionAtBlockEnd && selectionEmpty) {\n return chain()\n .command(mergeBlocksCommand(posBetweenBlocks))\n .scrollIntoView()\n .run();\n }\n\n return false;\n }),\n // If the next block is a columnList, moves the first block from its\n // first column to after the current block.\n () =>\n commands.command(({ state, tr, dispatch }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const selectionAtBlockEnd =\n state.selection.from === blockInfo.blockContent.afterPos - 1;\n if (!selectionAtBlockEnd) {\n return false;\n }\n\n const nextBlockInfo = getNextBlockInfo(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n if (!nextBlockInfo || nextBlockInfo.isBlockContainer) {\n return false;\n }\n\n if (dispatch) {\n const columnBeforePos = nextBlockInfo.bnBlock.beforePos + 1;\n const $blockBeforePos = tr.doc.resolve(columnBeforePos + 1);\n\n tr.delete(\n $blockBeforePos.pos,\n $blockBeforePos.pos + $blockBeforePos.nodeAfter!.nodeSize,\n );\n fixColumnList(tr, nextBlockInfo.bnBlock.beforePos);\n tr.insert(blockInfo.bnBlock.afterPos, $blockBeforePos.nodeAfter!);\n tr.setSelection(\n TextSelection.near(tr.doc.resolve($blockBeforePos.pos)),\n );\n\n return true;\n }\n\n return false;\n }),\n // If the block is the last in a column, moves it to the start of the\n // next column. If there is no next column, moves it below the\n // columnList.\n () =>\n commands.command(({ state, tr, dispatch }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const selectionAtBlockEnd =\n tr.selection.from === blockInfo.blockContent.afterPos - 1;\n if (!selectionAtBlockEnd) {\n return false;\n }\n\n const $pos = tr.doc.resolve(blockInfo.bnBlock.afterPos);\n\n const nextBlock = $pos.nodeAfter;\n if (nextBlock) {\n return false;\n }\n\n const parentBlock = $pos.node();\n if (parentBlock.type.name !== \"column\") {\n return false;\n }\n\n const $blockEndPos = tr.doc.resolve(blockInfo.bnBlock.afterPos);\n const $columnEndPos = tr.doc.resolve($blockEndPos.after());\n const columnListEndPos = $columnEndPos.after();\n\n if (dispatch) {\n // Position before first block in next column, or first block\n // after columnList if there is no next column.\n const nextBlockBeforePos =\n $columnEndPos.pos === columnListEndPos - 1\n ? columnListEndPos\n : $columnEndPos.pos + 1;\n const nextBlockInfo = getBlockInfoFromResolvedPos(\n tr.doc.resolve(nextBlockBeforePos),\n );\n\n tr.delete(\n nextBlockInfo.bnBlock.beforePos,\n nextBlockInfo.bnBlock.afterPos,\n );\n fixColumnList(\n tr,\n columnListEndPos - $columnEndPos.node().nodeSize,\n );\n tr.insert($blockEndPos.pos, nextBlockInfo.bnBlock.node);\n tr.setSelection(\n TextSelection.near(tr.doc.resolve(nextBlockBeforePos)),\n );\n }\n\n return true;\n }),\n // Deletes the next block at either the same or lower nesting level, if\n // the selection is empty and at the end of the block. If both the\n // current and next blocks have inline content, the next block's\n // content is appended to the current block's. The next block's own\n // children are unindented before it's deleted.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n const { blockContent } = blockInfo;\n\n const selectionAtBlockEnd =\n state.selection.from === blockContent.afterPos - 1;\n const selectionEmpty = state.selection.empty;\n\n if (selectionAtBlockEnd && selectionEmpty) {\n const getNextBlockInfoAtAnyLevel = (\n doc: Node,\n beforePos: number,\n ) => {\n const nextBlockInfo = getNextBlockInfo(doc, beforePos);\n if (nextBlockInfo) {\n return nextBlockInfo;\n }\n\n const parentBlockInfo = getParentBlockInfo(doc, beforePos);\n if (!parentBlockInfo) {\n return undefined;\n }\n\n return getNextBlockInfoAtAnyLevel(\n doc,\n parentBlockInfo.bnBlock.beforePos,\n );\n };\n\n const nextBlockInfo = getNextBlockInfoAtAnyLevel(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n if (!nextBlockInfo || !nextBlockInfo.isBlockContainer) {\n return false;\n }\n\n const nextBlockContent = nextBlockInfo.blockContent.node;\n const nextBlockHasInlineContent =\n nextBlockContent.type.spec.content === \"inline*\";\n const blockHasInlineContent =\n blockContent.node.type.spec.content === \"inline*\";\n\n return (\n chain()\n // Un-nests next block's children if necessary.\n .insertContentAt(\n nextBlockInfo.bnBlock.afterPos,\n nextBlockInfo.childContainer?.node.content ||\n Fragment.empty,\n )\n .deleteRange({\n from: nextBlockInfo.bnBlock.beforePos,\n to: nextBlockInfo.bnBlock.afterPos,\n })\n // Appends inline content from child block if possible.\n .insertContentAt(\n state.selection.from,\n nextBlockHasInlineContent && blockHasInlineContent\n ? nextBlockContent.content\n : null,\n )\n .setTextSelection(state.selection.from)\n .scrollIntoView()\n .run()\n );\n }\n\n return false;\n }),\n // Deletes the current block if it's an empty block with inline content,\n // and moves the selection to the next block.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const blockEmpty =\n blockInfo.blockContent.node.childCount === 0 &&\n blockInfo.blockContent.node.type.spec.content === \"inline*\";\n\n if (blockEmpty) {\n const nextBlockInfo = getNextBlockInfo(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n if (!nextBlockInfo || !nextBlockInfo.isBlockContainer) {\n return false;\n }\n\n let chainedCommands = chain();\n\n if (\n nextBlockInfo.blockContent.node.type.spec.content ===\n \"tableRow+\"\n ) {\n const tableBlockStartPos = blockInfo.bnBlock.afterPos + 1;\n const tableBlockContentStartPos = tableBlockStartPos + 1;\n const firstRowStartPos = tableBlockContentStartPos + 1;\n const firstCellStartPos = firstRowStartPos + 1;\n const firstCellParagraphStartPos = firstCellStartPos + 1;\n\n chainedCommands = chainedCommands.setTextSelection(\n firstCellParagraphStartPos,\n );\n } else if (\n nextBlockInfo.blockContent.node.type.spec.content === \"\"\n ) {\n chainedCommands = chainedCommands.setNodeSelection(\n nextBlockInfo.blockContent.beforePos,\n );\n } else {\n chainedCommands = chainedCommands.setTextSelection(\n nextBlockInfo.blockContent.beforePos + 1,\n );\n }\n\n return chainedCommands\n .deleteRange({\n from: blockInfo.bnBlock.beforePos,\n to: blockInfo.bnBlock.afterPos,\n })\n .scrollIntoView()\n .run();\n }\n\n return false;\n }),\n // Deletes next block if it contains no content and isn't a table,\n // when the selection is empty and at the end of the block. Moves the\n // current block into the deleted block's place.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n\n const selectionAtBlockEnd =\n state.selection.from === blockInfo.blockContent.afterPos - 1;\n const selectionEmpty = state.selection.empty;\n\n const nextBlockInfo = getNextBlockInfo(\n state.doc,\n blockInfo.bnBlock.beforePos,\n );\n if (!nextBlockInfo) {\n return false;\n }\n if (!nextBlockInfo.isBlockContainer) {\n return false;\n }\n\n if (nextBlockInfo && selectionAtBlockEnd && selectionEmpty) {\n const nextBlockNotTableAndNoContent =\n nextBlockInfo.blockContent.node.type.spec.content === \"\" ||\n (nextBlockInfo.blockContent.node.type.spec.content ===\n \"inline*\" &&\n nextBlockInfo.blockContent.node.childCount === 0);\n\n if (nextBlockNotTableAndNoContent) {\n const childBlocks =\n nextBlockInfo.bnBlock.node.lastChild!.content;\n return chain()\n .deleteRange({\n from: nextBlockInfo.bnBlock.beforePos,\n to: nextBlockInfo.bnBlock.afterPos,\n })\n .insertContentAt(\n blockInfo.bnBlock.afterPos,\n nextBlockInfo.bnBlock.node.childCount === 2\n ? childBlocks\n : null,\n )\n .run();\n }\n }\n\n return false;\n }),\n ]);\n\n const handleEnter = (withShift = false) => {\n return this.editor.commands.first(({ commands, tr }) => [\n // Removes a level of nesting if the block is empty & indented, while the selection is also empty & at the start\n // of the block.\n () =>\n commands.command(({ state, tr }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n const { bnBlock: blockContainer, blockContent } = blockInfo;\n\n const { depth } = state.doc.resolve(blockContainer.beforePos);\n\n const selectionAtBlockStart =\n state.selection.$anchor.parentOffset === 0;\n const selectionEmpty =\n state.selection.anchor === state.selection.head;\n const blockEmpty = blockContent.node.childCount === 0;\n const blockIndented = depth > 1;\n\n if (\n selectionAtBlockStart &&\n selectionEmpty &&\n blockEmpty &&\n blockIndented\n ) {\n return liftItem(\n tr,\n tr.doc.type.schema.nodes[\"blockContainer\"],\n tr.doc.type.schema.nodes[\"blockGroup\"],\n );\n }\n\n return false;\n }),\n // Creates a hard break if block is configured to do so.\n () =>\n commands.command(({ state }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n\n const blockSpec =\n this.options.editor.schema.blockSpecs[blockInfo.blockNoteType];\n\n // NOTE: This likely doesn't work as intended - `blockSchema[type]`\n // holds the block *config* (type/propSchema/content), which carries\n // no `meta`, so `meta?.hardBreakShortcut` is always `undefined` and\n // this falls back to the default. It should read from the block\n // spec's implementation instead (i.e.\n // `editor.schema.blockSpecs[type].implementation.meta`), the way the\n // syntax-highlighting extension reads `meta.highlight`. Left as-is\n // for a follow-up pass.\n const blockHardBreakShortcut =\n blockSpec?.implementation?.meta?.hardBreakShortcut ??\n \"shift+enter\";\n\n if (blockHardBreakShortcut === \"none\") {\n return false;\n }\n\n if (\n // If shortcut is not configured, or is configured as \"shift+enter\",\n // create a hard break for shift+enter, but not for enter.\n (blockHardBreakShortcut === \"shift+enter\" && withShift) ||\n // If shortcut is configured as \"enter\", create a hard break for\n // both enter and shift+enter.\n blockHardBreakShortcut === \"enter\"\n ) {\n // \"plain\" blocks (e.g. code/math/diagram source) hold text only\n // (their content is `text*`), which can't contain a `hardBreak`\n // node - inserting one would split the block into a new one.\n // They represent line breaks as literal newline characters.\n if (blockSpec?.config?.content === \"plain\") {\n tr.insertText(\"\\n\", tr.selection.head);\n return true;\n }\n\n const marks =\n tr.storedMarks ||\n tr.selection.$head\n .marks()\n .filter((m) =>\n this.editor.extensionManager.splittableMarks.includes(\n m.type.name,\n ),\n );\n\n tr.insert(\n tr.selection.head,\n tr.doc.type.schema.nodes.hardBreak.create(),\n ).ensureMarks(marks);\n return true;\n }\n\n return false;\n }),\n // Creates a new block and moves the selection to it if the current one is empty, while the selection is also\n // empty & at the start of the block.\n () =>\n commands.command(({ state, dispatch, tr }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n const { bnBlock: blockContainer, blockContent } = blockInfo;\n\n const selectionAtBlockStart =\n state.selection.$anchor.parentOffset === 0;\n const selectionEmpty =\n state.selection.anchor === state.selection.head;\n const blockEmpty = blockContent.node.childCount === 0;\n\n if (selectionAtBlockStart && selectionEmpty && blockEmpty) {\n const newBlockInsertionPos = blockContainer.afterPos;\n const newBlockContentPos = newBlockInsertionPos + 2;\n\n if (dispatch) {\n // Creates a new block with the children of the current block,\n // if it has any.\n const newBlock = state.schema.nodes[\n \"blockContainer\"\n ].createAndFill(\n undefined,\n [\n state.schema.nodes[\"paragraph\"].createAndFill() ||\n undefined,\n blockInfo.childContainer?.node,\n ].filter((node) => node !== undefined),\n )!;\n\n // Inserts the new block and moves the selection to it.\n tr.insert(newBlockInsertionPos, newBlock)\n .setSelection(\n new TextSelection(tr.doc.resolve(newBlockContentPos)),\n )\n .scrollIntoView();\n\n // Deletes old block's children, as they have been moved to\n // the new one.\n if (blockInfo.childContainer) {\n tr.delete(\n blockInfo.childContainer.beforePos,\n blockInfo.childContainer.afterPos,\n );\n }\n }\n\n return true;\n }\n\n return false;\n }),\n // Splits the current block, moving content inside that's after the cursor to a new text block below. Also\n // deletes the selection beforehand, if it's not empty.\n () =>\n commands.command(({ state, chain }) => {\n const blockInfo = getBlockInfoFromSelection(state);\n if (!blockInfo.isBlockContainer) {\n return false;\n }\n const { blockContent } = blockInfo;\n\n const selectionAtBlockStart =\n state.selection.$anchor.parentOffset === 0;\n const blockEmpty = blockContent.node.childCount === 0;\n\n if (!blockEmpty) {\n chain()\n .deleteSelection()\n .command(\n splitBlockCommand(\n state.selection.from,\n selectionAtBlockStart,\n selectionAtBlockStart,\n ),\n )\n .scrollIntoView()\n .run();\n\n return true;\n }\n\n return false;\n }),\n ]);\n };\n\n return {\n Backspace: handleBackspace,\n Delete: handleDelete,\n Enter: () => handleEnter(),\n \"Shift-Enter\": () => handleEnter(true),\n // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the\n // editor since the browser will try to use tab for keyboard navigation.\n Tab: () => {\n if (\n this.options.tabBehavior !== \"prefer-indent\" &&\n (this.options.editor.getExtension(FormattingToolbarExtension)?.store\n .state ||\n this.options.editor.getExtension(FilePanelExtension)?.store\n .state !== undefined)\n // TODO need to check if the link toolbar is open or another alternative entirely\n ) {\n // don't handle tabs if a toolbar is shown, so we can tab into / out of it\n return false;\n }\n return nestBlock(this.options.editor);\n },\n \"Shift-Tab\": () => {\n if (\n this.options.tabBehavior !== \"prefer-indent\" &&\n (this.options.editor.getExtension(FormattingToolbarExtension)?.store\n .state ||\n this.options.editor.getExtension(FilePanelExtension)?.store\n .state !== undefined)\n // TODO need to check if the link toolbar is open or another alternative entirely\n // other menu types?\n ) {\n // don't handle tabs if a toolbar is shown, so we can tab into / out of it\n return false;\n }\n return unnestBlock(this.options.editor);\n },\n \"Shift-Mod-ArrowUp\": () => {\n this.options.editor.moveBlocksUp();\n return true;\n },\n \"Shift-Mod-ArrowDown\": () => {\n this.options.editor.moveBlocksDown();\n return true;\n },\n \"Mod-z\": () => this.options.editor.undo(),\n \"Mod-y\": () => this.options.editor.redo(),\n \"Shift-Mod-z\": () => this.options.editor.redo(),\n };\n },\n});\n","import { Extension } from \"@tiptap/core\";\n\nexport const TextAlignmentExtension = Extension.create({\n name: \"textAlignment\",\n\n addGlobalAttributes() {\n return [\n {\n // Generally text alignment is handled through props using the custom\n // blocks API. Tables are the only blocks that are created as TipTap\n // nodes and ported to blocks, so we need to add text alignment in a\n // separate extension.\n types: [\"tableCell\", \"tableHeader\"],\n attributes: {\n textAlignment: {\n default: \"left\",\n parseHTML: (element) => {\n return element.getAttribute(\"data-text-alignment\");\n },\n renderHTML: (attributes) => {\n if (attributes.textAlignment === \"left\") {\n return {};\n }\n return {\n \"data-text-alignment\": attributes.textAlignment,\n };\n },\n },\n },\n },\n ];\n },\n});\n","import { Extension } from \"@tiptap/core\";\nimport { getTextColorAttribute } from \"../../../blocks/defaultProps.js\";\n\nexport const TextColorExtension = Extension.create({\n name: \"blockTextColor\",\n\n addGlobalAttributes() {\n return [\n {\n types: [\"table\", \"tableCell\", \"tableHeader\"],\n attributes: {\n textColor: getTextColorAttribute(),\n },\n },\n ];\n },\n});\n","// THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY.\n// Source: https://data.iana.org/TLD/tlds-alpha-by-domain.txt\n// Regenerate with: pnpm --filter @blocknote/core update-tlds\n// Encoding format ported from linkifyjs (MIT) — trie collapsed into ASCII.\n\nexport const ENCODED_TLDS =\n \"aaa1rp3bb0ott3vie4c1le2ogado5udhabi7c0ademy5centure6ountant0s9o1tor4d0s1ult4e0g1ro2tna4f0l1rica5g0akhan5ency5i0g1rbus3force5tel5kdn3l0ibaba4pay4lfinanz6state5y2sace3tom5m0azon4ericanexpress7family11x2fam3ica3sterdam8nalytics7droid5quan4z2o0l2partments8p0le4q0uarelle8r0ab1mco4chi3my2pa2t0e3s0da2ia2sociates9t0hleta5torney7u0ction5di0ble3o3spost5thor3o0s4w0s2x0a2z0ure5ba0by2idu3namex4d1k2r0celona5laycard4s5efoot5gains6seball5ketball8uhaus5yern5b0c1t1va3cg1n2d1e0ats2uty4er2rlin4st0buy5t2f1g1h0arti5i0ble3d1ke2ng0o3o1z2j1lack0friday9ockbuster8g1omberg7ue3m0s1w2n0pparibas9o0ats3ehringer8fa2m1nd2o0k0ing5sch2tik2on4t1utique6x2r0adesco6idgestone9oadway5ker3ther5ussels7s1t1uild0ers6siness6y1zz3v1w1y1z0h3ca0b1fe2l0l1vinklein9m0era3p2non3petown5ital0one8r0avan4ds2e0er0s4s2sa1e1h1ino4t0ering5holic7ba1n1re3c1d1enter4o1rn3f0a1d2g1h0anel2nel4rity4se2t2eap3intai5ristmas6ome4urch5i0priani6rcle4sco3tadel4i0c2y3k1l0aims4eaning6ick2nic1que6othing5ud3ub0med6m1n1o0ach3des3ffee4llege4ogne5m0mbank4unity6pany2re3uter5sec4ndos3struction8ulting7tact3ractors9oking4l1p2rsica5untry4pon0s4rses6pa2r0edit0card4union9icket5own3s1uise0s6u0isinella9v1w1x1y0mru3ou3z2dad1nce3ta1e1ing3sun4y2clk3ds2e0al0er2s3gree4livery5l1oitte5ta3mocrat6ntal2ist5si0gn4v2hl2iamonds6et2gital5rect0ory7scount3ver5h2y2j1k1m1np2o0cs1tor4g1mains5t1wnload7rive4tv2ubai3pont4rban5vag2r2z2earth3t2c0o2deka3u0cation8e1g1mail3erck5nergy4gineer0ing9terprises10pson4quipment8r0icsson6ni3s0q1tate5t1u0rovision8s2vents5xchange6pert3osed4ress5traspace10fage2il1rwinds6th3mily4n0s2rm0ers5shion4t3edex3edback6rrari3ero6i0delity5o2lm2nal1nce1ial7re0stone6mdale6sh0ing5t0ness6j1k1lickr3ghts4r2orist4wers5y2m1o0o0d1tball6rd1ex2sale4um3undation8x2r0ee1senius7l1ogans4ntier7tr2ujitsu5n0d2rniture7tbol5yi3ga0l0lery3o1up4me0s3p1rden4y2b0iz3d0n2e0a1nt0ing5orge5f1g0ee3h1i0ft0s3ves2ing5l0ass3e1obal2o4m0ail3bh2o1x2n1odaddy5ld0point6f2odyear5g0le4p1t1v2p1q1r0ainger5phics5tis4een3ipe3ocery4up4s1t1u0cci3ge2ide2tars5ru3w1y2hair2mburg5ngout5us3bo2dfc0bank7ealth0care8lp1sinki6re1mes5iphop4samitsu7tachi5v2k0t2m1n1ockey4ldings5iday5medepot5goods5s0ense7nda3rse3spital5t0ing5t0els3mail5use3w2r1sbc3t1u0ghes5yatt3undai7ibm2cbc2e1u2d1e0ee3fm2kano4l1m0amat4db2mo0bilien9n0c1dustries8finiti5o2g1k1stitute6urance4e4t0ernational10uit4vestments10o1piranga7q1r0ish4s0maili5t0anbul7t0au2v3jaguar4va3cb2e0ep2tzt3welry6io2ll2m0p2nj2o0bs1urg4t1y2p0morgan6rs3uegos4niper7kaufen5ddi3e0rryhotels6properties14fh2g1h1i0a1ds2m1ndle4tchen5wi3m1n1oeln3matsu5sher5p0mg2n2r0d1ed3uokgroup8w1y0oto4z2la0caixa5mborghini8er3nd0rover6xess5salle5t0ino3robe5w0yer5b1c1ds2ease3clerc5frak4gal2o2xus4gbt3i0dl2fe0insurance9style7ghting6ke2lly3mited4o2ncoln4k2ve1ing5k1lc1p2oan0s3cker3us3l1ndon4tte1o3ve3pl0financial11r1s1t0d0a3u0ndbeck6xe1ury5v1y2ma0drid4if1son4keup4n0agement7go3p1rket0ing3s4riott5shalls7ttel5ba2c0kinsey7d1e0d0ia3et2lbourne7me1orial6n0u2rck0msd7g1h1iami3crosoft7l1ni1t2t0subishi9k1l0b1s2m0a2n1o0bi0le4da2e1i1m1nash3ey2ster5rmon3tgage6scow4to0rcycles9v0ie4p1q1r1s0d2t0n1r2u0seum3ic4v1w1x1y1z2na0b1goya4me2vy3ba2c1e0c1t0bank4flix4work5ustar5w0s2xt0direct7us4f0l2g0o2hk2i0co2ke1on3nja3ssan1y5l1o0kia3rton4w0ruz3tv4p1r0a1w2tt2u1yc2z2obi1server7ffice5kinawa6layan0group9lo3m0ega4ne1g1l0ine5oo2pen3racle3nge4g0anic5igins6saka4tsuka4t2vh3pa0ge2nasonic7ris2s1tners4s1y3y2ccw3e0t2f0izer5g1h0armacy6d1ilips5one2to0graphy6s4ysio5ics1tet2ures6d1n0g1k2oneer5zza4k1l0ace2y0station9umbing5s3m1n0c2ohl2ker3litie5rn2st3r0axi3ess3ime3o0d0uctions8f1gressive8mo2perties3y5tection8u0dential9s1t1ub2w0c2y2qa1pon3uebec3st5racing4dio4e0ad1lestate6tor2y4cipes5d0umbrella9hab3ise0n3t2liance6n0t0als5pair3ort3ublican8st0aurant8view0s5xroth6ich0ardli6oh3l1o1p2o0cks3deo3gers4om3s0vp3u0gby3hr2n2w0e2yukyu6sa0arland6fe0ty4kura4le1on3msclub4ung5ndvik0coromant12ofi4p1rl2s1ve2xo3b0i1s2c0b1haeffler7midt4olarships8ol3ule3warz5ience5ot3d1e0arch3t2cure1ity6ek2lect4ner3rvices6ven3w1x0y3fr2g1h0angrila6rp3ell3ia1ksha5oes2p0ping5uji3w3i0lk2na1gles5te3j1k0i0n2y0pe4l0ing4m0art3ile4n0cf3o0ccer3ial4ftbank4ware6hu2lar2utions7ng1y2y2pa0ce3ort2t3r0l2s1t0ada2ples4r1tebank4farm7c0group6ockholm6rage3e3ream4udio2y3yle4u0cks3pplies3y2ort5rf1gery5zuki5v1watch4iss4x1y0dney4stems6z2tab1ipei4lk2obao4rget4tamotors6r2too4x0i3c0i2d0k2eam2ch0nology8l1masek5nnis4va3f1g1h0d1eater2re6iaa2ckets5enda4ps2res2ol4j0maxx4x2k0maxx5l1m0all4n1o0day3kyo3ols3p1ray3shiba5tal3urs3wn2yota3s3r0ade1ing4ining5vel0ers0insurance16ust3v2t1ube2i1nes3shu4v0s2w1z2ua1bank3s2g1k1nicom3versity8o2ol2ps2s1y1z2va0cations7na1guard7c1e0gas3ntures6risign5sicherung10t2g1i0ajes4deo3g1king4llas4n1p1rgin4sa1ion4va1o3laanderen9n1odka3lvo3te1ing3o2yage5u2wales2mart4ter4ng0gou5tch0es6eather0channel12bcam3er2site5d0ding5ibo2r3f1hoswho6ien2ki2lliamhill9n0dows4e1ners6me2oodside6rk0s2ld3w2s1tc1f3xbox3erox4ihuan4n2xx2yz3yachts4hoo3maxun5ndex5e1odobashi7ga2kohama6u0tube6t1un3za0ppos4ra3ero3ip2m1one3uerich6w2\";\n","/**\n * Lightweight URL detection module replacing linkifyjs.\n *\n * Provides two functions:\n * - findLinks(): find all URLs/emails in arbitrary text (replaces linkifyjs find())\n * - tokenizeLink(): tokenize a single word for autolink validation (replaces linkifyjs tokenize())\n */\n\nimport { ENCODED_TLDS } from \"./tlds.js\";\n\nexport interface LinkMatch {\n type: string;\n value: string;\n isLink: boolean;\n href: string;\n start: number;\n end: number;\n}\n\n// ---------------------------------------------------------------------------\n// TLD set – used only for schemeless URL validation.\n// Protocol URLs (http://, https://, etc.) skip TLD checks.\n// Decoded once at module load from the trie-encoded IANA list in tlds.ts.\n// ---------------------------------------------------------------------------\n\nfunction decodeTlds(encoded: string): string[] {\n const words: string[] = [];\n const stack: string[] = [];\n let i = 0;\n while (i < encoded.length) {\n let popDigitCount = 0;\n while (\n i + popDigitCount < encoded.length &&\n encoded.charCodeAt(i + popDigitCount) >= 48 &&\n encoded.charCodeAt(i + popDigitCount) <= 57\n ) {\n popDigitCount++;\n }\n if (popDigitCount > 0) {\n words.push(stack.join(\"\"));\n let popCount = parseInt(encoded.substring(i, i + popDigitCount), 10);\n while (popCount-- > 0) {\n stack.pop();\n }\n i += popDigitCount;\n } else {\n stack.push(encoded[i]);\n i++;\n }\n }\n return words;\n}\n\nconst TLD_SET = new Set(decodeTlds(ENCODED_TLDS));\n\n// Special hostnames recognized without a TLD\nconst SPECIAL_HOSTS = new Set([\"localhost\"]);\n\n// ---------------------------------------------------------------------------\n// Regex building blocks\n// ---------------------------------------------------------------------------\n\n// Characters that are unlikely to be part of a URL when they appear at the end\nconst TRAILING_PUNCT = /[.,;:!?\"']+$/;\n\n// Protocol URLs: http:// https:// ftp:// ftps://\nconst PROTOCOL_RE = /(?:https?|ftp|ftps):\\/\\/[^\\s]+/g;\n\n// Mailto URLs: mailto:...\nconst MAILTO_RE = /mailto:[^\\s]+/g;\n\n// Bare email addresses: user@domain.tld\nconst EMAIL_RE =\n /[a-zA-Z0-9._%+-]+@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}/g;\n\n// Schemeless URLs: domain.tld with optional port and path\n// Hostname: one or more labels separated by dots, TLD is alpha-only 2+ chars\nconst SCHEMELESS_RE =\n /(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}(?::\\d{1,5})?(?:[/?#][^\\s]*)?/g;\n\n// ---------------------------------------------------------------------------\n// Post-processing helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Trim trailing punctuation and unbalanced closing brackets from a URL match.\n */\nfunction trimTrailing(value: string): string {\n let v = value;\n\n // Iteratively trim trailing punctuation and unbalanced brackets\n let changed = true;\n while (changed) {\n changed = false;\n\n // Trim trailing punctuation chars\n const before = v;\n v = v.replace(TRAILING_PUNCT, \"\");\n if (v !== before) {\n changed = true;\n }\n\n // Trim unbalanced closing brackets from the end\n for (const [open, close] of [\n [\"(\", \")\"],\n [\"[\", \"]\"],\n ] as const) {\n while (v.endsWith(close)) {\n const openCount = countChar(v, open);\n const closeCount = countChar(v, close);\n if (closeCount > openCount) {\n v = v.slice(0, -1);\n changed = true;\n } else {\n break;\n }\n }\n }\n }\n\n return v;\n}\n\nfunction countChar(str: string, ch: string): number {\n let count = 0;\n for (let i = 0; i < str.length; i++) {\n if (str[i] === ch) {\n count++;\n }\n }\n return count;\n}\n\n/**\n * Extract the TLD from a hostname string.\n * Returns the last dot-separated segment.\n */\nfunction extractTld(hostname: string): string {\n const parts = hostname.split(\".\");\n return parts[parts.length - 1].toLowerCase();\n}\n\nfunction isValidTld(hostname: string): boolean {\n const tld = extractTld(hostname);\n return TLD_SET.has(tld);\n}\n\n/**\n * Build the href for a URL value, prepending the default protocol if needed.\n */\nfunction buildHref(\n value: string,\n type: string,\n defaultProtocol: string,\n): string {\n if (type === \"email\") {\n return \"mailto:\" + value;\n }\n if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\//.test(value) || /^mailto:/i.test(value)) {\n // Already has a protocol\n return value;\n }\n return defaultProtocol + \"://\" + value;\n}\n\n// ---------------------------------------------------------------------------\n// findLinks()\n// ---------------------------------------------------------------------------\n\nexport interface FindOptions {\n defaultProtocol?: string;\n}\n\ninterface RawMatch {\n type: string;\n value: string;\n start: number;\n end: number;\n}\n\n/**\n * Find all URLs and email addresses in the given text.\n * Drop-in replacement for linkifyjs find().\n */\nexport function findLinks(text: string, options?: FindOptions): LinkMatch[] {\n if (!text) {\n return [];\n }\n\n const defaultProtocol = options?.defaultProtocol || \"http\";\n const rawMatches: RawMatch[] = [];\n\n // 1. Protocol URLs\n for (const m of text.matchAll(PROTOCOL_RE)) {\n rawMatches.push({\n type: \"url\",\n value: m[0],\n start: m.index!,\n end: m.index! + m[0].length,\n });\n }\n\n // 2. Mailto URLs\n for (const m of text.matchAll(MAILTO_RE)) {\n rawMatches.push({\n type: \"url\",\n value: m[0],\n start: m.index!,\n end: m.index! + m[0].length,\n });\n }\n\n // 3. Bare email addresses\n for (const m of text.matchAll(EMAIL_RE)) {\n rawMatches.push({\n type: \"email\",\n value: m[0],\n start: m.index!,\n end: m.index! + m[0].length,\n });\n }\n\n // 4. Schemeless URLs\n for (const m of text.matchAll(SCHEMELESS_RE)) {\n rawMatches.push({\n type: \"url\",\n value: m[0],\n start: m.index!,\n end: m.index! + m[0].length,\n });\n }\n\n // Sort by start position\n rawMatches.sort((a, b) => a.start - b.start || b.end - a.end);\n\n // Deduplicate overlapping matches (prefer earlier & longer)\n const deduped: RawMatch[] = [];\n let lastEnd = -1;\n for (const match of rawMatches) {\n if (match.start >= lastEnd) {\n deduped.push(match);\n lastEnd = match.end;\n }\n }\n\n // Post-process each match\n const results: LinkMatch[] = [];\n for (const raw of deduped) {\n const value = trimTrailing(raw.value);\n if (!value) {\n continue;\n }\n\n const start = raw.start;\n const end = start + value.length;\n\n // For schemeless URLs, validate TLD\n if (raw.type === \"url\" && !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value)) {\n const hostname = new URL(\"http://\" + value).hostname;\n if (!isValidTld(hostname)) {\n continue;\n }\n }\n\n // For emails, validate TLD\n if (raw.type === \"email\") {\n const hostname = value.split(\"@\")[1];\n if (!isValidTld(hostname)) {\n continue;\n }\n }\n\n const href = buildHref(value, raw.type, defaultProtocol);\n\n results.push({\n type: raw.type,\n value,\n isLink: true,\n href,\n start,\n end,\n });\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// tokenizeLink()\n// ---------------------------------------------------------------------------\n\n/**\n * Tokenize a single word for autolink validation.\n * Drop-in replacement for: tokenize(word).map(t => t.toObject(defaultProtocol))\n *\n * Returns an array of LinkMatch tokens. The autolink code checks:\n * - 1 token with isLink=true → valid single link\n * - 3 tokens with middle isLink=true and outer brackets → valid wrapped link\n */\nexport function tokenizeLink(\n text: string,\n defaultProtocol = \"http\",\n): LinkMatch[] {\n if (!text) {\n return [nonLinkToken(text, 0, 0)];\n }\n\n // Check for bracket wrapping: (url), [url], {url}\n const brackets: Array<[string, string]> = [\n [\"(\", \")\"],\n [\"[\", \"]\"],\n [\"{\", \"}\"],\n ];\n for (const [open, close] of brackets) {\n if (text.startsWith(open) && text.endsWith(close) && text.length > 2) {\n const inner = text.slice(1, -1);\n if (isSingleUrl(inner)) {\n return [\n nonLinkToken(open, 0, 1),\n linkToken(inner, 1, 1 + inner.length, defaultProtocol),\n nonLinkToken(close, 1 + inner.length, text.length),\n ];\n }\n }\n }\n\n // Check for trailing punctuation (e.g., \"example.com.\" → link + dot)\n if (text.endsWith(\".\") && text.length > 1) {\n const withoutDot = text.slice(0, -1);\n if (isSingleUrl(withoutDot)) {\n return [\n linkToken(withoutDot, 0, withoutDot.length, defaultProtocol),\n nonLinkToken(\".\", withoutDot.length, text.length),\n ];\n }\n }\n\n // Check if the whole text is a single URL\n if (isSingleUrl(text)) {\n return [linkToken(text, 0, text.length, defaultProtocol)];\n }\n\n // Not a link\n return [nonLinkToken(text, 0, text.length)];\n}\n\n/**\n * Check if a string is a single complete URL (no extra chars).\n */\nfunction isSingleUrl(text: string): boolean {\n // Protocol URLs\n if (/^(?:https?|ftp|ftps):\\/\\/[^\\s]+$/.test(text)) {\n return true;\n }\n\n // Mailto URLs\n if (/^mailto:[^\\s]+$/.test(text)) {\n return true;\n }\n\n // Special hosts (e.g., localhost)\n if (SPECIAL_HOSTS.has(text.toLowerCase())) {\n return true;\n }\n\n // Schemeless URLs: hostname.tld with optional port and path\n const schemelessFull =\n /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+([a-zA-Z]{2,})(?::\\d{1,5})?(?:[/?#][^\\s]*)?$/;\n const match = text.match(schemelessFull);\n if (match) {\n const tld = match[1].toLowerCase();\n // TLD must be a-z only (no numbers) and recognized\n if (TLD_SET.has(tld)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction linkToken(\n value: string,\n start: number,\n end: number,\n defaultProtocol: string,\n): LinkMatch {\n const type =\n value.includes(\"@\") &&\n !value.includes(\"://\") &&\n !value.startsWith(\"mailto:\")\n ? \"email\"\n : \"url\";\n return {\n type,\n value,\n isLink: true,\n href: buildHref(value, type, defaultProtocol),\n start,\n end,\n };\n}\n\nfunction nonLinkToken(value: string, start: number, end: number): LinkMatch {\n return {\n type: \"text\",\n value,\n isLink: false,\n href: value,\n start,\n end,\n };\n}\n","// From DOMPurify\n// https://github.com/cure53/DOMPurify/blob/main/src/regexp.ts\nexport const UNICODE_WHITESPACE_PATTERN =\n \"[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]\";\n\nexport const UNICODE_WHITESPACE_REGEX = new RegExp(UNICODE_WHITESPACE_PATTERN);\nexport const UNICODE_WHITESPACE_REGEX_END = new RegExp(\n `${UNICODE_WHITESPACE_PATTERN}$`,\n);\nexport const UNICODE_WHITESPACE_REGEX_GLOBAL = new RegExp(\n UNICODE_WHITESPACE_PATTERN,\n \"g\",\n);\n","import type { NodeWithPos } from \"@tiptap/core\";\nimport {\n combineTransactionSteps,\n findChildrenInRange,\n getChangedRanges,\n getMarksBetween,\n} from \"@tiptap/core\";\nimport type { MarkType } from \"@tiptap/pm/model\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport type { LinkMatch } from \"./linkDetector.js\";\nimport { tokenizeLink } from \"./linkDetector.js\";\n\nimport {\n UNICODE_WHITESPACE_REGEX,\n UNICODE_WHITESPACE_REGEX_END,\n} from \"./whitespace.js\";\n\n/**\n * Check if the provided tokens form a valid link structure, which can either be a single link token\n * or a link token surrounded by parentheses or square brackets.\n */\nfunction isValidLinkStructure(tokens: LinkMatch[]) {\n if (tokens.length === 1) {\n return tokens[0].isLink;\n }\n\n if (tokens.length === 3 && tokens[1].isLink) {\n return [\"()\", \"[]\"].includes(tokens[0].value + tokens[2].value);\n }\n\n return false;\n}\n\ntype AutolinkOptions = {\n type: MarkType;\n defaultProtocol: string;\n validate: (url: string) => boolean;\n shouldAutoLink: (url: string) => boolean;\n};\n\n/**\n * Plugin that automatically adds link marks when typing URLs.\n */\nexport function autolink(options: AutolinkOptions): Plugin {\n return new Plugin({\n key: new PluginKey(\"autolink\"),\n appendTransaction: (transactions, oldState, newState) => {\n const docChanges =\n transactions.some((transaction) => transaction.docChanged) &&\n !oldState.doc.eq(newState.doc);\n\n const preventAutolink = transactions.some((transaction) =>\n transaction.getMeta(\"preventAutolink\"),\n );\n\n if (!docChanges || preventAutolink) {\n return;\n }\n\n const { tr } = newState;\n const transform = combineTransactionSteps(oldState.doc, [\n ...transactions,\n ]);\n const changes = getChangedRanges(transform);\n\n changes.forEach(({ newRange }) => {\n const nodesInChangedRanges = findChildrenInRange(\n newState.doc,\n newRange,\n (node) => node.isTextblock,\n );\n\n let textBlock: NodeWithPos | undefined;\n let textBeforeWhitespace: string | undefined;\n\n if (nodesInChangedRanges.length > 1) {\n textBlock = nodesInChangedRanges[0];\n textBeforeWhitespace = newState.doc.textBetween(\n textBlock.pos,\n textBlock.pos + textBlock.node.nodeSize,\n undefined,\n \" \",\n );\n } else if (nodesInChangedRanges.length) {\n const endText = newState.doc.textBetween(\n newRange.from,\n newRange.to,\n \" \",\n \" \",\n );\n if (!UNICODE_WHITESPACE_REGEX_END.test(endText)) {\n return;\n }\n textBlock = nodesInChangedRanges[0];\n textBeforeWhitespace = newState.doc.textBetween(\n textBlock.pos,\n newRange.to,\n undefined,\n \" \",\n );\n }\n\n if (textBlock && textBeforeWhitespace) {\n const wordsBeforeWhitespace = textBeforeWhitespace\n .split(UNICODE_WHITESPACE_REGEX)\n .filter(Boolean);\n\n if (wordsBeforeWhitespace.length <= 0) {\n return;\n }\n\n const lastWordBeforeSpace =\n wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1];\n const lastWordAndBlockOffset =\n textBlock.pos +\n textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace);\n\n if (!lastWordBeforeSpace) {\n return;\n }\n\n const linksBeforeSpace = tokenizeLink(\n lastWordBeforeSpace,\n options.defaultProtocol,\n );\n\n if (!isValidLinkStructure(linksBeforeSpace)) {\n return;\n }\n\n linksBeforeSpace\n .filter((link) => link.isLink)\n .map((link) => ({\n ...link,\n from: lastWordAndBlockOffset + link.start + 1,\n to: lastWordAndBlockOffset + link.end + 1,\n }))\n // ignore link inside code mark\n .filter((link) => {\n if (!newState.schema.marks.code) {\n return true;\n }\n\n return !newState.doc.rangeHasMark(\n link.from,\n link.to,\n newState.schema.marks.code,\n );\n })\n .filter((link) => options.validate(link.value))\n .filter((link) => options.shouldAutoLink(link.value))\n .forEach((link) => {\n if (\n getMarksBetween(link.from, link.to, newState.doc).some(\n (item) => item.mark.type === options.type,\n )\n ) {\n return;\n }\n\n tr.addMark(\n link.from,\n link.to,\n options.type.create({\n href: link.href,\n }),\n );\n });\n }\n });\n\n if (!tr.steps.length) {\n return;\n }\n\n return tr;\n },\n });\n}\n","import type { Editor } from \"@tiptap/core\";\nimport { getAttributes } from \"@tiptap/core\";\nimport type { MarkType } from \"@tiptap/pm/model\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport type { BlockNoteEditor } from \"../../../../editor/BlockNoteEditor.js\";\n\ntype ClickHandlerOptions = {\n type: MarkType;\n tiptapEditor: Editor;\n editor?: BlockNoteEditor<any, any, any>;\n onClick?: (\n event: MouseEvent,\n editor: BlockNoteEditor<any, any, any>,\n ) => boolean | void;\n};\n\nexport function clickHandler(options: ClickHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey(\"handleClickLink\"),\n props: {\n handleClick: (view, _pos, event) => {\n if (event.button !== 0) {\n return false;\n }\n\n if (!view.editable) {\n return false;\n }\n\n let link: HTMLAnchorElement | null = null;\n\n if (\n event.target instanceof HTMLAnchorElement &&\n // Differentiate between link inline content and read-only links.\n event.target.getAttribute(\"data-inline-content-type\") === \"link\"\n ) {\n link = event.target;\n } else {\n const target = event.target as HTMLElement | null;\n if (!target) {\n return false;\n }\n\n const root = options.tiptapEditor.view.dom;\n\n // Intentionally limit the lookup to the editor root.\n // Using tag names like DIV as boundaries breaks with custom NodeViews,\n link = target.closest<HTMLAnchorElement>(\n 'a[data-inline-content-type=\"link\"]',\n );\n\n if (link && !root.contains(link)) {\n link = null;\n }\n }\n\n if (!link) {\n return false;\n }\n\n if (options.onClick) {\n if (!options.editor) {\n throw new Error(\"BlockNoteEditor not found in Link click handler\");\n }\n const result = options.onClick(event, options.editor);\n return result ?? true;\n }\n\n const attrs = getAttributes(view.state, options.type.name);\n const href = link.href ?? attrs.href;\n const target = link.target ?? attrs.target;\n\n if (href) {\n window.open(href, target);\n return true;\n }\n\n return false;\n },\n },\n });\n}\n","import type { Editor } from \"@tiptap/core\";\nimport type { MarkType } from \"@tiptap/pm/model\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport { findLinks } from \"./linkDetector.js\";\n\ntype PasteHandlerOptions = {\n editor: Editor;\n defaultProtocol: string;\n type: MarkType;\n shouldAutoLink?: (url: string) => boolean;\n isValidLink: (href: string) => boolean;\n};\n\nexport function pasteHandler(options: PasteHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey(\"handlePasteLink\"),\n props: {\n handlePaste: (view, _event, slice) => {\n const { shouldAutoLink, isValidLink } = options;\n const { state } = view;\n const { selection } = state;\n const { empty } = selection;\n\n if (empty) {\n return false;\n }\n\n let textContent = \"\";\n\n slice.content.forEach((node) => {\n textContent += node.textContent;\n });\n\n const link = findLinks(textContent, {\n defaultProtocol: options.defaultProtocol,\n }).find((item) => item.isLink && item.value === textContent);\n\n if (\n !textContent ||\n !link ||\n !isValidLink(link.value) ||\n (shouldAutoLink !== undefined && !shouldAutoLink(link.value))\n ) {\n return false;\n }\n\n return options.editor.commands.setMark(options.type, {\n href: link.href,\n });\n },\n },\n });\n}\n","import type { PasteRuleMatch } from \"@tiptap/core\";\nimport { Mark, markPasteRule, mergeAttributes } from \"@tiptap/core\";\nimport type { Plugin } from \"@tiptap/pm/state\";\nimport type { BlockNoteEditor } from \"../../../editor/BlockNoteEditor.js\";\nimport { createExtension } from \"../../../editor/BlockNoteExtension.js\";\nimport { autolink } from \"./helpers/autolink.js\";\nimport { findLinks } from \"./helpers/linkDetector.js\";\nimport { clickHandler } from \"./helpers/clickHandler.js\";\nimport { pasteHandler } from \"./helpers/pasteHandler.js\";\nimport { UNICODE_WHITESPACE_REGEX_GLOBAL } from \"./helpers/whitespace.js\";\n\nconst DEFAULT_PROTOCOL = \"https\";\n\n// Pre-compiled regex for URI protocol validation.\n// Allows: http, https, ftp, ftps, mailto, tel, callto, sms, cid, xmpp\nconst ALLOWED_URI_REGEX =\n // eslint-disable-next-line no-useless-escape\n /^(?:(?:http|https|ftp|ftps|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z0-9+.\\-]+(?:[^a-z+.\\-:]|$))/i;\n\nexport function isAllowedUri(uri: string | undefined): boolean {\n if (!uri) {\n return true;\n }\n const cleaned = uri.replace(UNICODE_WHITESPACE_REGEX_GLOBAL, \"\");\n return ALLOWED_URI_REGEX.test(cleaned);\n}\n\n/**\n * Determine whether a detected URL should be auto-linked.\n * URLs with explicit protocols are always auto-linked.\n * Bare hostnames must have a TLD (no IP addresses or single words).\n */\nfunction shouldAutoLink(url: string): boolean {\n const hasProtocol = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(url);\n const hasMaybeProtocol = /^[a-z][a-z0-9+.-]*:/i.test(url);\n\n if (hasProtocol || (hasMaybeProtocol && !url.includes(\"@\"))) {\n return true;\n }\n // Strip userinfo (user:pass@) if present, then extract hostname\n const urlWithoutUserinfo = url.includes(\"@\") ? url.split(\"@\").pop()! : url;\n const hostname = urlWithoutUserinfo.split(/[/?#:]/)[0];\n\n // Don't auto-link IP addresses without protocol\n if (/^\\d{1,3}(\\.\\d{1,3}){3}$/.test(hostname)) {\n return false;\n }\n // Don't auto-link single-word hostnames without TLD (e.g., \"localhost\")\n if (!/\\./.test(hostname)) {\n return false;\n }\n return true;\n}\n\nexport type LinkOptions = {\n HTMLAttributes: Record<string, any>;\n editor?: BlockNoteEditor<any, any, any>;\n onClick?: (\n event: MouseEvent,\n editor: BlockNoteEditor<any, any, any>,\n ) => boolean | void;\n isValidLink: (href: string) => boolean;\n};\n\n/**\n * BlockNote Link mark extension.\n */\nexport const Link = Mark.create<LinkOptions>({\n name: \"link\",\n\n keepOnSplit: false,\n\n exitable: true,\n\n inclusive: false,\n\n addOptions() {\n return {\n HTMLAttributes: {\n target: \"_blank\",\n rel: \"noopener noreferrer nofollow\",\n className: \"bn-inline-content-section\",\n \"data-inline-content-type\": \"link\",\n },\n editor: undefined,\n onClick: undefined,\n isValidLink: isAllowedUri,\n };\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n parseHTML(element) {\n return element.getAttribute(\"href\");\n },\n },\n };\n },\n\n parseHTML() {\n const isValidLink = this.options.isValidLink;\n return [\n {\n tag: \"a[href]\",\n getAttrs: (dom) => {\n const href = (dom as HTMLElement).getAttribute(\"href\");\n if (!href || !isValidLink(href)) {\n return false;\n }\n return null;\n },\n },\n ];\n },\n\n renderHTML({ HTMLAttributes }) {\n if (!this.options.isValidLink(HTMLAttributes.href)) {\n return [\n \"a\",\n mergeAttributes(\n {\n ...HTMLAttributes,\n href: \"\",\n },\n this.options.HTMLAttributes,\n ),\n 0,\n ];\n }\n\n return [\n \"a\",\n mergeAttributes(HTMLAttributes, this.options.HTMLAttributes),\n 0,\n ];\n },\n\n addPasteRules() {\n const isValidLink = this.options.isValidLink;\n return [\n markPasteRule({\n find: (text) => {\n const foundLinks: PasteRuleMatch[] = [];\n\n if (text) {\n const links = findLinks(text, {\n defaultProtocol: DEFAULT_PROTOCOL,\n }).filter((item) => item.isLink && isValidLink(item.value));\n\n for (const link of links) {\n if (!shouldAutoLink(link.value)) {\n continue;\n }\n\n foundLinks.push({\n text: link.value,\n data: { href: link.href },\n index: link.start,\n });\n }\n }\n\n return foundLinks;\n },\n type: this.type,\n getAttributes: (match) => ({\n href: match.data?.href,\n }),\n }),\n ];\n },\n\n addProseMirrorPlugins() {\n const plugins: Plugin[] = [];\n\n plugins.push(\n autolink({\n type: this.type,\n defaultProtocol: DEFAULT_PROTOCOL,\n validate: this.options.isValidLink,\n shouldAutoLink,\n }),\n );\n\n plugins.push(\n clickHandler({\n type: this.type,\n tiptapEditor: this.editor,\n editor: this.options.editor,\n onClick: this.options.onClick,\n }),\n );\n\n plugins.push(\n pasteHandler({\n editor: this.editor,\n defaultProtocol: DEFAULT_PROTOCOL,\n type: this.type,\n shouldAutoLink,\n isValidLink: this.options.isValidLink,\n }),\n );\n\n return plugins;\n },\n});\n\ntype LinkExtensionOptions = {\n HTMLAttributes?: Record<string, any>;\n onClick?: (\n event: MouseEvent,\n editor: BlockNoteEditor<any, any, any>,\n ) => boolean | void;\n isValidLink?: (href: string) => boolean;\n};\n\n/**\n * BlockNote extension wrapping the {@link Link} TipTap mark. Wrapping the mark\n * lets other extensions order their click handlers relative to the link click\n * handler via `runsBefore: [\"link\"]`.\n */\nexport const LinkExtension = createExtension<any, LinkExtensionOptions>(\n ({ editor, options }) => {\n return {\n key: \"link\",\n tiptapExtensions: [\n Link.configure({\n HTMLAttributes: options.HTMLAttributes ?? {},\n editor,\n onClick: options.onClick,\n ...(options.isValidLink ? { isValidLink: options.isValidLink } : {}),\n }),\n ],\n } as const;\n },\n);\n","import { Node } from \"@tiptap/core\";\n\nimport type { BlockNoteEditor } from \"../editor/BlockNoteEditor.js\";\nimport { BlockNoteDOMAttributes } from \"../schema/index.js\";\nimport { mergeCSSClasses } from \"../util/browser.js\";\nimport { suggestionMarks } from \"./suggestionMarks.js\";\n\n// Object containing all possible block attributes.\nconst BlockAttributes: Record<string, string> = {\n blockColor: \"data-block-color\",\n blockStyle: \"data-block-style\",\n id: \"data-id\",\n depth: \"data-depth\",\n depthChange: \"data-depth-change\",\n};\n\n/**\n * The main \"Block node\" documents consist of\n */\nexport const BlockContainer = Node.create<{\n domAttributes?: BlockNoteDOMAttributes;\n editor: BlockNoteEditor<any, any, any>;\n}>({\n name: \"blockContainer\",\n group: \"blockGroupChild bnBlock\",\n // A block always contains content, and optionally a blockGroup which contains nested blocks\n content: \"blockContent blockGroup?\",\n // Ensures content-specific keyboard handlers trigger first.\n priority: 50,\n defining: true,\n marks() {\n return suggestionMarks(this.editor);\n },\n parseHTML() {\n return [\n {\n tag: \"div[data-node-type=\" + this.name + \"]\",\n getAttrs: (element) => {\n if (typeof element === \"string\") {\n return false;\n }\n\n const attrs: Record<string, string> = {};\n for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {\n if (element.getAttribute(HTMLAttr)) {\n attrs[nodeAttr] = element.getAttribute(HTMLAttr)!;\n }\n }\n\n return attrs;\n },\n },\n // Ignore `blockOuter` divs, but parse the `blockContainer` divs inside them.\n {\n tag: `div[data-node-type=\"blockOuter\"]`,\n skip: true,\n },\n ];\n },\n\n renderHTML({ HTMLAttributes }) {\n const blockOuter = document.createElement(\"div\");\n blockOuter.className = \"bn-block-outer\";\n blockOuter.setAttribute(\"data-node-type\", \"blockOuter\");\n for (const [attribute, value] of Object.entries(HTMLAttributes)) {\n if (attribute !== \"class\") {\n blockOuter.setAttribute(attribute, value);\n }\n }\n\n const blockHTMLAttributes = {\n ...(this.options.domAttributes?.block || {}),\n ...HTMLAttributes,\n };\n const block = document.createElement(\"div\");\n block.className = mergeCSSClasses(\"bn-block\", blockHTMLAttributes.class);\n block.setAttribute(\"data-node-type\", this.name);\n for (const [attribute, value] of Object.entries(blockHTMLAttributes)) {\n if (attribute !== \"class\") {\n block.setAttribute(attribute, value);\n }\n }\n\n blockOuter.appendChild(block);\n\n return {\n dom: blockOuter,\n contentDOM: block,\n };\n },\n});\n","import { Node } from \"@tiptap/core\";\nimport { BlockNoteDOMAttributes } from \"../schema/index.js\";\nimport { mergeCSSClasses } from \"../util/browser.js\";\nimport { suggestionMarks } from \"./suggestionMarks.js\";\n\nexport const BlockGroup = Node.create<{\n domAttributes?: BlockNoteDOMAttributes;\n}>({\n name: \"blockGroup\",\n group: \"childContainer\",\n content: \"blockGroupChild+\",\n marks() {\n return suggestionMarks(this.editor);\n },\n parseHTML() {\n return [\n {\n tag: \"div\",\n getAttrs: (element) => {\n if (typeof element === \"string\") {\n return false;\n }\n\n if (element.getAttribute(\"data-node-type\") === \"blockGroup\") {\n // Null means the element matches, but we don't want to add any attributes to the node.\n return null;\n }\n\n return false;\n },\n },\n ];\n },\n\n renderHTML({ HTMLAttributes }) {\n const blockGroupHTMLAttributes = {\n ...(this.options.domAttributes?.blockGroup || {}),\n ...HTMLAttributes,\n };\n const blockGroup = document.createElement(\"div\");\n blockGroup.className = mergeCSSClasses(\n \"bn-block-group\",\n blockGroupHTMLAttributes.class,\n );\n blockGroup.setAttribute(\"data-node-type\", \"blockGroup\");\n for (const [attribute, value] of Object.entries(blockGroupHTMLAttributes)) {\n if (attribute !== \"class\") {\n blockGroup.setAttribute(attribute, value);\n }\n }\n\n return {\n dom: blockGroup,\n contentDOM: blockGroup,\n };\n },\n});\n","import { Node } from \"@tiptap/core\";\nimport { suggestionMarks } from \"./suggestionMarks.js\";\n\nexport const Doc = Node.create({\n name: \"doc\",\n topNode: true,\n content: \"blockGroup\",\n marks() {\n return suggestionMarks(this.editor);\n },\n});\n","import {\n AnyExtension as AnyTiptapExtension,\n extensions,\n Node,\n Extension as TiptapExtension,\n} from \"@tiptap/core\";\nimport { Text } from \"@tiptap/extension-text\";\nimport { Gapcursor } from \"@tiptap/extensions/gap-cursor\";\nimport { createDropFileExtension } from \"../../../api/clipboard/fromClipboard/fileDropExtension.js\";\nimport { createPasteFromClipboardExtension } from \"../../../api/clipboard/fromClipboard/pasteExtension.js\";\nimport { createCopyToClipboardExtension } from \"../../../api/clipboard/toClipboard/copyExtension.js\";\nimport {\n BlockChangeExtension,\n DropCursorExtension,\n FilePanelExtension,\n FormattingToolbarExtension,\n HistoryExtension,\n InlineContentBoundaryEditExtension,\n LinkToolbarExtension,\n NodeSelectionKeyboardExtension,\n PlaceholderExtension,\n PositionMappingExtension,\n PreviousBlockTypeExtension,\n ShowSelectionExtension,\n SideMenuExtension,\n SourceBlockWithPreviewExtension,\n SourceInlineContentWithPreviewExtension,\n SuggestionMenu,\n TableHandlesExtension,\n TrailingNodeExtension,\n} from \"../../../extensions/index.js\";\nimport {\n BackgroundColorExtension,\n HardBreak,\n KeyboardShortcutsExtension,\n LinkExtension,\n TextAlignmentExtension,\n TextColorExtension,\n UniqueID,\n} from \"../../../extensions/tiptap-extensions/index.js\";\nimport { BlockContainer, BlockGroup, Doc } from \"../../../pm-nodes/index.js\";\nimport type {\n BlockNoteEditor,\n BlockNoteEditorOptions,\n} from \"../../BlockNoteEditor.js\";\nimport type { ExtensionFactoryInstance } from \"../../BlockNoteExtension.js\";\n\n/**\n * Get all the Tiptap extensions BlockNote is configured with by default\n */\nexport function getDefaultTiptapExtensions(\n editor: BlockNoteEditor<any, any, any>,\n options: BlockNoteEditorOptions<any, any, any>,\n) {\n const tiptapExtensions: AnyTiptapExtension[] = [\n extensions.ClipboardTextSerializer,\n extensions.Commands,\n extensions.Editable,\n extensions.FocusEvents,\n extensions.Tabindex,\n Gapcursor,\n\n UniqueID.configure({\n // everything from bnBlock group (nodes that represent a BlockNote block should have an id)\n types: [\"blockContainer\", \"columnList\", \"column\"],\n setIdAttribute: options.setIdAttribute,\n isWithinEditor: editor.isWithinEditor,\n }),\n HardBreak,\n Text,\n\n // marks:\n ...(Object.values(editor.schema.styleSpecs).map((styleSpec) => {\n return styleSpec.implementation.mark.configure({\n editor: editor,\n });\n }) as any[]),\n\n TextColorExtension,\n\n BackgroundColorExtension,\n TextAlignmentExtension,\n\n // make sure escape blurs editor, so that we can tab to other elements in the host page (accessibility)\n TiptapExtension.create({\n name: \"OverrideEscape\",\n addKeyboardShortcuts: () => {\n return {\n Escape: () => {\n if (editor.getExtension(SuggestionMenu)?.shown()) {\n // escape should close the suggestion menu, but not blur the editor\n return false;\n }\n editor.blur();\n return true;\n },\n };\n },\n }),\n\n // nodes\n Doc,\n BlockContainer.configure({\n editor: editor,\n domAttributes: options.domAttributes,\n }),\n KeyboardShortcutsExtension.configure({\n editor: editor,\n tabBehavior: options.tabBehavior,\n }),\n BlockGroup.configure({\n domAttributes: options.domAttributes,\n }),\n ...Object.values(editor.schema.inlineContentSpecs)\n .filter((a) => a.config !== \"link\" && a.config !== \"text\")\n .map((inlineContentSpec) => {\n return inlineContentSpec.implementation!.node.configure({\n editor: editor,\n });\n }),\n\n ...Object.values(editor.schema.blockSpecs).flatMap((blockSpec) => {\n return [\n // the node extension implementations\n ...(\"node\" in blockSpec.implementation\n ? [\n (blockSpec.implementation.node as Node).configure({\n editor: editor,\n domAttributes: options.domAttributes,\n }),\n ]\n : []),\n ];\n }),\n createCopyToClipboardExtension(editor),\n createPasteFromClipboardExtension(\n editor,\n options.pasteHandler ||\n ((context: {\n defaultPasteHandler: (context?: {\n prioritizeMarkdownOverHTML?: boolean;\n plainTextAsMarkdown?: boolean;\n }) => boolean | undefined;\n }) => context.defaultPasteHandler()),\n ),\n createDropFileExtension(editor),\n ];\n\n return tiptapExtensions;\n}\n\nexport function getDefaultExtensions(\n editor: BlockNoteEditor<any, any, any>,\n options: BlockNoteEditorOptions<any, any, any>,\n) {\n const extensions = [\n BlockChangeExtension(),\n DropCursorExtension(options),\n FilePanelExtension(options),\n FormattingToolbarExtension(options),\n LinkExtension({\n HTMLAttributes: options.links?.HTMLAttributes ?? {},\n onClick: options.links?.onClick,\n ...(options.links?.isValidLink\n ? { isValidLink: options.links.isValidLink }\n : {}),\n }),\n LinkToolbarExtension(options),\n NodeSelectionKeyboardExtension(),\n PlaceholderExtension(options),\n ShowSelectionExtension(options),\n SideMenuExtension(options),\n SourceBlockWithPreviewExtension(),\n SourceInlineContentWithPreviewExtension(),\n SuggestionMenu(options),\n HistoryExtension(),\n InlineContentBoundaryEditExtension(),\n PositionMappingExtension(),\n ...(options.trailingBlock !== false ? [TrailingNodeExtension()] : []),\n ] as ExtensionFactoryInstance[];\n\n if (\"table\" in editor.schema.blockSpecs) {\n extensions.push(TableHandlesExtension(options));\n }\n\n if (options.animations !== false) {\n extensions.push(PreviousBlockTypeExtension());\n }\n\n return extensions;\n}\n","import {\n InputRule,\n inputRules as inputRulesPlugin,\n} from \"@handlewithcare/prosemirror-inputrules\";\nimport {\n AnyExtension as AnyTiptapExtension,\n Extension as TiptapExtension,\n} from \"@tiptap/core\";\nimport { keymap } from \"@tiptap/pm/keymap\";\nimport { Plugin, TextSelection } from \"prosemirror-state\";\nimport { updateBlockTr } from \"../../../api/blockManipulation/commands/updateBlock/updateBlock.js\";\nimport { setTextCursorPosition } from \"../../../api/blockManipulation/selections/textCursorPosition.js\";\nimport {\n getBlockInfoFromSelection,\n getNodeId,\n} from \"../../../api/getBlockInfoFromPos.js\";\nimport { sortByDependencies } from \"../../../util/topo-sort.js\";\nimport type {\n BlockNoteEditor,\n BlockNoteEditorOptions,\n} from \"../../BlockNoteEditor.js\";\nimport type {\n Extension,\n ExtensionFactoryInstance,\n ExtensionFactory,\n} from \"../../BlockNoteExtension.js\";\nimport { originalFactorySymbol } from \"./symbol.js\";\nimport {\n getDefaultExtensions,\n getDefaultTiptapExtensions,\n} from \"./extensions.js\";\n\nexport class ExtensionManager {\n /**\n * A set of extension keys which are disabled by the options\n */\n private disabledExtensions = new Set<string>();\n /**\n * A list of all the extensions that are registered to the editor\n */\n private extensions: Extension[] = [];\n /**\n * A map of all the abort controllers for each extension that has an init method defined\n */\n private abortMap = new Map<Extension, AbortController>();\n /**\n * A map of all the extension factories that are registered to the editor\n */\n private extensionFactories = new Map<ExtensionFactory, Extension>();\n /**\n * Because a single blocknote extension can both have it's own prosemirror plugins & additional generated ones (e.g. keymap & input rules plugins)\n * We need to keep track of all the plugins for each extension, so that we can remove them when the extension is unregistered\n */\n private extensionPlugins: Map<Extension, Plugin[]> = new Map();\n /**\n * Maps an extension key to the set of extension keys that declared it as a\n * dependency via `blockNoteExtensions`. A sub-extension is a dependency of\n * the extension that declares it, so it must run *before* its parent(s).\n */\n private blockNoteExtensionDependents: Map<string, Set<string>> = new Map();\n\n constructor(\n private editor: BlockNoteEditor<any, any, any>,\n private options: BlockNoteEditorOptions<any, any, any>,\n ) {\n /**\n * When the editor is first mounted, we need to initialize all the extensions\n */\n editor.onMount(() => {\n for (const extension of this.extensions) {\n // If the extension has an init function, we can initialize it, otherwise, it is already added to the editor\n if (extension.mount) {\n // We create an abort controller for each extension, so that we can abort the extension when the editor is unmounted\n const abortController = new window.AbortController();\n const unmountCallback = extension.mount({\n dom: editor.prosemirrorView.dom,\n root: editor.prosemirrorView.root,\n signal: abortController.signal,\n });\n // If the extension returns a method to unmount it, we can register it to be called when the abort controller is aborted\n if (unmountCallback) {\n abortController.signal.addEventListener(\"abort\", () => {\n unmountCallback();\n });\n }\n // Keep track of the abort controller for each extension, so that we can abort it when the editor is unmounted\n this.abortMap.set(extension, abortController);\n }\n }\n });\n\n /**\n * When the editor is unmounted, we need to abort all the extensions' abort controllers\n */\n editor.onUnmount(() => {\n for (const [extension, abortController] of this.abortMap.entries()) {\n // No longer track the abort controller for this extension\n this.abortMap.delete(extension);\n // Abort each extension's abort controller\n abortController.abort();\n }\n });\n\n // TODO do disabled extensions need to be only for editor base extensions? Or all of them?\n this.disabledExtensions = new Set(options.disableExtensions || []);\n\n // Add the default extensions\n for (const extension of getDefaultExtensions(this.editor, this.options)) {\n this.addExtension(extension);\n }\n\n // Add the extensions from the options\n for (const extension of this.options.extensions ?? []) {\n this.addExtension(extension);\n }\n\n // Add the extensions from blocks specs\n for (const block of Object.values(this.editor.schema.blockSpecs)) {\n for (const extension of block.extensions ?? []) {\n this.addExtension(extension);\n }\n }\n }\n\n /**\n * Register one or more extensions to the editor after the editor is initialized.\n *\n * This allows users to switch on & off extensions \"at runtime\".\n */\n public registerExtension(\n extension:\n | Extension\n | ExtensionFactoryInstance\n | (Extension | ExtensionFactoryInstance)[],\n ): void {\n this.replaceExtension(undefined, extension);\n }\n\n /**\n * Register an extension to the editor\n * @param extension - The extension to register\n * @returns The extension instance\n */\n private addExtension(\n extension: Extension | ExtensionFactoryInstance,\n /**\n * When this extension is being added as a dependency declared in another\n * extension's `blockNoteExtensions`, this is the key of that declaring\n * (parent) extension.\n */\n parentKey?: string,\n ): Extension | undefined {\n let instance: Extension;\n if (typeof extension === \"function\") {\n instance = extension({ editor: this.editor });\n } else {\n instance = extension;\n }\n\n if (!instance || this.disabledExtensions.has(instance.key)) {\n return undefined as any;\n }\n\n // A sub-extension declared via `blockNoteExtensions` must run before the\n // extension that declares it. We record this dependency before the\n // de-duplication check below, so that it applies even when multiple\n // extensions declare the same sub-extension (and all but the first are\n // de-duplicated).\n if (parentKey) {\n let dependents = this.blockNoteExtensionDependents.get(instance.key);\n if (!dependents) {\n dependents = new Set();\n this.blockNoteExtensionDependents.set(instance.key, dependents);\n }\n dependents.add(parentKey);\n }\n\n // De-duplicate by key: if an extension with the same key is already\n // registered, don't register it again. This allows an extension to declare\n // a dependency on another extension via `blockNoteExtensions` without\n // conflicting when the user (or another extension) registers that same\n // extension directly. The first registration wins.\n if (this.extensions.some((e) => e.key === instance.key)) {\n return undefined as any;\n }\n\n // Now that we know that the extension is not disabled, we can add it to the extension factories\n if (typeof extension === \"function\") {\n const originalFactory = (instance as any)[originalFactorySymbol] as (\n ...args: any[]\n ) => ExtensionFactoryInstance;\n\n if (typeof originalFactory === \"function\") {\n this.extensionFactories.set(originalFactory, instance);\n }\n }\n\n this.extensions.push(instance);\n\n if (instance.blockNoteExtensions) {\n for (const subExtension of instance.blockNoteExtensions) {\n this.addExtension(subExtension, instance.key);\n }\n }\n\n return instance as any;\n }\n\n /**\n * Resolve an extension or a list of extensions into a list of extension instances\n * @param toResolve - The extension or list of extensions to resolve\n * @returns A list of extension instances\n */\n private resolveExtensions(\n toResolve:\n | undefined\n | string\n | Extension\n | ExtensionFactory\n | (Extension | ExtensionFactory | string | undefined)[],\n ): Extension[] {\n const extensions = [] as Extension[];\n if (typeof toResolve === \"function\") {\n const instance = this.extensionFactories.get(toResolve);\n if (instance) {\n extensions.push(instance);\n }\n } else if (Array.isArray(toResolve)) {\n for (const extension of toResolve) {\n extensions.push(...this.resolveExtensions(extension));\n }\n } else if (typeof toResolve === \"object\" && \"key\" in toResolve) {\n extensions.push(toResolve);\n } else if (typeof toResolve === \"string\") {\n const instance = this.extensions.find((e) => e.key === toResolve);\n if (instance) {\n extensions.push(instance);\n }\n }\n return extensions;\n }\n\n /**\n * Unregister an extension from the editor\n * @param toUnregister - The extension to unregister\n * @returns void\n */\n public unregisterExtension(\n toUnregister:\n | undefined\n | string\n | Extension\n | ExtensionFactory\n | (Extension | ExtensionFactory | string | undefined)[],\n ): void {\n this.replaceExtension(toUnregister, []);\n }\n\n /**\n * Atomically replace extension instances in the editor.\n * @param toUnregister - The extensions to unregister, can be a string key, an extension instance, an extension factory, or an array of any of those\n * @param toRegister - The extensions to register, can be an extension instance, an extension factory, or an array of any of those\n * @returns void\n */\n public replaceExtension(\n toUnregister:\n | undefined\n | string\n | Extension\n | ExtensionFactory\n | (Extension | ExtensionFactory | string | undefined)[],\n toRegister:\n | Extension\n | ExtensionFactoryInstance\n | (Extension | ExtensionFactoryInstance)[],\n ): void {\n // ---- Remove phase (no updatePlugins call) ----\n const extensionsToRemove = this.resolveExtensions(toUnregister);\n\n if (toUnregister && !extensionsToRemove.length) {\n // eslint-disable-next-line no-console\n console.warn(`No extensions found to unregister`, toUnregister);\n }\n\n let didWarnUnregister = false;\n // We collect both plugin references and plugin keys to remove.\n // Key-based matching is needed because re-entrant dispatches (e.g. from\n // y-prosemirror view hooks) can replace plugin instances in the ProseMirror\n // state with new objects that share the same key, making reference-based\n // matching unreliable.\n const pluginRefsToRemove = new Set<Plugin>();\n const pluginKeysToRemove = new Set<string>();\n for (const extension of extensionsToRemove) {\n this.extensions = this.extensions.filter((e) => e !== extension);\n this.extensionFactories.forEach((instance, factory) => {\n if (instance === extension) {\n this.extensionFactories.delete(factory);\n }\n });\n this.abortMap.get(extension)?.abort();\n this.abortMap.delete(extension);\n\n const plugins = this.extensionPlugins.get(extension);\n plugins?.forEach((plugin) => {\n pluginRefsToRemove.add(plugin);\n const key = (plugin as any).spec?.key;\n const keyStr = typeof key === \"object\" && key ? key.key : key;\n if (typeof keyStr === \"string\") {\n pluginKeysToRemove.add(keyStr);\n }\n });\n this.extensionPlugins.delete(extension);\n\n if (extension.tiptapExtensions && !didWarnUnregister) {\n didWarnUnregister = true;\n // eslint-disable-next-line no-console\n console.warn(\n `Extension ${extension.key} has tiptap extensions, but they will not be removed. Please separate the extension into multiple extensions if you want to remove them, or re-initialize the editor.`,\n toUnregister,\n );\n }\n }\n\n // ---- Add phase (no updatePlugins call) ----\n const newExtensions = ([] as (Extension | ExtensionFactoryInstance)[])\n .concat(toRegister)\n .filter(Boolean) as (Extension | ExtensionFactoryInstance)[];\n\n const registeredExtensions = newExtensions\n .map((ext) => this.addExtension(ext))\n .filter(Boolean) as Extension[];\n\n const pluginsToAdd: Plugin[] = [];\n for (const extension of registeredExtensions) {\n if (extension?.tiptapExtensions) {\n // eslint-disable-next-line no-console\n console.warn(\n `Extension ${extension.key} has tiptap extensions, but these cannot be changed after initializing the editor. Please separate the extension into multiple extensions if you want to add them, or re-initialize the editor.`,\n extension,\n );\n }\n\n if (extension?.inputRules?.length) {\n // eslint-disable-next-line no-console\n console.warn(\n `Extension ${extension.key} has input rules, but these cannot be changed after initializing the editor. Please separate the extension into multiple extensions if you want to add them, or re-initialize the editor.`,\n extension,\n );\n }\n\n this.getProsemirrorPluginsFromExtension(extension).plugins.forEach(\n (plugin) => {\n pluginsToAdd.push(plugin);\n },\n );\n }\n\n // Nothing to do\n if (\n !pluginRefsToRemove.size &&\n !pluginKeysToRemove.size &&\n !pluginsToAdd.length\n ) {\n return;\n }\n\n // ---- Single atomic plugin update ----\n this.updatePlugins((plugins) => [\n ...plugins.filter((plugin) => {\n // Fast path: exact reference match\n if (pluginRefsToRemove.has(plugin)) {\n return false;\n }\n // Fallback: match by key string (handles cases where plugin instances\n // in the state differ from the ones we tracked)\n if (pluginKeysToRemove.size) {\n const key = (plugin as any).spec?.key;\n const keyStr = typeof key === \"object\" && key ? key.key : key;\n if (typeof keyStr === \"string\" && pluginKeysToRemove.has(keyStr)) {\n return false;\n }\n }\n return true;\n }),\n ...pluginsToAdd,\n ]);\n }\n\n /**\n * Allows resetting the current prosemirror state's plugins\n * @param update - A function that takes the current plugins and returns the new plugins\n * @returns void\n */\n private updatePlugins(update: (plugins: Plugin[]) => Plugin[]): void {\n const currentState = this.editor.prosemirrorState;\n\n const state = currentState.reconfigure({\n plugins: update(currentState.plugins.slice()),\n });\n\n this.editor.prosemirrorView.updateState(state);\n }\n\n /**\n * Get all the extensions that are registered to the editor\n */\n public getTiptapExtensions(): AnyTiptapExtension[] {\n // Start with the default tiptap extensions\n const tiptapExtensions = getDefaultTiptapExtensions(\n this.editor,\n this.options,\n ).filter((extension) => !this.disabledExtensions.has(extension.name));\n\n const getPriority = sortByDependencies(\n this.extensions.map((extension) => {\n // A sub-extension declared via `blockNoteExtensions` must run before the\n // extension(s) that declared it, so we merge those parents into its\n // `runsBefore`.\n const dependents = this.blockNoteExtensionDependents.get(extension.key);\n if (!dependents?.size) {\n return extension;\n }\n return {\n key: extension.key,\n runsBefore: [...(extension.runsBefore ?? []), ...dependents],\n };\n }),\n );\n\n const inputRulesByPriority = new Map<number, InputRule[]>();\n for (const extension of this.extensions) {\n if (extension.tiptapExtensions) {\n tiptapExtensions.push(...extension.tiptapExtensions);\n }\n\n const priority = getPriority(extension.key);\n\n const { plugins: prosemirrorPlugins, inputRules } =\n this.getProsemirrorPluginsFromExtension(extension);\n // Sometimes a blocknote extension might need to make additional prosemirror plugins, so we generate them here\n if (prosemirrorPlugins.length) {\n tiptapExtensions.push(\n TiptapExtension.create({\n name: extension.key,\n priority,\n addProseMirrorPlugins: () => prosemirrorPlugins,\n }),\n );\n }\n if (inputRules.length) {\n if (!inputRulesByPriority.has(priority)) {\n inputRulesByPriority.set(priority, []);\n }\n inputRulesByPriority.get(priority)!.push(...inputRules);\n }\n }\n\n // Collect all input rules into 1 extension to reduce conflicts\n tiptapExtensions.push(\n TiptapExtension.create({\n name: \"blocknote-input-rules\",\n addProseMirrorPlugins() {\n const rules = [] as InputRule[];\n Array.from(inputRulesByPriority.keys())\n // We sort the rules by their priority (the key)\n .sort((a, b) => a - b)\n .reverse()\n .forEach((priority) => {\n // Append in reverse priority order\n rules.push(...inputRulesByPriority.get(priority)!);\n });\n const inputRules = inputRulesPlugin({ rules });\n // Sidecar plugin: triggers the same input rules on Enter by\n // delegating to the inputRules plugin's handleTextInput with a\n // synthetic \"\\n\" insertion. The handlewithcare regex `\\s$` already\n // matches `\\n`, so any rule that fires on space fires on Enter too.\n // We call its handleTextInput directly (rather than via\n // view.someProp) so other plugins don't observe the synthetic input,\n // and so the rule's undo metadata is keyed to the same plugin\n // instance that Tiptap's `commands.undoInputRule` reads from.\n const inputRulesEnter = new Plugin({\n props: {\n handleKeyDown(view, event) {\n if (event.key !== \"Enter\") {\n return false;\n }\n // Only trigger on plain Enter — modifier combos like\n // Shift/Cmd/Ctrl/Alt+Enter are reserved for other handlers\n // (e.g. soft-break, submit) and should fall through.\n if (\n event.shiftKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey\n ) {\n return false;\n }\n const { $cursor } = view.state.selection as TextSelection;\n if (!$cursor) {\n return false;\n }\n return !!inputRules.props.handleTextInput?.call(\n inputRules,\n view,\n $cursor.pos,\n $cursor.pos,\n \"\\n\",\n () =>\n view.state.tr.insertText(\"\\n\", $cursor.pos, $cursor.pos),\n );\n },\n },\n });\n return [inputRules, inputRulesEnter];\n },\n }),\n );\n\n // Add any tiptap extensions from the `_tiptapOptions`\n for (const extension of this.options._tiptapOptions?.extensions ?? []) {\n tiptapExtensions.push(extension);\n }\n\n return tiptapExtensions;\n }\n\n /**\n * This maps a blocknote extension into an array of Prosemirror plugins if it has any of the following:\n * - plugins\n * - keyboard shortcuts\n * - input rules\n */\n private getProsemirrorPluginsFromExtension(extension: Extension): {\n plugins: Plugin[];\n inputRules: InputRule[];\n } {\n const plugins: Plugin[] = [...(extension.prosemirrorPlugins ?? [])];\n const inputRules: InputRule[] = [];\n if (\n !extension.prosemirrorPlugins?.length &&\n !Object.keys(extension.keyboardShortcuts || {}).length &&\n !extension.inputRules?.length\n ) {\n // We can bail out early if the extension has no features to add to the tiptap editor\n return { plugins, inputRules };\n }\n\n this.extensionPlugins.set(extension, plugins);\n\n if (extension.inputRules?.length) {\n inputRules.push(\n ...extension.inputRules.map((inputRule) => {\n return new InputRule(\n inputRule.find,\n (state, match, start, end) => {\n const replaceWith = inputRule.replace({\n match,\n range: { from: start, to: end },\n editor: this.editor,\n });\n if (replaceWith) {\n const tr = state.tr;\n const blockInfo = getBlockInfoFromSelection(tr);\n\n if (\n !blockInfo.isBlockContainer ||\n this.editor.schema.blockSchema[blockInfo.blockNoteType]\n ?.content !== \"inline\"\n ) {\n return null;\n }\n\n tr.deleteRange(start, end);\n updateBlockTr(tr, blockInfo.bnBlock.beforePos, replaceWith);\n // updateBlockTr's replaceWith path leaves the selection after\n // the new block when the content is replaced wholesale (e.g.\n // when the rule returns content: []). Move the cursor back\n // inside the new block so the user can keep typing.\n setTextCursorPosition(\n tr,\n getNodeId(blockInfo.bnBlock.node, tr.doc),\n \"start\",\n );\n return tr;\n }\n return null;\n },\n { undoable: true },\n );\n }),\n );\n }\n\n if (Object.keys(extension.keyboardShortcuts || {}).length) {\n plugins.push(\n keymap(\n Object.fromEntries(\n Object.entries(extension.keyboardShortcuts!).map(([key, value]) => [\n key,\n () => value({ editor: this.editor }),\n ]),\n ),\n ),\n );\n }\n\n return { plugins, inputRules };\n }\n\n /**\n * Get all extensions\n */\n public getExtensions(): Map<string, Extension> {\n return new Map(\n this.extensions.map((extension) => [extension.key, extension]),\n );\n }\n\n /**\n * Get a specific extension by it's instance\n */\n public getExtension<\n const Ext extends Extension | ExtensionFactory = Extension,\n >(\n extension: string,\n ):\n | (Ext extends Extension\n ? Ext\n : Ext extends ExtensionFactory\n ? ReturnType<ReturnType<Ext>>\n : never)\n | undefined;\n public getExtension<const T extends ExtensionFactory>(\n extension: T,\n ): ReturnType<ReturnType<T>> | undefined;\n public getExtension<const T extends ExtensionFactory | string = string>(\n extension: T,\n ):\n | (T extends ExtensionFactory\n ? ReturnType<ReturnType<T>>\n : T extends string\n ? Extension\n : never)\n | undefined {\n if (typeof extension === \"string\") {\n const instance = this.extensions.find((e) => e.key === extension);\n if (!instance) {\n return undefined;\n }\n return instance as any;\n } else if (typeof extension === \"function\") {\n const instance = this.extensionFactories.get(extension);\n if (!instance) {\n return undefined;\n }\n return instance as any;\n }\n throw new Error(`Invalid extension type: ${typeof extension}`);\n }\n\n /**\n * Check if an extension exists\n */\n public hasExtension(key: string | Extension | ExtensionFactory): boolean {\n if (typeof key === \"string\") {\n return this.extensions.some((e) => e.key === key);\n } else if (typeof key === \"object\" && \"key\" in key) {\n return this.extensions.some((e) => e.key === key.key);\n } else if (typeof key === \"function\") {\n return this.extensionFactories.has(key);\n }\n return false;\n }\n}\n","import type { Node, ResolvedPos } from \"prosemirror-model\";\n\n/**\n * Expands a range (start to end) to include the rest of the word if it starts or ends within a word\n */\nexport function expandPMRangeToWords(\n doc: Node,\n range: { $from: ResolvedPos; $to: ResolvedPos },\n) {\n let { $from, $to } = range;\n\n // Expand Start\n // If the selection starts with a word character or punctuation, check if we need to expand left to include the rest of the word\n if ($from.pos > $from.start() && $from.pos < doc.content.size) {\n const charAfterStart = doc.textBetween($from.pos, $from.pos + 1);\n if (/^[\\w\\p{P}]$/u.test(charAfterStart)) {\n const textBefore = doc.textBetween($from.start(), $from.pos);\n const wordMatch = textBefore.match(/[\\w\\p{P}]+$/u);\n if (wordMatch) {\n $from = doc.resolve($from.pos - wordMatch[0].length);\n }\n }\n }\n\n // Expand End\n // If the selection ends with a word characte or punctuation, check if we need to expand right to include the rest of the word\n if ($to.pos < $to.end() && $to.pos > 0) {\n const charBeforeEnd = doc.textBetween($to.pos - 1, $to.pos);\n if (/^[\\w\\p{P}]$/u.test(charBeforeEnd)) {\n const textAfter = doc.textBetween($to.pos, $to.end());\n const wordMatch = textAfter.match(/^[\\w\\p{P}]+/u);\n if (wordMatch) {\n $to = doc.resolve($to.pos + wordMatch[0].length);\n }\n }\n }\n return { $from, $to, from: $from.pos, to: $to.pos };\n}\n","import { TextSelection, type Transaction } from \"prosemirror-state\";\nimport { TableMap } from \"prosemirror-tables\";\nimport { Block } from \"../../../blocks/defaultBlocks.js\";\nimport { Selection } from \"../../../editor/selectionTypes.js\";\nimport {\n BlockIdentifier,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../../schema/index.js\";\nimport { expandPMRangeToWords } from \"../../../util/expandToWords.js\";\nimport { getBlockInfo, getNearestBlockPos } from \"../../getBlockInfoFromPos.js\";\nimport {\n nodeToBlock,\n prosemirrorSliceToSlicedBlocks,\n} from \"../../nodeConversions/nodeToBlock.js\";\nimport { getNodeById } from \"../../nodeUtil.js\";\nimport { getBlockNoteSchema, getPmSchema } from \"../../pmUtil.js\";\n\nexport function getSelection<\n BSchema extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(tr: Transaction): Selection<BSchema, I, S> | undefined {\n // Return undefined if the selection is collapsed or a node is selected.\n if (tr.selection.empty || \"node\" in tr.selection) {\n return undefined;\n }\n\n const $startBlockBeforePos = tr.doc.resolve(\n getNearestBlockPos(tr.doc, tr.selection.from).posBeforeNode,\n );\n const $endBlockBeforePos = tr.doc.resolve(\n getNearestBlockPos(tr.doc, tr.selection.to).posBeforeNode,\n );\n\n // Converts the node at the given index and depth around `$startBlockBeforePos`\n // to a block. Used to get blocks at given indices at the shared depth and\n // at the depth of `$startBlockBeforePos`.\n const indexToBlock = (\n index: number,\n depth?: number,\n ): Block<BSchema, I, S> => {\n const pos = $startBlockBeforePos.posAtIndex(index, depth);\n const node = tr.doc.resolve(pos).nodeAfter;\n\n if (!node) {\n throw new Error(\n `Error getting selection - node not found at position ${pos}`,\n );\n }\n\n return nodeToBlock(node, tr.doc);\n };\n\n const blocks: Block<BSchema, I, S>[] = [];\n // Minimum depth at which the blocks share a common ancestor.\n const sharedDepth = $startBlockBeforePos.sharedDepth($endBlockBeforePos.pos);\n const startIndex = $startBlockBeforePos.index(sharedDepth);\n const endIndex = $endBlockBeforePos.index(sharedDepth);\n\n // In most cases, we want to return the blocks spanned by the selection at the\n // shared depth. However, when the block in which the selection starts is at a\n // higher depth than the shared depth, we omit the first block at the shared\n // depth. Instead, we include the first block at its depth, and any blocks at\n // a higher index up to the shared depth. The following example illustrates\n // this:\n // - id-0\n // - id-1\n // - >|id-2\n // - id-3\n // - id-4\n // - id-5\n // - id-6\n // - id-7\n // - id-8\n // - id-9|<\n // - id-10\n // Here, each block is represented by its ID, and the selection is represented\n // by the `>|` and `|<` markers. So the selection starts in block `id-2` and\n // ends in block `id-8`. In this case, the shared depth is 0, since the blocks\n // `id-6`, `id-7`, and `id-8` set the shared depth, as they are the least\n // nested blocks spanned by the selection. Therefore, these blocks are all\n // added to the `blocks` array. However, the selection starts in block `id-2`,\n // which is at a higher depth than the shared depth. So we add block `id-2` to\n // the `blocks` array, as well as any later siblings (in this case, `id-3`),\n // and move up one level of depth. The ancestor of block `id-2` at this depth\n // is block `id-1`, so we add all its later siblings to the `blocks` array as\n // well, again moving up one level of depth. Since we're now at the shared\n // depth, we are done. The final `blocks` array for this example would be:\n // [ id-2, id-3, id-4, id-6, id-7, id-8, id-9 ]\n if ($startBlockBeforePos.depth > sharedDepth) {\n // Adds the block that the selection starts in.\n blocks.push(nodeToBlock($startBlockBeforePos.nodeAfter!, tr.doc));\n\n // Traverses all depths from the depth of the block in which the selection\n // starts, up to the shared depth.\n for (let depth = $startBlockBeforePos.depth; depth > sharedDepth; depth--) {\n const parentNode = $startBlockBeforePos.node(depth);\n\n if (parentNode.type.isInGroup(\"childContainer\")) {\n const startIndexAtDepth = $startBlockBeforePos.index(depth) + 1;\n const childCountAtDepth = $startBlockBeforePos.node(depth).childCount;\n\n // Adds all blocks after the index of the block in which the selection\n // starts (or its ancestors at lower depths).\n for (let i = startIndexAtDepth; i < childCountAtDepth; i++) {\n blocks.push(indexToBlock(i, depth));\n }\n }\n }\n } else {\n // Adds the first block spanned by the selection at the shared depth.\n blocks.push(indexToBlock(startIndex, sharedDepth));\n }\n\n // Adds all blocks spanned by the selection at the shared depth, excluding\n // the first.\n for (let i = startIndex + 1; i <= endIndex; i++) {\n blocks.push(indexToBlock(i, sharedDepth));\n }\n\n if (blocks.length === 0) {\n throw new Error(\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string\n `Error getting selection - selection doesn't span any blocks (${tr.selection})`,\n );\n }\n\n return {\n blocks,\n };\n}\n\nexport function setSelection(\n tr: Transaction,\n startBlock: BlockIdentifier,\n endBlock: BlockIdentifier,\n) {\n const startBlockId =\n typeof startBlock === \"string\" ? startBlock : startBlock.id;\n const endBlockId = typeof endBlock === \"string\" ? endBlock : endBlock.id;\n const pmSchema = getPmSchema(tr);\n const schema = getBlockNoteSchema(pmSchema);\n\n if (startBlockId === endBlockId) {\n throw new Error(\n `Attempting to set selection with the same anchor and head blocks (id ${startBlockId})`,\n );\n }\n const anchorPosInfo = getNodeById(startBlockId, tr.doc);\n if (!anchorPosInfo) {\n throw new Error(`Block with ID ${startBlockId} not found`);\n }\n const headPosInfo = getNodeById(endBlockId, tr.doc);\n if (!headPosInfo) {\n throw new Error(`Block with ID ${endBlockId} not found`);\n }\n\n const anchorBlockInfo = getBlockInfo(anchorPosInfo);\n const headBlockInfo = getBlockInfo(headPosInfo);\n\n const anchorBlockConfig =\n schema.blockSchema[\n anchorBlockInfo.blockNoteType as keyof typeof schema.blockSchema\n ];\n const headBlockConfig =\n schema.blockSchema[\n headBlockInfo.blockNoteType as keyof typeof schema.blockSchema\n ];\n\n if (\n !anchorBlockInfo.isBlockContainer ||\n anchorBlockConfig.content === \"none\"\n ) {\n throw new Error(\n `Attempting to set selection anchor in block without content (id ${startBlockId})`,\n );\n }\n if (!headBlockInfo.isBlockContainer || headBlockConfig.content === \"none\") {\n throw new Error(\n `Attempting to set selection anchor in block without content (id ${endBlockId})`,\n );\n }\n\n let startPos: number;\n let endPos: number;\n\n if (anchorBlockConfig.content === \"table\") {\n const tableMap = TableMap.get(anchorBlockInfo.blockContent.node);\n const firstCellPos =\n anchorBlockInfo.blockContent.beforePos +\n tableMap.positionAt(0, 0, anchorBlockInfo.blockContent.node) +\n 1;\n startPos = firstCellPos + 2;\n } else {\n startPos = anchorBlockInfo.blockContent.beforePos + 1;\n }\n\n if (headBlockConfig.content === \"table\") {\n const tableMap = TableMap.get(headBlockInfo.blockContent.node);\n const lastCellPos =\n headBlockInfo.blockContent.beforePos +\n tableMap.positionAt(\n tableMap.height - 1,\n tableMap.width - 1,\n headBlockInfo.blockContent.node,\n ) +\n 1;\n const lastCellNodeSize = tr.doc.resolve(lastCellPos).nodeAfter!.nodeSize;\n endPos = lastCellPos + lastCellNodeSize - 2;\n } else {\n endPos = headBlockInfo.blockContent.afterPos - 1;\n }\n\n // TODO: We should polish up the `MultipleNodeSelection` and use that instead.\n // Right now it's missing a few things like a jsonID and styling to show\n // which nodes are selected. `TextSelection` is ok for now, but has the\n // restriction that the start/end blocks must have content.\n tr.setSelection(TextSelection.create(tr.doc, startPos, endPos));\n}\n\nexport function getSelectionCutBlocks(tr: Transaction, expandToWords = false) {\n // TODO: fix image node selection\n\n const range = expandToWords\n ? expandPMRangeToWords(tr.doc, tr.selection)\n : tr.selection;\n\n let start = range.$from;\n let end = range.$to;\n\n // the selection moves below are used to make sure `prosemirrorSliceToSlicedBlocks` returns\n // the correct information about whether content is cut at the start or end of a block\n\n // if the end is at the end of a node (|</span></p>) move it forward so we include all closing tags (</span></p>|)\n while (end.parentOffset >= end.parent.nodeSize - 2 && end.depth > 0) {\n end = tr.doc.resolve(end.pos + 1);\n }\n\n // if the end is at the start of an empty node (</span></p><p>|) move it backwards so we drop empty start tags (</span></p>|)\n while (end.parentOffset === 0 && end.depth > 0) {\n end = tr.doc.resolve(end.pos - 1);\n }\n\n // if the start is at the start of a node (<p><span>|) move it backwards so we include all open tags (|<p><span>)\n while (start.parentOffset === 0 && start.depth > 0) {\n start = tr.doc.resolve(start.pos - 1);\n }\n\n // if the start is at the end of a node (|</p><p><span>|) move it forwards so we drop all closing tags (|<p><span>)\n while (start.parentOffset >= start.parent.nodeSize - 2 && start.depth > 0) {\n start = tr.doc.resolve(start.pos + 1);\n }\n\n const selectionInfo = prosemirrorSliceToSlicedBlocks(\n tr.doc.slice(start.pos, end.pos, true),\n );\n\n return {\n _meta: {\n startPos: start.pos,\n endPos: end.pos,\n },\n ...selectionInfo,\n };\n}\n","import { isNodeSelection, posToDOMRect } from \"@tiptap/core\";\nimport {\n getSelection,\n getSelectionCutBlocks,\n setSelection,\n} from \"../../api/blockManipulation/selections/selection.js\";\nimport {\n getTextCursorPosition,\n setTextCursorPosition,\n} from \"../../api/blockManipulation/selections/textCursorPosition.js\";\nimport {\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n} from \"../../blocks/defaultBlocks.js\";\nimport {\n BlockIdentifier,\n BlockSchema,\n InlineContentSchema,\n StyleSchema,\n} from \"../../schema/index.js\";\nimport { BlockNoteEditor } from \"../BlockNoteEditor.js\";\nimport { TextCursorPosition } from \"../cursorPositionTypes.js\";\nimport { Selection } from \"../selectionTypes.js\";\n\nexport class SelectionManager<\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 the current selection. This contains all blocks (included nested blocks)\n * that the selection spans across.\n *\n * If the selection starts / ends halfway through a block, the returned data will contain the entire block.\n */\n public getSelection(): Selection<BSchema, ISchema, SSchema> | undefined {\n return this.editor.transact((tr) => getSelection(tr));\n }\n\n /**\n * Gets a snapshot of the current selection. This contains all blocks (included nested blocks)\n * that the selection spans across.\n *\n * If the selection starts / ends halfway through a block, the returned block will be\n * only the part of the block that is included in the selection.\n */\n public getSelectionCutBlocks(expandToWords = false) {\n return this.editor.transact((tr) =>\n getSelectionCutBlocks(tr, expandToWords),\n );\n }\n\n /**\n * Sets the selection to a range of blocks.\n * @param startBlock The identifier of the block that should be the start of the selection.\n * @param endBlock The identifier of the block that should be the end of the selection.\n */\n public setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier) {\n return this.editor.transact((tr) => setSelection(tr, startBlock, endBlock));\n }\n\n /**\n * Gets a snapshot of the current text cursor position.\n * @returns A snapshot of the current text cursor position.\n */\n public getTextCursorPosition(): TextCursorPosition<\n BSchema,\n ISchema,\n SSchema\n > {\n return this.editor.transact((tr) => getTextCursorPosition(tr));\n }\n\n /**\n * Sets the text cursor position to the start or end of an existing block. Throws an error if the target block could\n * not be found.\n * @param targetBlock The identifier of an existing block that the text cursor should be moved to.\n * @param placement Whether the text cursor should be placed at the start or end of the block.\n */\n public setTextCursorPosition(\n targetBlock: BlockIdentifier,\n placement: \"start\" | \"end\" = \"start\",\n ) {\n return this.editor.transact((tr) =>\n setTextCursorPosition(tr, targetBlock, placement),\n );\n }\n\n /**\n * Gets the bounding box of the current selection.\n */\n public getSelectionBoundingBox() {\n if (!this.editor.prosemirrorView) {\n return undefined;\n }\n\n const { selection } = this.editor.prosemirrorState;\n\n // support for CellSelections\n const { ranges } = selection;\n const from = Math.min(...ranges.map((range) => range.$from.pos));\n const to = Math.max(...ranges.map((range) => range.$to.pos));\n\n if (isNodeSelection(selection)) {\n const node = this.editor.prosemirrorView.nodeDOM(from) as HTMLElement;\n if (node) {\n return node.getBoundingClientRect();\n }\n }\n\n return posToDOMRect(\n this.editor.prosemirrorView,\n from,\n to,\n ).toJSON() as DOMRect;\n }\n}\n","import { Command, Transaction } from \"prosemirror-state\";\nimport type { HistoryExtension } from \"../../extensions/History/History.js\";\nimport { BlockNoteEditor } from \"../BlockNoteEditor.js\";\n\nexport class StateManager {\n constructor(private editor: BlockNoteEditor<any, any, any>) {}\n\n /**\n * Stores the currently active transaction, which is the accumulated transaction from all {@link dispatch} calls during a {@link transact} calls\n */\n private activeTransaction: Transaction | null = null;\n\n /**\n * For any command that can be executed, you can check if it can be executed by calling `editor.can(command)`.\n * @example\n * ```ts\n * if (editor.can(editor.undo)) {\n * // show button\n * } else {\n * // hide button\n * }\n */\n public can(cb: () => boolean) {\n try {\n this.isInCan = true;\n return cb();\n } finally {\n this.isInCan = false;\n }\n }\n\n // Flag to indicate if we're in a `can` call\n private isInCan = false;\n\n /**\n * Execute a prosemirror command. This is mostly for backwards compatibility with older code.\n *\n * @note You should prefer the {@link transact} method when possible, as it will automatically handle the dispatching of the transaction and work across blocknote transactions.\n *\n * @example\n * ```ts\n * editor.exec((state, dispatch, view) => {\n * dispatch(state.tr.insertText(\"Hello, world!\"));\n * });\n * ```\n */\n public exec(command: Command) {\n if (this.activeTransaction) {\n throw new Error(\n \"`exec` should not be called within a `transact` call, move the `exec` call outside of the `transact` call\",\n );\n }\n if (this.isInCan) {\n return this.canExec(command);\n }\n const state = this.prosemirrorState;\n const view = this.prosemirrorView;\n const dispatch = (tr: Transaction) => this.prosemirrorView.dispatch(tr);\n\n return command(state, dispatch, view);\n }\n\n /**\n * Check if a command can be executed. A command should return `false` if it is not valid in the current state.\n *\n * @example\n * ```ts\n * if (editor.canExec(command)) {\n * // show button\n * } else {\n * // hide button\n * }\n * ```\n */\n public canExec(command: Command): boolean {\n if (this.activeTransaction) {\n throw new Error(\n \"`canExec` should not be called within a `transact` call, move the `canExec` call outside of the `transact` call\",\n );\n }\n const state = this.prosemirrorState;\n const view = this.prosemirrorView;\n\n return command(state, undefined, view);\n }\n\n /**\n * Execute a function within a \"blocknote transaction\".\n * All changes to the editor within the transaction will be grouped together, so that\n * we can dispatch them as a single operation (thus creating only a single undo step)\n *\n * @note There is no need to dispatch the transaction, as it will be automatically dispatched when the callback is complete.\n *\n * @example\n * ```ts\n * // All changes to the editor will be grouped together\n * editor.transact((tr) => {\n * tr.insertText(\"Hello, world!\");\n * // These two operations will be grouped together in a single undo step\n * editor.transact((tr) => {\n * tr.insertText(\"Hello, world!\");\n * });\n * });\n * ```\n */\n public transact<T>(\n callback: (\n /**\n * The current active transaction, this will automatically be dispatched to the editor when the callback is complete\n * If another `transact` call is made within the callback, it will be passed the same transaction as the parent call.\n */\n tr: Transaction,\n ) => T,\n ): T {\n if (this.activeTransaction) {\n // Already in a transaction, so we can just callback immediately\n return callback(this.activeTransaction);\n }\n\n try {\n // Enter transaction mode, by setting a starting transaction\n this.activeTransaction = this.editor._tiptapEditor.state.tr;\n\n // Capture all dispatch'd transactions\n const result = callback(this.activeTransaction);\n\n // Any transactions captured by the `dispatch` call will be stored in `this.activeTransaction`\n const activeTr = this.activeTransaction;\n\n this.activeTransaction = null;\n if (\n activeTr &&\n // Only dispatch if the transaction was actually modified in some way\n (activeTr.docChanged ||\n activeTr.selectionSet ||\n activeTr.scrolledIntoView ||\n activeTr.storedMarksSet ||\n !activeTr.isGeneric)\n ) {\n // Dispatch the transaction if it was modified\n this.prosemirrorView.dispatch(activeTr);\n }\n\n return result;\n } finally {\n // We wrap this in a finally block to ensure we don't disable future transactions just because of an error in the callback\n this.activeTransaction = null;\n }\n }\n /**\n * Get the underlying prosemirror state\n * @note Prefer using `editor.transact` to read the current editor state, as that will ensure the state is up to date\n * @see https://prosemirror.net/docs/ref/#state.EditorState\n */\n public get prosemirrorState() {\n if (this.activeTransaction) {\n throw new Error(\n \"`prosemirrorState` should not be called within a `transact` call, move the `prosemirrorState` call outside of the `transact` call or use `editor.transact` to read the current editor state\",\n );\n }\n return this.editor._tiptapEditor.state;\n }\n\n /**\n * Get the underlying prosemirror view\n * @see https://prosemirror.net/docs/ref/#view.EditorView\n */\n public get prosemirrorView() {\n return this.editor._tiptapEditor.view;\n }\n\n public isFocused() {\n return this.prosemirrorView?.hasFocus() || false;\n }\n\n public focus() {\n this.prosemirrorView?.focus();\n }\n\n /**\n * Checks if the editor is currently editable, or if it's locked.\n * @returns True if the editor is editable, false otherwise.\n */\n public get isEditable(): boolean {\n if (!this.editor._tiptapEditor) {\n if (!this.editor.headless) {\n throw new Error(\"no editor, but also not headless?\");\n }\n return false;\n }\n return this.editor._tiptapEditor.isEditable === undefined\n ? true\n : this.editor._tiptapEditor.isEditable;\n }\n\n /**\n * Makes the editor editable or locks it, depending on the argument passed.\n * @param editable True to make the editor editable, or false to lock it.\n */\n public set isEditable(editable: boolean) {\n if (!this.editor._tiptapEditor) {\n if (!this.editor.headless) {\n throw new Error(\"no editor, but also not headless?\");\n }\n // not relevant on headless\n return;\n }\n if (this.editor._tiptapEditor.options.editable !== editable) {\n this.editor._tiptapEditor.setEditable(editable);\n }\n }\n\n /**\n * Undo the last action.\n */\n public undo(): boolean {\n // Purposefully not using the UndoPlugin to not import y-prosemirror when not needed\n const undoPlugin =\n this.editor.getExtension<typeof HistoryExtension>(\"yUndo\");\n if (undoPlugin) {\n return this.exec(undoPlugin.undoCommand);\n }\n\n const historyPlugin =\n this.editor.getExtension<typeof HistoryExtension>(\"history\");\n if (historyPlugin) {\n return this.exec(historyPlugin.undoCommand);\n }\n\n throw new Error(\"No undo plugin found\");\n }\n\n /**\n * Redo the last action.\n */\n public redo() {\n const undoPlugin =\n this.editor.getExtension<typeof HistoryExtension>(\"yUndo\");\n if (undoPlugin) {\n return this.exec(undoPlugin.redoCommand);\n }\n\n const historyPlugin =\n this.editor.getExtension<typeof HistoryExtension>(\"history\");\n if (historyPlugin) {\n return this.exec(historyPlugin.redoCommand);\n }\n\n throw new Error(\"No redo plugin found\");\n }\n}\n","import { selectionToInsertionEnd } from \"@tiptap/core\";\nimport { Node } from \"prosemirror-model\";\n\nimport type { Transaction } from \"prosemirror-state\";\n\n// similar to tiptap insertContentAt\nexport function insertContentAt(\n tr: Transaction,\n position: number | { from: number; to: number },\n nodes: Node[],\n options: {\n updateSelection: boolean;\n } = { updateSelection: true },\n) {\n // don’t dispatch an empty fragment because this can lead to strange errors\n // if (content.toString() === \"<>\") {\n // return true;\n // }\n\n let { from, to } =\n typeof position === \"number\"\n ? { from: position, to: position }\n : { from: position.from, to: position.to };\n\n let isOnlyTextContent = true;\n let isOnlyBlockContent = true;\n // const nodes = isFragment(content) ? content : [content];\n\n let text = \"\";\n\n nodes.forEach((node) => {\n // check if added node is valid\n node.check();\n\n if (isOnlyTextContent && node.isText && node.marks.length === 0) {\n text += node.text;\n } else {\n isOnlyTextContent = false;\n }\n\n isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false;\n });\n\n // check if we can replace the wrapping node by\n // the newly inserted content\n // example:\n // replace an empty paragraph by an inserted image\n // instead of inserting the image below the paragraph\n if (from === to && isOnlyBlockContent) {\n const { parent } = tr.doc.resolve(from);\n const isEmptyTextBlock =\n parent.isTextblock && !parent.type.spec.code && !parent.childCount;\n\n if (isEmptyTextBlock) {\n from -= 1;\n to += 1;\n }\n }\n\n // if there is only plain text we have to use `insertText`\n // because this will keep the current marks\n if (isOnlyTextContent) {\n // if value is string, we can use it directly\n // otherwise if it is an array, we have to join it\n // if (Array.isArray(value)) {\n // tr.insertText(value.map((v) => v.text || \"\").join(\"\"), from, to);\n // } else if (typeof value === \"object\" && !!value && !!value.text) {\n // tr.insertText(value.text, from, to);\n // } else {\n // tr.insertText(value as string, from, to);\n // }\n tr.insertText(text, from, to);\n } else {\n tr.replaceWith(from, to, nodes);\n }\n\n // set cursor at end of inserted content\n if (options.updateSelection) {\n selectionToInsertionEnd(tr, tr.steps.length - 1, -1);\n }\n\n return true;\n}\n","import { getMarkRange } from \"@tiptap/core\";\nimport { insertContentAt } from \"../../api/blockManipulation/insertContentAt.js\";\nimport { inlineContentToNodes } from \"../../api/nodeConversions/blockToNode.js\";\nimport {\n BlockSchema,\n InlineContentSchema,\n PartialInlineContent,\n StyleSchema,\n Styles,\n} from \"../../schema/index.js\";\nimport {\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n} from \"../../blocks/defaultBlocks.js\";\nimport { UnreachableCaseError } from \"../../util/typescript.js\";\nimport { BlockNoteEditor } from \"../BlockNoteEditor.js\";\n\nexport class StyleManager<\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 * Insert a piece of content at the current cursor position.\n *\n * @param content can be a string, or array of partial inline content elements\n */\n public insertInlineContent(\n content: PartialInlineContent<ISchema, SSchema>,\n { updateSelection = false }: { updateSelection?: boolean } = {},\n ) {\n const nodes = inlineContentToNodes(content, this.editor.pmSchema);\n\n this.editor.transact((tr) => {\n insertContentAt(\n tr,\n {\n from: tr.selection.from,\n to: tr.selection.to,\n },\n nodes,\n {\n updateSelection,\n },\n );\n });\n }\n\n /**\n * Gets the active text styles at the text cursor position or at the end of the current selection if it's active.\n */\n public getActiveStyles() {\n return this.editor.transact((tr) => {\n const styles: Styles<SSchema> = {};\n const marks = tr.selection.$to.marks();\n\n for (const mark of marks) {\n const config = this.editor.schema.styleSchema[mark.type.name];\n if (!config) {\n if (\n // Links are not considered styles in blocknote\n mark.type.name !== \"link\" &&\n // \"blocknoteIgnore\" tagged marks (such as comments) are also not considered BlockNote \"styles\"\n !mark.type.spec.blocknoteIgnore\n ) {\n // eslint-disable-next-line no-console\n console.warn(\"mark not found in styleschema\", mark.type.name);\n }\n\n continue;\n }\n if (config.propSchema === \"boolean\") {\n (styles as any)[config.type] = true;\n } else {\n (styles as any)[config.type] = mark.attrs.stringValue;\n }\n }\n\n return styles;\n });\n }\n\n /**\n * Adds styles to the currently selected content.\n * @param styles The styles to add.\n */\n public addStyles(styles: Styles<SSchema>) {\n for (const [style, value] of Object.entries(styles)) {\n const config = this.editor.schema.styleSchema[style];\n if (!config) {\n throw new Error(`style ${style} not found in styleSchema`);\n }\n if (config.propSchema === \"boolean\") {\n this.editor._tiptapEditor.commands.setMark(style);\n } else if (config.propSchema === \"string\") {\n this.editor._tiptapEditor.commands.setMark(style, {\n stringValue: value,\n });\n } else {\n throw new UnreachableCaseError(config.propSchema);\n }\n }\n }\n\n /**\n * Removes styles from the currently selected content.\n * @param styles The styles to remove.\n */\n public removeStyles(styles: Styles<SSchema>) {\n for (const style of Object.keys(styles)) {\n this.editor._tiptapEditor.commands.unsetMark(style);\n }\n }\n\n /**\n * Toggles styles on the currently selected content.\n * @param styles The styles to toggle.\n */\n public toggleStyles(styles: Styles<SSchema>) {\n for (const [style, value] of Object.entries(styles)) {\n const config = this.editor.schema.styleSchema[style];\n if (!config) {\n throw new Error(`style ${style} not found in styleSchema`);\n }\n if (config.propSchema === \"boolean\") {\n this.editor._tiptapEditor.commands.toggleMark(style);\n } else if (config.propSchema === \"string\") {\n this.editor._tiptapEditor.commands.toggleMark(style, {\n stringValue: value,\n });\n } else {\n throw new UnreachableCaseError(config.propSchema);\n }\n }\n }\n\n /**\n * Gets the currently selected text.\n */\n public getSelectedText() {\n return this.editor.transact((tr) => {\n return tr.doc.textBetween(tr.selection.from, tr.selection.to);\n });\n }\n\n /**\n * Find the link mark and its range at the given position.\n * Returns undefined if there is no link at that position.\n */\n public getLinkMarkAtPos(pos: number) {\n return this.editor.transact((tr) => {\n const resolvedPos = tr.doc.resolve(pos);\n const linkMark = resolvedPos\n .marks()\n .find((mark) => mark.type.name === \"link\");\n\n if (!linkMark) {\n return undefined;\n }\n\n const range = getMarkRange(resolvedPos, linkMark.type);\n if (!range) {\n return undefined;\n }\n\n return {\n href: linkMark.attrs.href as string,\n from: range.from,\n to: range.to,\n text: tr.doc.textBetween(range.from, range.to),\n };\n });\n }\n\n /**\n * Gets the URL of the last link in the current selection, or `undefined` if there are no links in the selection.\n */\n public getSelectedLinkUrl() {\n return this.editor.transact((tr) => {\n return this.getLinkMarkAtPos(tr.selection.from)?.href;\n });\n }\n\n /**\n * Creates a new link to replace the selected content.\n * @param url The link URL.\n * @param text The text to display the link with.\n */\n public createLink(url: string, text?: string) {\n if (url === \"\") {\n return;\n }\n\n this.editor.transact((tr) => {\n const { from, to } = tr.selection;\n const linkMark = this.editor.pmSchema.mark(\"link\", { href: url });\n\n if (text) {\n tr.insertText(text, from, to).addMark(\n from,\n from + text.length,\n linkMark,\n );\n } else {\n tr.addMark(from, to, linkMark);\n }\n });\n }\n\n /**\n * Updates the link at the given position with a new URL and text.\n * @param url The new link URL.\n * @param text The new text to display.\n * @param position The position inside the link to edit. Defaults to the current selection anchor.\n */\n public editLink(\n url: string,\n text: string,\n position = this.editor.transact((tr) => tr.selection.anchor),\n ) {\n this.editor.transact((tr) => {\n const linkData = this.getLinkMarkAtPos(position + 1);\n const { from, to } = linkData || {\n from: tr.selection.from,\n to: tr.selection.to,\n };\n\n const linkMark = this.editor.pmSchema.mark(\"link\", { href: url });\n const existingText = tr.doc.textBetween(from, to);\n if (text !== existingText) {\n tr.insertText(text, from, to);\n }\n tr.addMark(from, from + text.length, linkMark);\n });\n this.editor.prosemirrorView.focus();\n }\n\n /**\n * Removes the link at the given position, keeping the text.\n * @param position The position inside the link to remove. Defaults to the current selection anchor.\n */\n public deleteLink(\n position = this.editor.transact((tr) => tr.selection.anchor),\n ) {\n this.editor.transact((tr) => {\n const linkData = this.getLinkMarkAtPos(position + 1);\n const { from, to } = linkData || {\n from: tr.selection.from,\n to: tr.selection.to,\n };\n\n tr.removeMark(from, to, this.editor.pmSchema.marks[\"link\"]).setMeta(\n \"preventAutolink\",\n true,\n );\n });\n this.editor.prosemirrorView.focus();\n }\n}\n","import { Fragment, Schema, Slice } from \"@tiptap/pm/model\";\nimport { EditorView } from \"@tiptap/pm/view\";\n\nimport { getBlockInfoFromSelection } from \"../api/getBlockInfoFromPos.js\";\nimport { findParentNodeClosestToPos } from \"@tiptap/core\";\n\n/**\n * Checks if the current selection is inside a table cell.\n * Returns the depth of the tableCell/tableHeader node if found, -1 otherwise.\n */\nfunction isInTableCell(view: EditorView): boolean {\n return (\n findParentNodeClosestToPos(view.state.selection.$from, (n) => {\n return n.type.name === \"tableCell\" || n.type.name === \"tableHeader\";\n }) !== undefined\n );\n}\n\n/**\n * Converts block content to inline content with hard breaks.\n * This is used when pasting into table cells which can only contain inline content.\n */\nfunction convertBlocksToInlineContent(\n fragment: Fragment,\n schema: Schema,\n): Fragment {\n const hardBreak = schema.nodes.hardBreak;\n let result = Fragment.empty;\n\n fragment.forEach((node) => {\n if (node.isTextblock && node.childCount > 0) {\n // Extract inline content from paragraphs, headings, etc.\n result = result.append(node.content);\n result = result.addToEnd(hardBreak.create());\n } else if (node.isText) {\n result = result.addToEnd(node);\n } else if (node.isBlock && node.childCount > 0) {\n // Recurse into block containers, blockGroups, etc.\n result = result.append(\n convertBlocksToInlineContent(node.content, schema),\n );\n result = result.addToEnd(hardBreak.create());\n }\n });\n\n // Remove trailing hard break\n if (result.lastChild?.type === hardBreak) {\n result = result.cut(0, result.size - 1);\n }\n\n return result;\n}\n\n// helper function to remove a child from a fragment\nfunction removeChild(node: Fragment, n: number) {\n const children: any[] = [];\n node.forEach((child, _, i) => {\n if (i !== n) {\n children.push(child);\n }\n });\n return Fragment.from(children);\n}\n\n/**\n * Wrap adjacent tableRow items in a table.\n *\n * This makes sure the content that we paste is always a table (and not a tableRow)\n * A table works better for the remaing paste handling logic, as it's actually a blockContent node\n */\nexport function wrapTableRows(f: Fragment, schema: Schema) {\n const newItems: any[] = [];\n for (let i = 0; i < f.childCount; i++) {\n if (f.child(i).type.name === \"tableRow\") {\n if (\n newItems.length > 0 &&\n newItems[newItems.length - 1].type.name === \"table\"\n ) {\n // append to existing table\n const prevTable = newItems[newItems.length - 1];\n const newTable = prevTable.copy(prevTable.content.addToEnd(f.child(i)));\n newItems[newItems.length - 1] = newTable;\n } else {\n // create new table to wrap tableRow with\n const newTable = schema.nodes.table.createChecked(\n undefined,\n f.child(i),\n );\n newItems.push(newTable);\n }\n } else {\n newItems.push(f.child(i));\n }\n }\n f = Fragment.from(newItems);\n return f;\n}\n\n/**\n * fix for https://github.com/ProseMirror/prosemirror/issues/1430#issuecomment-1822570821\n *\n * This fix wraps pasted ProseMirror nodes in their own `blockContainer` nodes\n * in most cases. This is to ensure that ProseMirror inserts them as separate\n * blocks, which it sometimes doesn't do because it doesn't have enough context\n * about the hierarchy of the pasted nodes. The issue can be seen when pasting\n * e.g. an image or two consecutive paragraphs, where PM tries to nest the\n * pasted block(s) when it shouldn't.\n *\n * However, the fix is not applied in a few cases. See `shouldApplyFix` for\n * which cases are excluded.\n */\nexport function transformPasted(slice: Slice, view: EditorView) {\n let f = Fragment.from(slice.content);\n f = wrapTableRows(f, view.state.schema);\n\n const retyped = retypeLeadingParagraphForEmptyTarget(f, view, slice);\n if (retyped) {\n return retyped;\n }\n\n if (isInTableCell(view)) {\n let hasTableContent = false;\n f.descendants((node) => {\n if (node.type.isInGroup(\"tableContent\")) {\n hasTableContent = true;\n }\n });\n if (\n !hasTableContent &&\n // is the content valid for a table paragraph?\n !view.state.schema.nodes.tableParagraph.validContent(f)\n ) {\n // if not, convert the content to inline content\n return new Slice(\n convertBlocksToInlineContent(f, view.state.schema),\n 0,\n 0,\n );\n }\n }\n\n if (!shouldApplyFix(f, view)) {\n // Don't apply the fix.\n return new Slice(f, slice.openStart, slice.openEnd);\n }\n\n for (let i = 0; i < f.childCount; i++) {\n if (f.child(i).type.spec.group === \"blockContent\") {\n const content = [f.child(i)];\n\n // when there is a blockGroup with lists, it should be nested in the new blockcontainer\n // (if we remove this if-block, the nesting bug will be fixed, but lists won't be nested correctly)\n if (\n i + 1 < f.childCount &&\n f.child(i + 1).type.name === \"blockGroup\" // TODO\n ) {\n const nestedChild = f\n .child(i + 1)\n .child(0)\n .child(0);\n\n if (\n nestedChild.type.name === \"bulletListItem\" ||\n nestedChild.type.name === \"numberedListItem\" ||\n nestedChild.type.name === \"checkListItem\"\n ) {\n content.push(f.child(i + 1));\n f = removeChild(f, i + 1);\n }\n }\n const container = view.state.schema.nodes.blockContainer.createChecked(\n undefined,\n content,\n );\n f = f.replaceChild(i, container);\n }\n }\n return new Slice(f, slice.openStart, slice.openEnd);\n}\n\n/**\n * Pasting plain text into an empty inline-content block (e.g. an empty\n * bullet list item) would normally replace that block with a paragraph:\n * BlockNote's serializer always wraps content in\n * `blockGroup > blockContainer > <block>`, producing a closed slice that\n * ProseMirror inserts as a new block rather than splicing inline.\n *\n * To preserve the empty block's type, retype the leading paragraph in the\n * slice to match the target block. Subsequent blocks in the slice are left\n * alone and end up as siblings.\n *\n * Scoped to: empty, non-paragraph, inline-content target + paragraph leading\n * the slice. A non-empty target already gives ProseMirror a valid inline\n * insertion point so it splices correctly on its own; non-paragraph leading\n * blocks (heading, list item, …) carry semantic meaning the user picked, so\n * we keep the existing replace behavior.\n */\nfunction retypeLeadingParagraphForEmptyTarget(\n fragment: Fragment,\n view: EditorView,\n slice: Slice,\n): Slice | null {\n if (isInTableCell(view)) {\n return null;\n }\n\n // `transformPasted` is also called for drop events, where the slice will be\n // inserted at the drop position rather than the current selection. In that\n // case the selection-derived target is wrong, so bail out and let the\n // default behavior handle drops.\n if (view.dragging) {\n return null;\n }\n\n const blockInfo = getBlockInfoFromSelection(view.state);\n const target = blockInfo.isBlockContainer\n ? blockInfo.blockContent.node\n : null;\n if (\n !target ||\n target.type.name === \"paragraph\" ||\n target.type.spec.content !== \"inline*\" ||\n target.childCount > 0\n ) {\n return null;\n }\n\n const blockGroup = fragment.firstChild;\n const blockContainer = blockGroup?.firstChild;\n const leading = blockContainer?.firstChild;\n if (\n blockGroup?.type.name !== \"blockGroup\" ||\n blockContainer?.type.name !== \"blockContainer\" ||\n leading?.type.name !== \"paragraph\"\n ) {\n return null;\n }\n\n const retyped = target.type.create(target.attrs, leading.content);\n const newBlockContainer = blockContainer.copy(\n blockContainer.content.replaceChild(0, retyped),\n );\n const newBlockGroup = blockGroup.copy(\n blockGroup.content.replaceChild(0, newBlockContainer),\n );\n return new Slice(\n fragment.replaceChild(0, newBlockGroup),\n slice.openStart,\n slice.openEnd,\n );\n}\n\n/**\n * Used in `transformPasted` to check if the fix there should be applied, i.e.\n * if the pasted fragment should be wrapped in a `blockContainer` node. This\n * will explicitly tell ProseMirror to treat it as a separate block.\n */\nfunction shouldApplyFix(fragment: Fragment, view: EditorView) {\n const nodeHasSingleChild = fragment.childCount === 1;\n const nodeHasInlineContent =\n fragment.firstChild?.type.spec.content === \"inline*\";\n const nodeHasTableContent =\n fragment.firstChild?.type.spec.content === \"tableRow+\";\n\n if (nodeHasSingleChild) {\n if (nodeHasInlineContent) {\n // Case when we paste a single node with inline content, e.g. a paragraph\n // or heading. We want to insert the content in-line for better UX instead\n // of a separate block, so we return false.\n return false;\n }\n\n if (nodeHasTableContent) {\n // Not ideal that we check selection here, as `transformPasted` is called\n // for both paste and drop events. Drop events can potentially cause\n // issues as they don't always happen at the current selection.\n const blockInfo = getBlockInfoFromSelection(view.state);\n if (blockInfo.isBlockContainer) {\n const selectedBlockHasTableContent =\n blockInfo.blockContent.node.type.spec.content === \"tableRow+\";\n\n // Case for when we paste a single node with table content, i.e. a\n // table. Normally, we return true as we want to ensure the table is\n // inserted as a separate block. However, if the selection is in an\n // existing table, we return false, as we want the content of the pasted\n // table to be added to the existing one for better UX.\n return !selectedBlockHasTableContent;\n }\n }\n }\n\n return true;\n}\n","import {\n createDocument,\n EditorOptions,\n FocusPosition,\n getSchema,\n Editor as TiptapEditor,\n} from \"@tiptap/core\";\nimport { type Command, type Transaction } from \"@tiptap/pm/state\";\nimport { Node, Schema } from \"prosemirror-model\";\nimport type { BlocksChanged } from \"../api/getBlocksChangedByTransaction.js\";\nimport { blockToNode } from \"../api/nodeConversions/blockToNode.js\";\nimport {\n Block,\n BlockNoteSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n PartialBlock,\n} from \"../blocks/index.js\";\nimport {\n BlockChangeExtension,\n DropCursorOptions,\n} from \"../extensions/index.js\";\nimport { UniqueID } from \"../extensions/tiptap-extensions/UniqueID/UniqueID.js\";\nimport type { Dictionary } from \"../i18n/dictionary.js\";\nimport { en } from \"../i18n/locales/index.js\";\nimport type {\n BlockIdentifier,\n BlockNoteDOMAttributes,\n BlockSchema,\n BlockSpecs,\n CustomBlockNoteSchema,\n InlineContentSchema,\n InlineContentSpecs,\n PartialInlineContent,\n Styles,\n StyleSchema,\n StyleSpecs,\n} from \"../schema/index.js\";\nimport \"../style.css\";\nimport { mergeCSSClasses } from \"../util/browser.js\";\nimport { EventEmitter } from \"../util/EventEmitter.js\";\nimport type { NoInfer } from \"../util/typescript.js\";\nimport {\n Extension,\n ExtensionFactory,\n ExtensionFactoryInstance,\n} from \"./BlockNoteExtension.js\";\nimport type { TextCursorPosition } from \"./cursorPositionTypes.js\";\nimport {\n BlockManager,\n EventManager,\n ExportManager,\n ExtensionManager,\n SelectionManager,\n StateManager,\n StyleManager,\n} from \"./managers/index.js\";\nimport type { Selection } from \"./selectionTypes.js\";\nimport { transformPasted } from \"./transformPasted.js\";\n\nexport type BlockCache<\n BSchema extends BlockSchema = any,\n ISchema extends InlineContentSchema = any,\n SSchema extends StyleSchema = any,\n> = WeakMap<Node, Block<BSchema, ISchema, SSchema>>;\n\nexport interface BlockNoteEditorOptions<\n BSchema extends BlockSchema,\n ISchema extends InlineContentSchema,\n SSchema extends StyleSchema,\n> {\n /**\n * Whether changes to blocks (like indentation, creating lists, changing headings) should be animated or not. Defaults to `true`.\n *\n * @default true\n */\n animations?: boolean;\n\n /**\n * Whether the editor should be focused automatically when it's created.\n *\n * @default false\n */\n autofocus?: FocusPosition;\n\n /**\n * Use default BlockNote font and reset the styles of <p> <li> <h1> elements etc., that are used in BlockNote.\n *\n * @default true\n */\n defaultStyles?: boolean;\n\n /**\n * A dictionary object containing translations for the editor.\n *\n * See [Localization / i18n](https://www.blocknotejs.org/docs/advanced/localization) for more info.\n *\n * @remarks `Dictionary` is a type that contains all the translations for the editor.\n */\n dictionary?: Dictionary & Record<string, any>;\n\n /**\n * Disable internal extensions (based on keys / extension name)\n *\n * @note Advanced\n */\n disableExtensions?: string[];\n\n /**\n * An object containing attributes that should be added to HTML elements of the editor.\n *\n * See [Adding DOM Attributes](https://www.blocknotejs.org/docs/theming#adding-dom-attributes) for more info.\n *\n * @example { editor: { class: \"my-editor-class\" } }\n * @remarks `Record<string, Record<string, string>>`\n */\n domAttributes?: Partial<BlockNoteDOMAttributes>;\n\n /**\n * Options for configuring the drop cursor behavior when dragging and dropping blocks.\n * Allows customization of cursor appearance and drop position computation through hooks.\n * @remarks `DropCursorOptions`\n */\n dropCursor?: DropCursorOptions;\n\n /**\n * The content that should be in the editor when it's created, represented as an array of {@link PartialBlock} objects.\n *\n * See [Partial Blocks](https://www.blocknotejs.org/docs/editor-api/manipulating-blocks#partial-blocks) for more info.\n *\n * @remarks `PartialBlock[]`\n */\n initialContent?: PartialBlock<\n NoInfer<BSchema>,\n NoInfer<ISchema>,\n NoInfer<SSchema>\n >[];\n\n /**\n * Options for configuring how links behave in the editor.\n */\n links?: {\n /**\n * HTML attributes to add to rendered link elements.\n *\n * @default {}\n * @example { class: \"my-link-class\", target: \"_blank\" }\n */\n HTMLAttributes?: Record<string, any>;\n /**\n * Custom handler invoked when a link is clicked. If left `undefined`,\n * links are opened in a new window on click. If provided, the default\n * open-on-click behavior is disabled and this function is called instead.\n *\n * Return `false` to let ProseMirror continue handling the click event.\n * Returning `true` or nothing (the default) marks the event as handled.\n */\n onClick?: (\n event: MouseEvent,\n editor: BlockNoteEditor<any, any, any>,\n ) => boolean | void;\n /**\n * Callback that decides whether a given `href` is a valid link. Applied at\n * every gate where a link enters the document: HTML import, HTML export,\n * paste, and autolink. Useful for supporting additional URI schemes (e.g.\n * `vscode:`, `myapp:`) or tightening the default allowlist.\n *\n * Defaults to `isAllowedUri`, which allows\n * `http|https|ftp|ftps|mailto|tel|callto|sms|cid|xmpp`. Import\n * `isAllowedUri` from `@blocknote/core` to layer on top of the default.\n *\n * @example\n * ```ts\n * import { isAllowedUri } from \"@blocknote/core\";\n *\n * BlockNoteEditor.create({\n * links: {\n * isValidLink: (href) =>\n * isAllowedUri(href) || href.startsWith(\"myapp:\"),\n * },\n * });\n * ```\n */\n isValidLink?: (href: string) => boolean;\n };\n\n /**\n * @deprecated, provide placeholders via dictionary instead\n * @internal\n */\n placeholders?: Record<string, string | undefined>;\n\n /**\n * Custom paste handler that can be used to override the default paste behavior.\n *\n * See [Paste Handling](https://www.blocknotejs.org/docs/advanced/paste-handling) for more info.\n *\n * @remarks `PasteHandler`\n * @returns The function should return `true` if the paste event was handled, otherwise it should return `false` if it should be canceled or `undefined` if it should be handled by another handler.\n *\n * @example\n * ```ts\n * pasteHandler: ({ defaultPasteHandler }) => {\n * return defaultPasteHandler({ pasteBehavior: \"prefer-html\" });\n * }\n * ```\n */\n pasteHandler?: (context: {\n event: ClipboardEvent;\n editor: BlockNoteEditor<\n NoInfer<BSchema>,\n NoInfer<ISchema>,\n NoInfer<SSchema>\n >;\n /**\n * The default paste handler\n * @param context The context object\n * @returns Whether the paste event was handled or not\n */\n defaultPasteHandler: (context?: {\n /**\n * Whether to prioritize Markdown content in `text/plain` over `text/html` when pasting from the clipboard.\n * @default true\n */\n prioritizeMarkdownOverHTML?: boolean;\n /**\n * Whether to parse `text/plain` content from the clipboard as Markdown content.\n * @default true\n */\n plainTextAsMarkdown?: boolean;\n }) => boolean | undefined;\n }) => boolean | undefined;\n\n /**\n * Resolve a URL of a file block to one that can be displayed or downloaded. This can be used for creating authenticated URL or\n * implementing custom protocols / schemes\n * @returns The URL that's\n */\n resolveFileUrl?: (url: string) => Promise<string>;\n\n /**\n * The schema of the editor. The schema defines which Blocks, InlineContent, and Styles are available in the editor.\n *\n * See [Custom Schemas](https://www.blocknotejs.org/docs/custom-schemas) for more info.\n * @remarks `BlockNoteSchema`\n */\n schema: CustomBlockNoteSchema<BSchema, ISchema, SSchema>;\n\n /**\n * A flag indicating whether to set an HTML ID for every block\n *\n * When set to `true`, on each block an id attribute will be set with the block id\n * Otherwise, the HTML ID attribute will not be set.\n *\n * (note that the id is always set on the `data-id` attribute)\n */\n setIdAttribute?: boolean;\n\n /**\n * Determines behavior when pressing Tab (or Shift-Tab) while multiple blocks are selected and a toolbar is open.\n * - `\"prefer-navigate-ui\"`: Changes focus to the toolbar. User must press Escape to close toolbar before indenting blocks. Better for keyboard accessibility.\n * - `\"prefer-indent\"`: Always indents selected blocks, regardless of toolbar state. Keyboard navigation of toolbars not possible.\n * @default \"prefer-navigate-ui\"\n */\n tabBehavior?: \"prefer-navigate-ui\" | \"prefer-indent\";\n\n /**\n * Allows enabling / disabling features of tables.\n *\n * See [Tables](https://www.blocknotejs.org/docs/editor-basics/document-structure#tables) for more info.\n *\n * @remarks `TableConfig`\n */\n tables?: {\n /**\n * Whether to allow splitting and merging cells within a table.\n *\n * @default false\n */\n splitCells?: boolean;\n /**\n * Whether to allow changing the background color of cells.\n *\n * @default false\n */\n cellBackgroundColor?: boolean;\n /**\n * Whether to allow changing the text color of cells.\n *\n * @default false\n */\n cellTextColor?: boolean;\n /**\n * Whether to allow changing cells into headers.\n *\n * @default false\n */\n headers?: boolean;\n };\n\n /**\n * When the editor document doesn't end in an empty paragraph block, this option causes the editor to render an element simulating one.\n * When clicked by the user, it gets turned into an actual block at the end of the document. This element is not shown when the option is `false`.\n *\n * @default true\n */\n trailingBlock?: boolean;\n\n /**\n * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).\n * This method should set when creating the editor as this is application-specific.\n *\n * `undefined` means the application doesn't support file uploads.\n *\n * @param file The file that should be uploaded.\n * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)\n * @remarks `(file: File) => Promise<UploadFileResult>`\n */\n uploadFile?: (\n file: File,\n blockId?: string,\n ) => Promise<string | Record<string, any>>;\n\n /**\n * additional tiptap options, undocumented\n * @internal\n */\n _tiptapOptions?: Partial<EditorOptions>;\n\n /**\n * Register extensions to the editor.\n *\n * See [Extensions](/docs/features/extensions) for more info.\n *\n * @remarks `ExtensionFactory[]`\n */\n extensions?: Array<ExtensionFactoryInstance>;\n}\n\nconst blockNoteTipTapOptions = {\n enableInputRules: true,\n enablePasteRules: true,\n enableCoreExtensions: false,\n};\n\nexport class BlockNoteEditor<\n BSchema extends BlockSchema = DefaultBlockSchema,\n ISchema extends InlineContentSchema = DefaultInlineContentSchema,\n SSchema extends StyleSchema = DefaultStyleSchema,\n> extends EventEmitter<{\n create: void;\n}> {\n /**\n * The underlying prosemirror schema\n */\n public readonly pmSchema: Schema;\n\n public readonly _tiptapEditor: TiptapEditor & {\n contentComponent: any;\n };\n\n /**\n * Used by React to store a reference to an `ElementRenderer` helper utility to make sure we can render React elements\n * in the correct context (used by `ReactRenderUtil`)\n */\n public elementRenderer: ((node: any, container: HTMLElement) => void) | null =\n null;\n\n /**\n * Cache of all blocks. This makes sure we don't have to \"recompute\" blocks if underlying Prosemirror Nodes haven't changed.\n * This is especially useful when we want to keep track of the same block across multiple operations,\n * with this cache, blocks stay the same object reference (referential equality with ===).\n */\n public blockCache: BlockCache = new WeakMap();\n\n /**\n * The dictionary contains translations for the editor.\n */\n public readonly dictionary: Dictionary & Record<string, any>;\n\n /**\n * The schema of the editor. The schema defines which Blocks, InlineContent, and Styles are available in the editor.\n */\n public readonly schema: BlockNoteSchema<BSchema, ISchema, SSchema>;\n\n public readonly blockImplementations: BlockSpecs;\n public readonly inlineContentImplementations: InlineContentSpecs;\n public readonly styleImplementations: StyleSpecs;\n\n /**\n * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).\n * This method should set when creating the editor as this is application-specific.\n *\n * `undefined` means the application doesn't support file uploads.\n *\n * @param file The file that should be uploaded.\n * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)\n */\n public readonly uploadFile:\n | ((file: File, blockId?: string) => Promise<string | Record<string, any>>)\n | undefined;\n\n private onUploadStartCallbacks: ((blockId?: string) => void)[] = [];\n private onUploadEndCallbacks: ((blockId?: string) => void)[] = [];\n\n public readonly resolveFileUrl?: (url: string) => Promise<string>;\n /**\n * Editor settings\n */\n public readonly settings: {\n tables: {\n splitCells: boolean;\n cellBackgroundColor: boolean;\n cellTextColor: boolean;\n headers: boolean;\n };\n };\n public static create<\n Options extends Partial<BlockNoteEditorOptions<any, any, any>> | undefined,\n >(\n options?: Options,\n ): Options extends {\n schema: CustomBlockNoteSchema<infer BSchema, infer ISchema, infer SSchema>;\n }\n ? BlockNoteEditor<BSchema, ISchema, SSchema>\n : BlockNoteEditor<\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema\n > {\n return new BlockNoteEditor(options ?? {}) as any;\n }\n\n protected constructor(\n protected readonly options: Partial<\n BlockNoteEditorOptions<BSchema, ISchema, SSchema>\n >,\n ) {\n super();\n\n this.dictionary = options.dictionary || en;\n this.settings = {\n tables: {\n splitCells: options?.tables?.splitCells ?? false,\n cellBackgroundColor: options?.tables?.cellBackgroundColor ?? false,\n cellTextColor: options?.tables?.cellTextColor ?? false,\n headers: options?.tables?.headers ?? false,\n },\n };\n\n // apply defaults\n const newOptions = {\n defaultStyles: true,\n schema:\n options.schema ||\n (BlockNoteSchema.create() as unknown as CustomBlockNoteSchema<\n BSchema,\n ISchema,\n SSchema\n >),\n ...options,\n placeholders: {\n ...this.dictionary.placeholders,\n ...options.placeholders,\n },\n };\n\n this.schema = newOptions.schema;\n this.blockImplementations = newOptions.schema.blockSpecs;\n this.inlineContentImplementations = newOptions.schema.inlineContentSpecs;\n this.styleImplementations = newOptions.schema.styleSpecs;\n\n // TODO this should just be an extension\n if (newOptions.uploadFile) {\n const uploadFile = newOptions.uploadFile;\n this.uploadFile = async (file, blockId) => {\n this.onUploadStartCallbacks.forEach((callback) =>\n callback.apply(this, [blockId]),\n );\n try {\n return await uploadFile(file, blockId);\n } finally {\n this.onUploadEndCallbacks.forEach((callback) =>\n callback.apply(this, [blockId]),\n );\n }\n };\n }\n\n this.resolveFileUrl = newOptions.resolveFileUrl;\n\n this._eventManager = new EventManager(this as any);\n this._extensionManager = new ExtensionManager(this, newOptions);\n\n const tiptapExtensions = this._extensionManager.getTiptapExtensions();\n\n const tiptapOptions: EditorOptions = {\n ...blockNoteTipTapOptions,\n ...newOptions._tiptapOptions,\n element: null,\n autofocus: newOptions.autofocus ?? false,\n extensions: tiptapExtensions,\n editorProps: {\n scrollMargin: { top: 72, bottom: 72, left: 0, right: 0 },\n ...newOptions._tiptapOptions?.editorProps,\n attributes: {\n // As of TipTap v2.5.0 the tabIndex is removed when the editor is not\n // editable, so you can't focus it. We want to revert this as we have\n // UI behaviour that relies on it.\n tabIndex: \"0\",\n // eslint-disable-next-line @typescript-eslint/no-misused-spread\n ...newOptions._tiptapOptions?.editorProps?.attributes,\n ...newOptions.domAttributes?.editor,\n class: mergeCSSClasses(\n \"bn-editor\",\n newOptions.defaultStyles ? \"bn-default-styles\" : \"\",\n newOptions.domAttributes?.editor?.class || \"\",\n ),\n },\n transformPasted,\n },\n } as any;\n\n try {\n const initialContent = newOptions.initialContent || [\n {\n type: \"paragraph\",\n id: UniqueID.options.generateID(),\n },\n ];\n\n if (!Array.isArray(initialContent) || initialContent.length === 0) {\n throw new Error(\n \"initialContent must be a non-empty array of blocks, received: \" +\n JSON.stringify(initialContent),\n );\n }\n const schema = getSchema(tiptapOptions.extensions!);\n // `blockToNode` (via `isPlainContentNodeType`) resolves the block schema\n // through `schema.cached.blockNoteEditor`, so stamp it on this throwaway\n // schema now — the real `pmSchema` is stamped separately below.\n schema.cached.blockNoteEditor = this;\n const pmNodes = initialContent.map((b) =>\n blockToNode(b, schema, this.schema.styleSchema).toJSON(),\n );\n const doc = createDocument(\n {\n type: \"doc\",\n content: [\n {\n type: \"blockGroup\",\n content: pmNodes,\n },\n ],\n },\n schema,\n tiptapOptions.parseOptions,\n );\n\n this._tiptapEditor = new TiptapEditor({\n ...tiptapOptions,\n content: doc.toJSON(),\n }) as any;\n this.pmSchema = this._tiptapEditor.schema;\n } catch (e) {\n throw new Error(\n \"Error creating document from blocks passed as `initialContent`\",\n { cause: e },\n );\n }\n\n this.pmSchema.cached.blockNoteEditor = this;\n\n this._tiptapEditor.on(\"mount\", () => {\n this.headless = false;\n });\n this._tiptapEditor.on(\"unmount\", () => {\n this.headless = true;\n });\n\n // Initialize managers\n this._blockManager = new BlockManager(this as any);\n\n this._exportManager = new ExportManager(this as any);\n this._selectionManager = new SelectionManager(this as any);\n this._stateManager = new StateManager(this as any);\n this._styleManager = new StyleManager(this as any);\n\n this.emit(\"create\");\n }\n\n // Manager instances\n private readonly _blockManager: BlockManager<any, any, any>;\n private readonly _eventManager: EventManager<any, any, any>;\n private readonly _exportManager: ExportManager<any, any, any>;\n private readonly _extensionManager: ExtensionManager;\n private readonly _selectionManager: SelectionManager<any, any, any>;\n private readonly _stateManager: StateManager;\n private readonly _styleManager: StyleManager<any, any, any>;\n\n /**\n * BlockNote extensions that are added to the editor, keyed by the extension key\n */\n public get extensions() {\n return this._extensionManager.getExtensions();\n }\n\n /**\n * Execute a prosemirror command. This is mostly for backwards compatibility with older code.\n *\n * @note You should prefer the {@link transact} method when possible, as it will automatically handle the dispatching of the transaction and work across blocknote transactions.\n *\n * @example\n * ```ts\n * editor.exec((state, dispatch, view) => {\n * dispatch(state.tr.insertText(\"Hello, world!\"));\n * });\n * ```\n */\n public exec(command: Command) {\n return this._stateManager.exec(command);\n }\n\n /**\n * Check if a command can be executed. A command should return `false` if it is not valid in the current state.\n *\n * @example\n * ```ts\n * if (editor.canExec(command)) {\n * // show button\n * } else {\n * // hide button\n * }\n * ```\n */\n public canExec(command: Command): boolean {\n return this._stateManager.canExec(command);\n }\n\n /**\n * Execute a function within a \"blocknote transaction\".\n * All changes to the editor within the transaction will be grouped together, so that\n * we can dispatch them as a single operation (thus creating only a single undo step)\n *\n * @note There is no need to dispatch the transaction, as it will be automatically dispatched when the callback is complete.\n *\n * @example\n * ```ts\n * // All changes to the editor will be grouped together\n * editor.transact((tr) => {\n * tr.insertText(\"Hello, world!\");\n * // These two operations will be grouped together in a single undo step\n * editor.transact((tr) => {\n * tr.insertText(\"Hello, world!\");\n * });\n * });\n * ```\n */\n public transact<T>(\n callback: (\n /**\n * The current active transaction, this will automatically be dispatched to the editor when the callback is complete\n * If another `transact` call is made within the callback, it will be passed the same transaction as the parent call.\n */\n tr: Transaction,\n ) => T,\n ): T {\n return this._stateManager.transact(callback);\n }\n\n /**\n * Remove extension(s) from the editor\n */\n public unregisterExtension: ExtensionManager[\"unregisterExtension\"] = (\n ...args: Parameters<ExtensionManager[\"unregisterExtension\"]>\n ) => this._extensionManager.unregisterExtension(...args);\n\n /**\n * Register extension(s) to the editor\n */\n public registerExtension: ExtensionManager[\"registerExtension\"] = (\n ...args: Parameters<ExtensionManager[\"registerExtension\"]>\n ) => this._extensionManager.registerExtension(...args) as any;\n\n /**\n * Atomically unregister old extensions and register new ones in a single\n * plugin update, avoiding re-entrant dispatch issues.\n */\n public replaceExtension: ExtensionManager[\"replaceExtension\"] = (\n ...args: Parameters<ExtensionManager[\"replaceExtension\"]>\n ) => this._extensionManager.replaceExtension(...args);\n\n /**\n * Get an extension from the editor\n */\n // Declared as an explicit intersection of the two `ExtensionManager`\n // overloads rather than `ExtensionManager[\"getExtension\"]`: indexed access on\n // an overloaded method collapses the signatures, which widened the factory\n // overload's `ReturnType<ReturnType<T>>` result to `any` (losing e.g. a\n // returned extension's `store` type).\n public getExtension: (<\n const Ext extends Extension | ExtensionFactory = Extension,\n >(\n extension: string,\n ) =>\n | (Ext extends Extension\n ? Ext\n : Ext extends ExtensionFactory\n ? ReturnType<ReturnType<Ext>>\n : never)\n | undefined) &\n (<const T extends ExtensionFactory>(\n extension: T,\n ) => ReturnType<ReturnType<T>> | undefined) = ((extension: any) =>\n this._extensionManager.getExtension(extension)) as any;\n\n /**\n * Mount the editor to a DOM element.\n *\n * @param element The DOM element to mount the editor's contenteditable into.\n * @param options.portalTarget Where to mount `editor.portalElement` — the\n * container that floating UI (toolbars, menus, etc) portals into. When\n * omitted, defaults to `element.parentElement` (which is the editor's\n * `bn-container` in typical React usage), or to `document.body` /\n * the surrounding shadow root when no parent is available.\n *\n * @warning Not needed to call manually when using React, use BlockNoteView to take care of mounting\n */\n public mount = (\n element: HTMLElement,\n options?: { portalTarget?: HTMLElement | null },\n ) => {\n const root = element.getRootNode();\n const isInShadowRoot =\n typeof ShadowRoot !== \"undefined\" && root instanceof ShadowRoot;\n const target =\n options?.portalTarget ??\n element.parentElement ??\n (isInShadowRoot ? (root as ShadowRoot) : document.body);\n target.appendChild(this.portalElement);\n this._tiptapEditor.mount({ mount: element });\n };\n\n /**\n * Unmount the editor from the DOM element it is bound to\n */\n public unmount = () => {\n this.portalElement?.remove();\n this._tiptapEditor.unmount();\n };\n\n /**\n * Get the underlying prosemirror state\n * @note Prefer using `editor.transact` to read the current editor state, as that will ensure the state is up to date\n * @see https://prosemirror.net/docs/ref/#state.EditorState\n */\n public get prosemirrorState() {\n return this._stateManager.prosemirrorState;\n }\n\n /**\n * Get the underlying prosemirror view\n * @see https://prosemirror.net/docs/ref/#view.EditorView\n */\n public get prosemirrorView() {\n return this._stateManager.prosemirrorView;\n }\n\n public get domElement() {\n if (this.headless) {\n return undefined;\n }\n return this.prosemirrorView?.dom as HTMLDivElement | undefined;\n }\n\n private _portalElement: HTMLElement | undefined;\n\n /**\n * The portal container element at `document.body` used by floating UI\n * elements (menus, toolbars) to escape overflow:hidden ancestors.\n * Set by BlockNoteView; undefined in headless mode.\n */\n public get portalElement() {\n if (typeof document === \"undefined\") {\n throw new Error(\n \"Portal element accessed, but not available in headless mode\",\n );\n }\n if (!this._portalElement) {\n this._portalElement = document.createElement(\"div\");\n }\n return this._portalElement;\n }\n\n /**\n * Checks whether a DOM element belongs to this editor — either inside the\n * editor's DOM tree or inside its portal container (used for floating UI\n * elements like menus and toolbars).\n */\n public isWithinEditor = (element: Element): boolean => {\n return !!(\n this.domElement?.parentElement?.contains(element) ||\n this.portalElement?.contains(element)\n );\n };\n\n public isFocused() {\n if (this.headless) {\n return false;\n }\n return this.prosemirrorView?.hasFocus() || false;\n }\n\n public headless = true;\n\n /**\n * Focus on the editor\n */\n public focus() {\n if (this.headless) {\n return;\n }\n this.prosemirrorView.focus();\n }\n\n /**\n * Blur the editor\n */\n public blur() {\n if (this.headless) {\n return;\n }\n this.domElement?.blur();\n }\n\n // TODO move to extension\n public onUploadStart(callback: (blockId?: string) => void) {\n this.onUploadStartCallbacks.push(callback);\n\n return () => {\n const index = this.onUploadStartCallbacks.indexOf(callback);\n if (index > -1) {\n this.onUploadStartCallbacks.splice(index, 1);\n }\n };\n }\n\n public onUploadEnd(callback: (blockId?: string) => void) {\n this.onUploadEndCallbacks.push(callback);\n\n return () => {\n const index = this.onUploadEndCallbacks.indexOf(callback);\n if (index > -1) {\n this.onUploadEndCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * @deprecated, use `editor.document` instead\n */\n public get topLevelBlocks(): Block<BSchema, ISchema, SSchema>[] {\n return this.document;\n }\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._blockManager.document;\n }\n\n /**\n * Gets a snapshot of an existing block from the editor.\n * @param blockIdentifier The identifier of an existing block that should be\n * retrieved.\n * @returns The block that matches the identifier, or `undefined` if no\n * matching block was found.\n */\n public getBlock(\n blockIdentifier: BlockIdentifier,\n ): Block<BSchema, ISchema, SSchema> | undefined {\n return this._blockManager.getBlock(blockIdentifier);\n }\n\n /**\n * Gets a snapshot of the previous sibling of an existing block from the\n * editor.\n * @param blockIdentifier The identifier of an existing block for which the\n * previous sibling should be retrieved.\n * @returns The previous sibling of the block that matches the identifier.\n * `undefined` if no matching block was found, or it's the first child/block\n * in the document.\n */\n public getPrevBlock(\n blockIdentifier: BlockIdentifier,\n ): Block<BSchema, ISchema, SSchema> | undefined {\n return this._blockManager.getPrevBlock(blockIdentifier);\n }\n\n /**\n * Gets a snapshot of the next sibling of an existing block from the editor.\n * @param blockIdentifier The identifier of an existing block for which the\n * next sibling should be retrieved.\n * @returns The next sibling of the block that matches the identifier.\n * `undefined` if no matching block was found, or it's the last child/block in\n * the document.\n */\n public getNextBlock(\n blockIdentifier: BlockIdentifier,\n ): Block<BSchema, ISchema, SSchema> | undefined {\n return this._blockManager.getNextBlock(blockIdentifier);\n }\n\n /**\n * Gets a snapshot of the parent of an existing block from the editor.\n * @param blockIdentifier The identifier of an existing block for which the\n * parent should be retrieved.\n * @returns The parent of the block that matches the identifier. `undefined`\n * if no matching block was found, or the block isn't nested.\n */\n public getParentBlock(\n blockIdentifier: BlockIdentifier,\n ): Block<BSchema, ISchema, SSchema> | undefined {\n return this._blockManager.getParentBlock(blockIdentifier);\n }\n\n /**\n * Traverses all blocks in the editor depth-first, and executes a callback for each.\n * @param callback The callback to execute for each block. Returning `false` stops the traversal.\n * @param reverse Whether the blocks should be traversed in reverse order.\n */\n public forEachBlock(\n callback: (block: Block<BSchema, ISchema, SSchema>) => boolean,\n reverse = false,\n ): void {\n this._blockManager.forEachBlock(callback, reverse);\n }\n\n /**\n * Executes a callback whenever the editor's contents change.\n * @param callback The callback to execute.\n *\n * @deprecated use {@link BlockNoteEditor.onChange} instead\n */\n public onEditorContentChange(callback: () => void) {\n this._tiptapEditor.on(\"update\", callback);\n }\n\n /**\n * Executes a callback whenever the editor's selection changes.\n * @param callback The callback to execute.\n *\n * @deprecated use `onSelectionChange` instead\n */\n public onEditorSelectionChange(callback: () => void) {\n this._tiptapEditor.on(\"selectionUpdate\", callback);\n }\n\n /**\n * Executes a callback before any change is applied to the editor, allowing you to cancel the change.\n * @param callback The callback to execute.\n * @returns A function to remove the callback.\n */\n public onBeforeChange(\n callback: (context: {\n getChanges: () => BlocksChanged<BSchema, ISchema, SSchema>;\n tr: Transaction;\n }) => boolean | void,\n ): () => void {\n return this._extensionManager\n .getExtension(BlockChangeExtension)!\n .subscribe(callback);\n }\n\n /**\n * Gets a snapshot of the current text cursor position.\n * @returns A snapshot of the current text cursor position.\n */\n public getTextCursorPosition(): TextCursorPosition<\n BSchema,\n ISchema,\n SSchema\n > {\n return this._selectionManager.getTextCursorPosition();\n }\n\n /**\n * Sets the text cursor position to the start or end of an existing block. Throws an error if the target block could\n * not be found.\n * @param targetBlock The identifier of an existing block that the text cursor should be moved to.\n * @param placement Whether the text cursor should be placed at the start or end of the block.\n */\n public setTextCursorPosition(\n targetBlock: BlockIdentifier,\n placement: \"start\" | \"end\" = \"start\",\n ) {\n return this._selectionManager.setTextCursorPosition(targetBlock, placement);\n }\n\n /**\n * Gets a snapshot of the current selection. This contains all blocks (included nested blocks)\n * that the selection spans across.\n *\n * If the selection starts / ends halfway through a block, the returned data will contain the entire block.\n */\n public getSelection(): Selection<BSchema, ISchema, SSchema> | undefined {\n return this._selectionManager.getSelection();\n }\n\n /**\n * Gets a snapshot of the current selection. This contains all blocks (included nested blocks)\n * that the selection spans across.\n *\n * If the selection starts / ends halfway through a block, the returned block will be\n * only the part of the block that is included in the selection.\n */\n public getSelectionCutBlocks(expandToWords = false) {\n return this._selectionManager.getSelectionCutBlocks(expandToWords);\n }\n\n /**\n * Sets the selection to a range of blocks.\n * @param startBlock The identifier of the block that should be the start of the selection.\n * @param endBlock The identifier of the block that should be the end of the selection.\n */\n public setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier) {\n return this._selectionManager.setSelection(startBlock, endBlock);\n }\n\n /**\n * Checks if the editor is currently editable, or if it's locked.\n * @returns True if the editor is editable, false otherwise.\n */\n public get isEditable(): boolean {\n return this._stateManager.isEditable;\n }\n\n /**\n * Makes the editor editable or locks it, depending on the argument passed.\n * @param editable True to make the editor editable, or false to lock it.\n */\n public set isEditable(editable: boolean) {\n this._stateManager.isEditable = editable;\n }\n\n /**\n * Inserts new blocks into the editor. If a block's `id` is undefined, BlockNote generates one automatically. Throws an\n * error if the reference block could not be found.\n * @param blocksToInsert An array of partial blocks that should be inserted.\n * @param referenceBlock An identifier for an existing block, at which the new blocks should be inserted.\n * @param placement Whether the blocks should be inserted just before, just after, or nested inside the\n * `referenceBlock`.\n */\n public insertBlocks(\n blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[],\n referenceBlock: BlockIdentifier,\n placement: \"before\" | \"after\" = \"before\",\n ) {\n return this._blockManager.insertBlocks(\n blocksToInsert,\n referenceBlock,\n placement,\n );\n }\n\n /**\n * Updates an existing block in the editor. Since updatedBlock is a PartialBlock object, some fields might not be\n * defined. These undefined fields are kept as-is from the existing block. Throws an error if the block to update could\n * not be found.\n * @param blockToUpdate The block that should be updated.\n * @param update A partial block which defines how the existing block should be changed.\n */\n public updateBlock(\n blockToUpdate: BlockIdentifier,\n update: PartialBlock<BSchema, ISchema, SSchema>,\n ) {\n return this._blockManager.updateBlock(blockToUpdate, update);\n }\n\n /**\n * Removes existing blocks from the editor. Throws an error if any of the blocks could not be found.\n * @param blocksToRemove An array of identifiers for existing blocks that should be removed.\n */\n public removeBlocks(blocksToRemove: BlockIdentifier[]) {\n return this._blockManager.removeBlocks(blocksToRemove);\n }\n\n /**\n * Replaces existing blocks in the editor with new blocks. If the blocks that should be removed are not adjacent or\n * are at different nesting levels, `blocksToInsert` will be inserted at the position of the first block in\n * `blocksToRemove`. Throws an error if any of the blocks to remove could not be found.\n * @param blocksToRemove An array of blocks that should be replaced.\n * @param blocksToInsert An array of partial blocks to replace the old ones with.\n */\n public replaceBlocks(\n blocksToRemove: BlockIdentifier[],\n blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[],\n ) {\n return this._blockManager.replaceBlocks(blocksToRemove, blocksToInsert);\n }\n\n /**\n * Undo the last action.\n */\n public undo(): boolean {\n return this._stateManager.undo();\n }\n\n /**\n * Redo the last action.\n */\n public redo(): boolean {\n return this._stateManager.redo();\n }\n\n /**\n * Insert a piece of content at the current cursor position.\n *\n * @param content can be a string, or array of partial inline content elements\n */\n public insertInlineContent(\n content: PartialInlineContent<ISchema, SSchema>,\n { updateSelection = false }: { updateSelection?: boolean } = {},\n ) {\n this._styleManager.insertInlineContent(content, { updateSelection });\n }\n\n /**\n * Gets the active text styles at the text cursor position or at the end of the current selection if it's active.\n */\n public getActiveStyles(): Styles<SSchema> {\n return this._styleManager.getActiveStyles();\n }\n\n /**\n * Adds styles to the currently selected content.\n * @param styles The styles to add.\n */\n public addStyles(styles: Styles<SSchema>) {\n this._styleManager.addStyles(styles);\n }\n\n /**\n * Removes styles from the currently selected content.\n * @param styles The styles to remove.\n */\n public removeStyles(styles: Styles<SSchema>) {\n this._styleManager.removeStyles(styles);\n }\n\n /**\n * Toggles styles on the currently selected content.\n * @param styles The styles to toggle.\n */\n public toggleStyles(styles: Styles<SSchema>) {\n this._styleManager.toggleStyles(styles);\n }\n\n /**\n * Gets the currently selected text.\n */\n public getSelectedText() {\n return this._styleManager.getSelectedText();\n }\n\n /**\n * Gets the URL of the last link in the current selection, or `undefined` if there are no links in the selection.\n */\n public getSelectedLinkUrl() {\n return this._styleManager.getSelectedLinkUrl();\n }\n\n /**\n * Creates a new link to replace the selected content.\n * @param url The link URL.\n * @param text The text to display the link with.\n */\n public createLink(url: string, text?: string) {\n this._styleManager.createLink(url, text);\n }\n\n /**\n * Find the link mark and its range at the given position.\n * Returns undefined if there is no link at that position.\n */\n public getLinkMarkAtPos(pos: number) {\n return this._styleManager.getLinkMarkAtPos(pos);\n }\n\n /**\n * Updates the link at the given position with a new URL and text.\n * @param url The new link URL.\n * @param text The new text to display.\n * @param position The position inside the link to edit. Defaults to the current selection anchor.\n */\n public editLink(url: string, text: string, position?: number) {\n this._styleManager.editLink(url, text, position);\n }\n\n /**\n * Removes the link at the given position, keeping the text.\n * @param position The position inside the link to remove. Defaults to the current selection anchor.\n */\n public deleteLink(position?: number) {\n this._styleManager.deleteLink(position);\n }\n\n /**\n * Checks if the block containing the text cursor can be nested.\n */\n public canNestBlock() {\n return this._blockManager.canNestBlock();\n }\n\n /**\n * Nests the block containing the text cursor into the block above it.\n */\n public nestBlock() {\n this._blockManager.nestBlock();\n }\n\n /**\n * Checks if the block containing the text cursor is nested.\n */\n public canUnnestBlock() {\n return this._blockManager.canUnnestBlock();\n }\n\n /**\n * Lifts the block containing the text cursor out of its parent.\n */\n public unnestBlock() {\n this._blockManager.unnestBlock();\n }\n\n /**\n * Moves the selected blocks up. If the previous block has children, moves\n * them to the end of its children. If there is no previous block, but the\n * current blocks share a common parent, moves them out of & before it. If a\n * `blockIdentifier` is provided, that block is moved instead of the\n * selection, and the selection is left unchanged.\n */\n public moveBlocksUp(blockIdentifier?: BlockIdentifier) {\n return this._blockManager.moveBlocksUp(blockIdentifier);\n }\n\n /**\n * Moves the selected blocks down. If the next block has children, moves\n * them to the start of its children. If there is no next block, but the\n * current blocks share a common parent, moves them out of & after it. If a\n * `blockIdentifier` is provided, that block is moved instead of the\n * selection, and the selection is left unchanged.\n */\n public moveBlocksDown(blockIdentifier?: BlockIdentifier) {\n return this._blockManager.moveBlocksDown(blockIdentifier);\n }\n\n /**\n * Exports blocks into a simplified HTML string. To better conform to HTML standards, children of blocks which aren't list\n * items are un-nested in the output HTML.\n *\n * @param blocks An array of blocks that should be serialized into HTML.\n * @returns The blocks, serialized as an HTML string.\n */\n public blocksToHTMLLossy(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document,\n ): string {\n return this._exportManager.blocksToHTMLLossy(blocks);\n }\n\n /**\n * Serializes blocks into an HTML string in the format that would normally be rendered by the editor.\n *\n * Use this method if you want to server-side render HTML (for example, a blog post that has been edited in BlockNote)\n * and serve it to users without loading the editor on the client (i.e.: displaying the blog post)\n *\n * @param blocks An array of blocks that should be serialized into HTML.\n * @returns The blocks, serialized as an HTML string.\n */\n public blocksToFullHTML(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document,\n ): string {\n return this._exportManager.blocksToFullHTML(blocks);\n }\n\n /**\n * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and\n * `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote\n * doesn't recognize an HTML element's tag, it will parse it as a paragraph or plain text.\n * @param html The HTML string to parse blocks from.\n * @returns The blocks parsed from the HTML string.\n */\n public tryParseHTMLToBlocks(\n html: string,\n ): Block<BSchema, ISchema, SSchema>[] {\n return this._exportManager.tryParseHTMLToBlocks(html);\n }\n\n /**\n * Serializes blocks into a Markdown string. The output is simplified as Markdown does not support all features of\n * BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed.\n * @param blocks An array of blocks that should be serialized into Markdown.\n * @returns The blocks, serialized as a Markdown string.\n */\n public blocksToMarkdownLossy(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document,\n ): string {\n return this._exportManager.blocksToMarkdownLossy(blocks);\n }\n\n /**\n * Creates a list of blocks from a Markdown string. Tries to create `Block` and `InlineNode` objects based on\n * Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it\n * as text.\n * @param markdown The Markdown string to parse blocks from.\n * @returns The blocks parsed from the Markdown string.\n */\n public tryParseMarkdownToBlocks(\n markdown: string,\n ): Block<BSchema, ISchema, SSchema>[] {\n return this._exportManager.tryParseMarkdownToBlocks(markdown);\n }\n\n /**\n * A callback function that runs whenever the editor's contents change.\n *\n * @param callback The callback to execute.\n * @returns A function to remove the callback.\n */\n public onChange(\n callback: (\n editor: BlockNoteEditor<BSchema, ISchema, SSchema>,\n context: {\n /**\n * Returns the blocks that were inserted, updated, or deleted by the change that occurred.\n */\n getChanges(): BlocksChanged<BSchema, ISchema, SSchema>;\n },\n ) => void,\n /**\n * If true, the callback will be triggered when the changes are caused by a remote user\n * @default true\n */\n includeUpdatesFromRemote?: boolean,\n ) {\n return this._eventManager.onChange(callback, includeUpdatesFromRemote);\n }\n\n /**\n * A callback function that runs whenever the text cursor position or selection changes.\n *\n * @param callback The callback to execute.\n * @returns A function to remove the callback.\n */\n public onSelectionChange(\n callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void,\n includeSelectionChangedByRemote?: boolean,\n ) {\n return this._eventManager.onSelectionChange(\n callback,\n includeSelectionChangedByRemote,\n );\n }\n\n /**\n * A callback function that runs when the editor has been mounted.\n *\n * This can be useful for plugins to initialize themselves after the editor has been mounted.\n *\n * @param callback The callback to execute.\n * @returns A function to remove the callback.\n */\n public onMount(\n callback: (ctx: {\n editor: BlockNoteEditor<BSchema, ISchema, SSchema>;\n }) => void,\n ) {\n return this._eventManager.onMount(callback);\n }\n\n /**\n * A callback function that runs when the editor has been unmounted.\n *\n * This can be useful for plugins to clean up themselves after the editor has been unmounted.\n *\n * @param callback The callback to execute.\n * @returns A function to remove the callback.\n */\n public onUnmount(\n callback: (ctx: {\n editor: BlockNoteEditor<BSchema, ISchema, SSchema>;\n }) => void,\n ) {\n return this._eventManager.onUnmount(callback);\n }\n\n /**\n * Gets the bounding box of the current selection.\n * @returns The bounding box of the current selection.\n */\n public getSelectionBoundingBox() {\n return this._selectionManager.getSelectionBoundingBox();\n }\n\n public get isEmpty() {\n const doc = this.document;\n // Note: only works for paragraphs as default blocks (but for now this is default in blocknote)\n // checking prosemirror directly might be faster\n return (\n doc.length === 0 ||\n (doc.length === 1 &&\n doc[0].type === \"paragraph\" &&\n (doc[0].content as any).length === 0)\n );\n }\n\n /**\n * Paste HTML into the editor. Defaults to converting HTML to BlockNote HTML.\n * @param html The HTML to paste.\n * @param raw Whether to paste the HTML as is, or to convert it to BlockNote HTML.\n */\n public pasteHTML(html: string, raw = false) {\n this._exportManager.pasteHTML(html, raw);\n }\n\n /**\n * Paste text into the editor. Defaults to interpreting text as markdown.\n * @param text The text to paste.\n */\n public pasteText(text: string) {\n return this._exportManager.pasteText(text);\n }\n\n /**\n * Paste markdown into the editor.\n * @param markdown The markdown to paste.\n */\n public pasteMarkdown(markdown: string) {\n return this._exportManager.pasteMarkdown(markdown);\n }\n}\n","import { BlockNoteSchema } from \"../blocks/BlockNoteSchema.js\";\nimport { COLORS_DEFAULT } from \"../editor/defaultColors.js\";\nimport type { Dictionary } from \"../i18n/dictionary.js\";\nimport { en } from \"../i18n/locales/index.js\";\nimport {\n BlockFromConfig,\n BlockSchema,\n InlineContent,\n InlineContentSchema,\n StyleSchema,\n StyledText,\n Styles,\n} from \"../schema/index.js\";\n\nimport type {\n BlockMapping,\n InlineContentMapping,\n StyleMapping,\n} from \"./mapping.js\";\n\nexport type ExporterOptions = {\n /**\n * A function that can be used to resolve files, images, etc.\n * Exporters might need the binary contents of files like images,\n * which might not always be available from the same origin as the main page.\n * You can use this option to proxy requests through a server you control\n * to avoid cross-origin (CORS) issues.\n *\n * @default uses a BlockNote hosted proxy (https://corsproxy.api.blocknotejs.org/)\n * @param url - The URL of the file to resolve\n * @returns A Promise that resolves to a string (the URL to use instead of the original)\n * or a Blob (you can return the Blob directly if you have already fetched it)\n */\n resolveFileUrl?: (url: string) => Promise<string | Blob>;\n /**\n * Colors to use for background of blocks, font colors, and highlight colors\n */\n colors: typeof COLORS_DEFAULT;\n /**\n * The strings an exporter renders into the produced document (file link\n * texts, error placeholders). Accepts a locale from\n * `@blocknote/core/locales` or an editor dictionary; block packages that\n * ship their own exporter strings (e.g. math, diagram) read their sections\n * from this same object, exactly as they do from an editor dictionary.\n *\n * @default the English strings\n */\n dictionary?: { exporter: Dictionary[\"exporter\"] } & {\n // Block packages read their own sections (e.g. `math`, `diagram`) from\n // the same object; their types live with those packages.\n [blockDictionary: string]: unknown;\n };\n};\nexport abstract class Exporter<\n B extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n RB,\n RI,\n RS,\n TS,\n> {\n public constructor(\n _schema: BlockNoteSchema<B, I, S>, // only used for type inference\n protected readonly mappings: {\n blockMapping: BlockMapping<B, I, S, RB, RI>;\n inlineContentMapping: InlineContentMapping<I, S, RI, TS>;\n styleMapping: StyleMapping<S, RS>;\n },\n public readonly options: ExporterOptions,\n ) {}\n\n /**\n * The strings this exporter renders into the produced document - the\n * `exporter` section of the configured dictionary (the `dictionary`\n * option of {@link ExporterOptions}), or the English defaults.\n */\n public get dictionary(): Dictionary[\"exporter\"] {\n return this.options.dictionary?.exporter ?? en.exporter;\n }\n\n public async resolveFile(url: string) {\n if (!this.options?.resolveFileUrl) {\n return (await fetch(url)).blob();\n }\n const ret = await this.options.resolveFileUrl(url);\n if (ret instanceof Blob) {\n return ret;\n }\n return (await fetch(ret)).blob();\n }\n\n public mapStyles(styles: Styles<S>) {\n const stylesArray = Object.entries(styles).map(([key, value]) => {\n const mapping = this.mappings.styleMapping[key];\n if (!mapping) {\n throw new Error(\n `Exporter is missing a style mapping for style \"${key}\". If this style comes from a separate package, spread that package's exporter mappings into your styleMapping.`,\n );\n }\n const mappedStyle = mapping(value, this);\n return mappedStyle;\n });\n return stylesArray;\n }\n\n public mapInlineContent(inlineContent: InlineContent<I, S>) {\n const mapping = this.mappings.inlineContentMapping[inlineContent.type];\n if (!mapping) {\n throw new Error(\n `Exporter is missing an inline content mapping for inline content type \"${inlineContent.type}\". If this inline content comes from a separate package, spread that package's exporter mappings into your inlineContentMapping.`,\n );\n }\n return mapping(inlineContent, this);\n }\n\n public transformInlineContent(inlineContentArray: InlineContent<I, S>[]) {\n return inlineContentArray.map((ic) => this.mapInlineContent(ic));\n }\n\n public abstract transformStyledText(styledText: StyledText<S>): TS;\n\n public async mapBlock(\n block: BlockFromConfig<B[keyof B], I, S>,\n nestingLevel: number,\n numberedListIndex: number,\n children?: Array<Awaited<RB>>,\n ) {\n const mapping = this.mappings.blockMapping[block.type];\n if (!mapping) {\n throw new Error(\n `Exporter is missing a block mapping for block type \"${block.type}\". If this block comes from a separate package, spread that package's exporter mappings into your blockMapping.`,\n );\n }\n return mapping(block, this, nestingLevel, numberedListIndex, children);\n }\n}\n","/**\n * An image generated during export (e.g. a rendered formula or diagram): the\n * encoded image bytes plus the dimensions to display it at.\n *\n * The bytes (rather than e.g. a data URL string or a `Blob`) are the source\n * of truth: they carry no encoding ambiguity, work in every environment, and\n * are readable synchronously - each output format converts them at its own\n * boundary (data URL for HTML-based targets, raw bytes for DOCX, base64 for\n * email attachments).\n */\nexport type ExportImage = {\n /** MIME type of `data`, e.g. `\"image/png\"` or `\"image/svg+xml\"`. */\n mimeType: string;\n /** The encoded image bytes. */\n data: Uint8Array;\n /**\n * Dimensions to display the image at, in the target format's units (CSS\n * pixels, points, ...). For raster images, `data`'s own pixel dimensions\n * may be larger - images are often rendered at 2-4x for sharpness.\n */\n width: number;\n height: number;\n};\n\n/**\n * Encodes bytes as base64. This papers over a platform gap: until\n * `Uint8Array.prototype.toBase64()` (ES2026) is available in every runtime\n * BlockNote supports, the only universal built-in is `btoa`, which takes\n * binary *strings*. Uses `toBase64` when the runtime has it.\n */\nexport function bytesToBase64(bytes: Uint8Array): string {\n if (\"toBase64\" in bytes && typeof bytes.toBase64 === \"function\") {\n return (bytes as Uint8Array & { toBase64(): string }).toBase64();\n }\n\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n\n/**\n * Encodes an {@link ExportImage}'s bytes as a base64 data URL, for targets\n * that take image sources as URLs (HTML `src` attributes, react-pdf image\n * sources, ...).\n */\nexport function exportImageToDataURL(image: ExportImage): string {\n return `data:${image.mimeType};base64,${bytesToBase64(image.data)}`;\n}\n","import { BlockNoteSchema } from \"../blocks/BlockNoteSchema.js\";\nimport {\n BlockFromConfigNoChildren,\n BlockSchema,\n InlineContentFromConfig,\n InlineContentSchema,\n StyleSchema,\n Styles,\n} from \"../schema/index.js\";\nimport type { Exporter } from \"./Exporter.js\";\n\n/**\n * Defines a mapping from all block types with a schema to a result type `R`.\n */\nexport type BlockMapping<\n B extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n RB,\n RI,\n> = {\n [K in keyof B]: (\n block: BlockFromConfigNoChildren<B[K], I, S>,\n // we don't know the exact types that are supported by the exporter at this point,\n // because the mapping only knows about converting certain types (which might be a subset of the supported types)\n // this is why there are many `any` types here (same for types below)\n exporter: Exporter<any, any, any, RB, RI, any, any>,\n nestingLevel: number,\n numberedListIndex?: number,\n children?: Array<Awaited<RB>>,\n ) => RB | Promise<RB>;\n};\n\n/**\n * Defines a mapping from all inline content types with a schema to a result type R.\n */\nexport type InlineContentMapping<\n I extends InlineContentSchema,\n S extends StyleSchema,\n RI,\n TS,\n> = {\n [K in keyof I]: (\n inlineContent: InlineContentFromConfig<I[K], S>,\n // Deliberately loose on the schema generics, like `BlockMapping` above -\n // otherwise a mapping declared for one schema can't be reused (e.g.\n // spread) in a mapping for a schema with different types.\n exporter: Exporter<any, any, any, any, RI, any, TS>,\n ) => RI;\n};\n\n/**\n * Defines a mapping from all style types with a schema to a result type R.\n */\nexport type StyleMapping<S extends StyleSchema, RS> = {\n [K in keyof S]: (\n style: Styles<S>[K],\n exporter: Exporter<any, any, any, any, any, RS, any>,\n ) => RS;\n};\n\n/**\n * The mapping factory is a utility function to easily create mappings for\n * a BlockNoteSchema. Using the factory makes it easier to get typescript code completion etc.\n */\nexport function mappingFactory<\n B extends BlockSchema,\n I extends InlineContentSchema,\n S extends StyleSchema,\n>(_schema: BlockNoteSchema<B, I, S>) {\n return {\n createBlockMapping: <R, RI>(mapping: BlockMapping<B, I, S, R, RI>) =>\n mapping,\n createInlineContentMapping: <R, RS>(\n mapping: InlineContentMapping<I, S, R, RS>,\n ) => mapping,\n createStyleMapping: <R>(mapping: StyleMapping<S, R>) => mapping,\n };\n}\n","/**\n * Combines items by group. This can be used to combine multiple slash menu item arrays,\n * while making sure that items from the same group are adjacent to each other.\n */\nexport function combineByGroup<T extends { group?: string }>(\n items: T[],\n ...additionalItemsArray: {\n group?: string;\n }[][]\n) {\n const combinedItems = [...items];\n for (const additionalItems of additionalItemsArray) {\n for (const additionalItem of additionalItems) {\n const lastItemWithSameGroup = combinedItems.findLastIndex(\n (item) => item.group === additionalItem.group,\n );\n if (lastItemWithSameGroup === -1) {\n combinedItems.push(additionalItem as T);\n } else {\n combinedItems.splice(lastItemWithSameGroup + 1, 0, additionalItem as T);\n }\n }\n }\n return combinedItems;\n}\n"],"mappings":"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"}