UNPKG

@blocknote/core

Version:

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

193 lines (168 loc) 5.52 kB
import { DOMSerializer, Fragment } from "prosemirror-model"; import { PartialBlock } from "../../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { BlockSchema, InlineContentSchema, StyleSchema, } from "../../../../schema/index.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; import { inlineContentToNodes, tableContentToNodes, } from "../../../nodeConversions/blockToNode.js"; export function serializeInlineContentInternalHTML< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, >( editor: BlockNoteEditor<any, I, S>, blockContent: PartialBlock<BSchema, I, S>["content"], serializer: DOMSerializer, blockType?: string, options?: { document?: Document }, ) { let nodes: any; // TODO: reuse function from nodeconversions? if (!blockContent) { throw new Error("blockContent is required"); } else if (typeof blockContent === "string") { nodes = inlineContentToNodes([blockContent], editor.pmSchema, blockType); } else if (Array.isArray(blockContent)) { nodes = inlineContentToNodes(blockContent, editor.pmSchema, blockType); } else if (blockContent.type === "tableContent") { nodes = tableContentToNodes(blockContent, editor.pmSchema); } else { throw new UnreachableCaseError(blockContent.type); } // We call the prosemirror serializer here because it handles Marks and Inline Content nodes nicely. // If we'd want to support custom serialization or externalHTML for Inline Content, we'd have to implement // a custom serializer here. const dom = serializer.serializeFragment(Fragment.from(nodes), options); return dom; } function serializeBlock< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, >( editor: BlockNoteEditor<BSchema, I, S>, block: PartialBlock<BSchema, I, S>, serializer: DOMSerializer, listIndex: number, options?: { document?: Document }, ) { const BC_NODE = editor.pmSchema.nodes["blockContainer"]; let props = block.props; // set default props in case we were passed a partial block if (!block.props) { props = {}; for (const [name, spec] of Object.entries( editor.schema.blockSchema[block.type as any].propSchema, )) { if (spec.default !== undefined) { (props as any)[name] = spec.default; } } } const impl = editor.blockImplementations[block.type as any].implementation; const ret = impl.toInternalHTML({ ...block, props } as any, editor as any); if (block.type === "numberedListItem") { // This is a workaround to make sure there's a list index set. // Normally, this is set on the internal prosemirror nodes by the NumberedListIndexingPlugin, // but: // - (a) this information is not available on the Blocks passed to the serializer. (we only have access to BlockNote Blocks) // - (b) the NumberedListIndexingPlugin might not even have run, because we can manually call blocksToFullHTML // with blocks that are not part of the active document ret.dom.setAttribute("data-index", listIndex.toString()); } if (ret.contentDOM && block.content) { const ic = serializeInlineContentInternalHTML( editor, block.content as any, // TODO serializer, block.type, options, ); ret.contentDOM.appendChild(ic); } const pmType = editor.pmSchema.nodes[block.type as any]; if (pmType.isInGroup("bnBlock")) { if (block.children && block.children.length > 0) { const fragment = serializeBlocks( editor, block.children, serializer, options, ); ret.contentDOM?.append(fragment); } return ret.dom; } // wrap the block in a blockContainer const bc = BC_NODE.spec?.toDOM?.( BC_NODE.create({ id: block.id, ...props, }), ) as { dom: HTMLElement; contentDOM?: HTMLElement; }; bc.contentDOM?.appendChild(ret.dom); if (block.children && block.children.length > 0) { bc.contentDOM?.appendChild( serializeBlocksInternalHTML(editor, block.children, serializer, options), ); } return bc.dom; } function serializeBlocks< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, >( editor: BlockNoteEditor<BSchema, I, S>, blocks: PartialBlock<BSchema, I, S>[], serializer: DOMSerializer, options?: { document?: Document }, ) { const doc = options?.document ?? document; const fragment = doc.createDocumentFragment(); let listIndex = 0; for (const block of blocks) { if (block.type === "numberedListItem") { listIndex++; } else { listIndex = 0; } const blockDOM = serializeBlock( editor, block, serializer, listIndex, options, ); fragment.appendChild(blockDOM); } return fragment; } export const serializeBlocksInternalHTML = < BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, >( editor: BlockNoteEditor<BSchema, I, S>, blocks: PartialBlock<BSchema, I, S>[], serializer: DOMSerializer, options?: { document?: Document }, ) => { const BG_NODE = editor.pmSchema.nodes["blockGroup"]; const bg = BG_NODE.spec!.toDOM!(BG_NODE.create({})) as { dom: HTMLElement; contentDOM?: HTMLElement; }; const fragment = serializeBlocks(editor, blocks, serializer, options); bg.contentDOM?.appendChild(fragment); return bg.dom; };