{"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  blockToNode,\n  blocksToMarkdown,\n  createExternalHTMLExporter,\n  createInternalHTMLSerializer,\n  nodeToBlock,\n} from \"@blocknote/core\";\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 {\n  prosemirrorToYDoc,\n  prosemirrorToYXmlFragment,\n  yXmlFragmentToProseMirrorRootNode,\n} from \"y-prosemirror\";\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    const blocks: Block<BSchema, InlineContentSchema, StyleSchema>[] = [];\n\n    // note, this code is similar to editor.document\n    pmNode.firstChild!.descendants((node) => {\n      blocks.push(nodeToBlock(node, this.editor.pmSchema));\n\n      return false;\n    });\n\n    return blocks;\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    // note: theoretically this should also be possible without creating prosemirror nodes,\n    // but this is definitely the easiest way\n    const doc = this.editor.pmSchema.nodeFromJSON(json);\n    return this._prosemirrorNodeToBlocks(doc);\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    const pmSchema = this.editor.pmSchema;\n    const pmNodes = blocks.map((b) => blockToNode(b, pmSchema));\n\n    const doc = pmSchema.topNodeType.create(\n      null,\n      pmSchema.nodes[\"blockGroup\"].create(null, pmNodes),\n    );\n    return doc;\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    const pmNode = yXmlFragmentToProseMirrorRootNode(\n      xmlFragment,\n      this.editor.pmSchema,\n    );\n    return this._prosemirrorNodeToBlocks(pmNode);\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 prosemirrorToYXmlFragment(\n      this._blocksToProsemirrorNode(blocks),\n      xmlFragment,\n    );\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 this.yXmlFragmentToBlocks(ydoc.getXmlFragment(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 prosemirrorToYDoc(\n      this._blocksToProsemirrorNode(blocks),\n      xmlFragment,\n    );\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            }),\n          ),\n        );\n      });\n      try {\n        return await fn();\n      } finally {\n        tmpRoot.unmount();\n      }\n    });\n  }\n}\n"],"names":["ServerBlockNoteEditor","options","__publicField","jsdom","BlockNoteEditor","fn","prevWindow","prevDocument","pmNode","blocks","node","nodeToBlock","json","doc","pmSchema","pmNodes","b","blockToNode","xmlFragment","yXmlFragmentToProseMirrorRootNode","prosemirrorToYXmlFragment","ydoc","prosemirrorToYDoc","createExternalHTMLExporter","createInternalHTMLSerializer","html","blocksToMarkdown","markdown","comp","tmpRoot","createRoot","flushSync","createElement","BlockNoteViewRaw"],"mappings":"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"}