@blocknote/server-util
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
1 lines • 13.7 kB
Source Map (JSON)
{"version":3,"file":"blocknote-server-util.cjs","sources":["../src/context/ServerBlockNoteEditor.ts"],"sourcesContent":["import {\n Block,\n BlockNoteEditor,\n BlockNoteEditorOptions,\n BlockSchema,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n InlineContentSchema,\n PartialBlock,\n StyleSchema,\n blocksToMarkdown,\n createExternalHTMLExporter,\n createInternalHTMLSerializer,\n docToBlocks,\n} from \"@blocknote/core\";\nimport {\n _blocksToProsemirrorNode as blocksToProsemirrorNodeUtil,\n _prosemirrorJSONToBlocks as prosemirrorJSONToBlocksUtil,\n blocksToYDoc as blocksToYDocUtil,\n blocksToYXmlFragment as blocksToYXmlFragmentUtil,\n yDocToBlocks as yDocToBlocksUtil,\n yXmlFragmentToBlocks as yXmlFragmentToBlocksUtil,\n} from \"@blocknote/core/yjs\";\n\nimport { BlockNoteViewRaw } from \"@blocknote/react\";\nimport { Node } from \"@tiptap/pm/model\";\nimport * as jsdom from \"jsdom\";\nimport * as React from \"react\";\nimport { createElement } from \"react\";\nimport { flushSync } from \"react-dom\";\nimport { createRoot } from \"react-dom/client\";\nimport type * as Y from \"yjs\";\n\n/**\n * Use the ServerBlockNoteEditor to interact with BlockNote documents in a server (nodejs) environment.\n */\nexport class ServerBlockNoteEditor<\n BSchema extends BlockSchema = DefaultBlockSchema,\n ISchema extends InlineContentSchema = DefaultInlineContentSchema,\n SSchema extends StyleSchema = DefaultStyleSchema,\n> {\n /**\n * Internal BlockNoteEditor (not recommended to use directly, use the methods of this class instead)\n */\n public readonly editor: BlockNoteEditor<BSchema, ISchema, SSchema>;\n\n /**\n * We currently use a JSDOM instance to mock document and window methods\n *\n * A possible improvement could be to make this:\n * a) pluggable so other shims can be used as well\n * b) obsolete, but for this all blocks should be React based and we need to remove all references to document / window\n * from the core / react package. (and even then, it's likely some custom blocks would still use document / window methods)\n */\n private jsdom = new jsdom.JSDOM();\n\n /**\n * Calls a function with mocking window and document using JSDOM\n *\n * We could make this obsolete by passing in a document / window object to the render / serialize methods of Blocks\n */\n public async _withJSDOM<T>(fn: () => Promise<T>) {\n const prevWindow = globalThis.window;\n const prevDocument = globalThis.document;\n globalThis.document = this.jsdom.window.document;\n (globalThis as any).window = this.jsdom.window;\n (globalThis as any).window.__TEST_OPTIONS = (\n prevWindow as any\n )?.__TEST_OPTIONS;\n try {\n return await fn();\n } finally {\n globalThis.document = prevDocument;\n globalThis.window = prevWindow;\n }\n }\n\n public static create<\n BSchema extends BlockSchema = DefaultBlockSchema,\n ISchema extends InlineContentSchema = DefaultInlineContentSchema,\n SSchema extends StyleSchema = DefaultStyleSchema,\n >(options: Partial<BlockNoteEditorOptions<BSchema, ISchema, SSchema>> = {}) {\n return new ServerBlockNoteEditor(options) as ServerBlockNoteEditor<\n BSchema,\n ISchema,\n SSchema\n >;\n }\n\n protected constructor(\n options: Partial<BlockNoteEditorOptions<any, any, any>>,\n ) {\n this.editor = BlockNoteEditor.create(options) as any;\n }\n\n /** PROSEMIRROR / BLOCKNOTE conversions */\n\n /**\n * Turn Prosemirror JSON to BlockNote style JSON\n * @param json Prosemirror JSON\n * @returns BlockNote style JSON\n */\n public _prosemirrorNodeToBlocks(pmNode: Node) {\n return docToBlocks(pmNode);\n }\n\n /**\n * Turn Prosemirror JSON to BlockNote style JSON\n * @param json Prosemirror JSON\n * @returns BlockNote style JSON\n */\n public _prosemirrorJSONToBlocks(json: any) {\n return prosemirrorJSONToBlocksUtil(this.editor, json);\n }\n\n /**\n * Turn BlockNote JSON to Prosemirror node / state\n * @param blocks BlockNote blocks\n * @returns Prosemirror root node\n */\n public _blocksToProsemirrorNode(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[],\n ) {\n return blocksToProsemirrorNodeUtil(this.editor, blocks);\n }\n\n /** YJS / BLOCKNOTE conversions */\n\n /**\n * Turn a Y.XmlFragment collaborative doc into a BlockNote document (BlockNote style JSON of all blocks)\n * @returns BlockNote document (BlockNote style JSON of all blocks)\n */\n public yXmlFragmentToBlocks(xmlFragment: Y.XmlFragment) {\n return yXmlFragmentToBlocksUtil(this.editor, xmlFragment);\n }\n\n /**\n * Convert blocks to a Y.XmlFragment\n *\n * This can be used when importing existing content to Y.Doc for the first time,\n * note that this should not be used to rehydrate a Y.Doc from a database once\n * collaboration has begun as all history will be lost\n *\n * @param blocks the blocks to convert\n * @returns Y.XmlFragment\n */\n public blocksToYXmlFragment(\n blocks: Block<BSchema, ISchema, SSchema>[],\n xmlFragment?: Y.XmlFragment,\n ) {\n return blocksToYXmlFragmentUtil(this.editor, blocks, xmlFragment);\n }\n\n /**\n * Turn a Y.Doc collaborative doc into a BlockNote document (BlockNote style JSON of all blocks)\n * @returns BlockNote document (BlockNote style JSON of all blocks)\n */\n public yDocToBlocks(ydoc: Y.Doc, xmlFragment = \"prosemirror\") {\n return yDocToBlocksUtil(this.editor, ydoc, xmlFragment);\n }\n\n /**\n * This can be used when importing existing content to Y.Doc for the first time,\n * note that this should not be used to rehydrate a Y.Doc from a database once\n * collaboration has begun as all history will be lost\n *\n * @param blocks\n */\n public blocksToYDoc(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[],\n xmlFragment = \"prosemirror\",\n ) {\n return blocksToYDocUtil(this.editor, blocks, xmlFragment);\n }\n\n /** HTML / BLOCKNOTE conversions */\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 async blocksToHTMLLossy(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[],\n ): Promise<string> {\n return this._withJSDOM(async () => {\n const exporter = createExternalHTMLExporter(\n this.editor.pmSchema,\n this.editor,\n );\n\n return exporter.exportBlocks(blocks, {\n document: this.jsdom.window.document,\n });\n });\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 async blocksToFullHTML(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[],\n ): Promise<string> {\n return this._withJSDOM(async () => {\n const exporter = createInternalHTMLSerializer(\n this.editor.pmSchema,\n this.editor,\n );\n\n return exporter.serializeBlocks(blocks, {\n document: this.jsdom.window.document,\n });\n });\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 async tryParseHTMLToBlocks(\n html: string,\n ): Promise<Block<BSchema, ISchema, SSchema>[]> {\n return this._withJSDOM(async () => {\n return this.editor.tryParseHTMLToBlocks(html);\n });\n }\n\n /** MARKDOWN / BLOCKNOTE conversions */\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 async blocksToMarkdownLossy(\n blocks: PartialBlock<BSchema, ISchema, SSchema>[],\n ): Promise<string> {\n return this._withJSDOM(async () => {\n return blocksToMarkdown(blocks, this.editor.pmSchema, this.editor, {\n document: this.jsdom.window.document,\n });\n });\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 async tryParseMarkdownToBlocks(\n markdown: string,\n ): Promise<Block<BSchema, ISchema, SSchema>[]> {\n return this._withJSDOM(async () => {\n return this.editor.tryParseMarkdownToBlocks(markdown);\n });\n }\n\n /**\n * If you're using React Context in your blocks, you can use this method to wrap editor calls for importing / exporting / block manipulation\n * with the React Context Provider.\n * \n * Example:\n * \n * ```tsx\n const html = await editor.withReactContext(\n ({ children }) => (\n <YourContext.Provider value={true}>{children}</YourContext.Provider>\n ),\n async () => editor.blocksToFullHTML(blocks)\n );\n */\n public async withReactContext<T>(comp: React.FC<any>, fn: () => Promise<T>) {\n return this._withJSDOM(async () => {\n const tmpRoot = createRoot(\n this.jsdom.window.document.createElement(\"div\"),\n );\n\n flushSync(() => {\n tmpRoot.render(\n createElement(\n comp,\n {},\n createElement(BlockNoteViewRaw<any, any, any>, {\n editor: this.editor,\n // Skip UI components to avoid rendering them in the server environment\n formattingToolbar: false,\n linkToolbar: false,\n slashMenu: false,\n emojiPicker: false,\n sideMenu: false,\n filePanel: false,\n tableHandles: false,\n comments: false,\n }),\n ),\n );\n });\n try {\n return await fn();\n } finally {\n tmpRoot.unmount();\n await new Promise((resolve) => setTimeout(resolve, 3));\n }\n });\n }\n}\n"],"names":["ServerBlockNoteEditor","options","__publicField","jsdom","BlockNoteEditor","fn","prevWindow","prevDocument","pmNode","docToBlocks","json","prosemirrorJSONToBlocksUtil","blocks","blocksToProsemirrorNodeUtil","xmlFragment","yXmlFragmentToBlocksUtil","blocksToYXmlFragmentUtil","ydoc","yDocToBlocksUtil","blocksToYDocUtil","createExternalHTMLExporter","createInternalHTMLSerializer","html","blocksToMarkdown","markdown","comp","tmpRoot","createRoot","flushSync","createElement","BlockNoteViewRaw","resolve"],"mappings":"4vBAqCO,MAAMA,CAIX,CAiDU,YACRC,EACA,CA/CcC,EAAA,eAURA,EAAA,aAAQ,IAAIC,EAAM,OAsCxB,KAAK,OAASC,kBAAgB,OAAOH,CAAO,CAC9C,CAhCA,MAAa,WAAcI,EAAsB,CAC/C,MAAMC,EAAa,WAAW,OACxBC,EAAe,WAAW,SAChC,WAAW,SAAW,KAAK,MAAM,OAAO,SACvC,WAAmB,OAAS,KAAK,MAAM,OACvC,WAAmB,OAAO,eACzBD,GAAA,YAAAA,EACC,eACH,GAAI,CACF,OAAO,MAAMD,EAAA,CACf,QAAA,CACE,WAAW,SAAWE,EACtB,WAAW,OAASD,CACtB,CACF,CAEA,OAAc,OAIZL,EAAsE,GAAI,CAC1E,OAAO,IAAID,EAAsBC,CAAO,CAK1C,CAeO,yBAAyBO,EAAc,CAC5C,OAAOC,EAAAA,YAAYD,CAAM,CAC3B,CAOO,yBAAyBE,EAAW,CACzC,OAAOC,2BAA4B,KAAK,OAAQD,CAAI,CACtD,CAOO,yBACLE,EACA,CACA,OAAOC,2BAA4B,KAAK,OAAQD,CAAM,CACxD,CAQO,qBAAqBE,EAA4B,CACtD,OAAOC,uBAAyB,KAAK,OAAQD,CAAW,CAC1D,CAYO,qBACLF,EACAE,EACA,CACA,OAAOE,EAAAA,qBAAyB,KAAK,OAAQJ,EAAQE,CAAW,CAClE,CAMO,aAAaG,EAAaH,EAAc,cAAe,CAC5D,OAAOI,EAAAA,aAAiB,KAAK,OAAQD,EAAMH,CAAW,CACxD,CASO,aACLF,EACAE,EAAc,cACd,CACA,OAAOK,EAAAA,aAAiB,KAAK,OAAQP,EAAQE,CAAW,CAC1D,CAWA,MAAa,kBACXF,EACiB,CACjB,OAAO,KAAK,WAAW,SACJQ,EAAAA,2BACf,KAAK,OAAO,SACZ,KAAK,MAAA,EAGS,aAAaR,EAAQ,CACnC,SAAU,KAAK,MAAM,OAAO,QAAA,CAC7B,CACF,CACH,CAWA,MAAa,iBACXA,EACiB,CACjB,OAAO,KAAK,WAAW,SACJS,EAAAA,6BACf,KAAK,OAAO,SACZ,KAAK,MAAA,EAGS,gBAAgBT,EAAQ,CACtC,SAAU,KAAK,MAAM,OAAO,QAAA,CAC7B,CACF,CACH,CASA,MAAa,qBACXU,EAC6C,CAC7C,OAAO,KAAK,WAAW,SACd,KAAK,OAAO,qBAAqBA,CAAI,CAC7C,CACH,CAUA,MAAa,sBACXV,EACiB,CACjB,OAAO,KAAK,WAAW,SACdW,EAAAA,iBAAiBX,EAAQ,KAAK,OAAO,SAAU,KAAK,OAAQ,CACjE,SAAU,KAAK,MAAM,OAAO,QAAA,CAC7B,CACF,CACH,CASA,MAAa,yBACXY,EAC6C,CAC7C,OAAO,KAAK,WAAW,SACd,KAAK,OAAO,yBAAyBA,CAAQ,CACrD,CACH,CAgBA,MAAa,iBAAoBC,EAAqBpB,EAAsB,CAC1E,OAAO,KAAK,WAAW,SAAY,CACjC,MAAMqB,EAAUC,EAAAA,WACd,KAAK,MAAM,OAAO,SAAS,cAAc,KAAK,CAAA,EAGhDC,EAAAA,UAAU,IAAM,CACdF,EAAQ,OACNG,EAAAA,cACEJ,EACA,CAAA,EACAI,EAAAA,cAAcC,EAAAA,iBAAiC,CAC7C,OAAQ,KAAK,OAEb,kBAAmB,GACnB,YAAa,GACb,UAAW,GACX,YAAa,GACb,SAAU,GACV,UAAW,GACX,aAAc,GACd,SAAU,EAAA,CACX,CAAA,CACH,CAEJ,CAAC,EACD,GAAI,CACF,OAAO,MAAMzB,EAAA,CACf,QAAA,CACEqB,EAAQ,QAAA,EACR,MAAM,IAAI,QAASK,GAAY,WAAWA,EAAS,CAAC,CAAC,CACvD,CACF,CAAC,CACH,CACF"}