UNPKG

@blocknote/server-util

Version:

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

231 lines (230 loc) 8.3 kB
var m = Object.defineProperty; var d = (e, o, r) => o in e ? m(e, o, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[o] = r; var s = (e, o, r) => d(e, typeof o != "symbol" ? o + "" : o, r); import { BlockNoteEditor as a, nodeToBlock as l, blockToNode as h, createExternalHTMLExporter as T, createInternalHTMLSerializer as u, blocksToMarkdown as p } from "@blocknote/core"; import { BlockNoteViewRaw as w } from "@blocknote/react"; import * as k from "jsdom"; import { createElement as i } from "react"; import { flushSync as y } from "react-dom"; import { createRoot as S } from "react-dom/client"; import { yXmlFragmentToProseMirrorRootNode as b, prosemirrorToYXmlFragment as M, prosemirrorToYDoc as _ } from "y-prosemirror"; class n { constructor(o) { /** * Internal BlockNoteEditor (not recommended to use directly, use the methods of this class instead) */ s(this, "editor"); /** * We currently use a JSDOM instance to mock document and window methods * * A possible improvement could be to make this: * a) pluggable so other shims can be used as well * b) obsolete, but for this all blocks should be React based and we need to remove all references to document / window * from the core / react package. (and even then, it's likely some custom blocks would still use document / window methods) */ s(this, "jsdom", new k.JSDOM()); this.editor = a.create(o); } /** * Calls a function with mocking window and document using JSDOM * * We could make this obsolete by passing in a document / window object to the render / serialize methods of Blocks */ async _withJSDOM(o) { const r = globalThis.window, t = globalThis.document; globalThis.document = this.jsdom.window.document, globalThis.window = this.jsdom.window, globalThis.window.__TEST_OPTIONS = r == null ? void 0 : r.__TEST_OPTIONS; try { return await o(); } finally { globalThis.document = t, globalThis.window = r; } } static create(o = {}) { return new n(o); } /** PROSEMIRROR / BLOCKNOTE conversions */ /** * Turn Prosemirror JSON to BlockNote style JSON * @param json Prosemirror JSON * @returns BlockNote style JSON */ _prosemirrorNodeToBlocks(o) { const r = []; return o.firstChild.descendants((t) => (r.push(l(t, this.editor.pmSchema)), !1)), r; } /** * Turn Prosemirror JSON to BlockNote style JSON * @param json Prosemirror JSON * @returns BlockNote style JSON */ _prosemirrorJSONToBlocks(o) { const r = this.editor.pmSchema.nodeFromJSON(o); return this._prosemirrorNodeToBlocks(r); } /** * Turn BlockNote JSON to Prosemirror node / state * @param blocks BlockNote blocks * @returns Prosemirror root node */ _blocksToProsemirrorNode(o) { const r = this.editor.pmSchema, t = o.map((c) => h(c, r)); return r.topNodeType.create( null, r.nodes.blockGroup.create(null, t) ); } /** YJS / BLOCKNOTE conversions */ /** * Turn a Y.XmlFragment collaborative doc into a BlockNote document (BlockNote style JSON of all blocks) * @returns BlockNote document (BlockNote style JSON of all blocks) */ yXmlFragmentToBlocks(o) { const r = b( o, this.editor.pmSchema ); return this._prosemirrorNodeToBlocks(r); } /** * Convert blocks to a Y.XmlFragment * * This can be used when importing existing content to Y.Doc for the first time, * note that this should not be used to rehydrate a Y.Doc from a database once * collaboration has begun as all history will be lost * * @param blocks the blocks to convert * @returns Y.XmlFragment */ blocksToYXmlFragment(o, r) { return M( this._blocksToProsemirrorNode(o), r ); } /** * Turn a Y.Doc collaborative doc into a BlockNote document (BlockNote style JSON of all blocks) * @returns BlockNote document (BlockNote style JSON of all blocks) */ yDocToBlocks(o, r = "prosemirror") { return this.yXmlFragmentToBlocks(o.getXmlFragment(r)); } /** * This can be used when importing existing content to Y.Doc for the first time, * note that this should not be used to rehydrate a Y.Doc from a database once * collaboration has begun as all history will be lost * * @param blocks */ blocksToYDoc(o, r = "prosemirror") { return _( this._blocksToProsemirrorNode(o), r ); } /** HTML / BLOCKNOTE conversions */ /** * Exports blocks into a simplified HTML string. To better conform to HTML standards, children of blocks which aren't list * items are un-nested in the output HTML. * * @param blocks An array of blocks that should be serialized into HTML. * @returns The blocks, serialized as an HTML string. */ async blocksToHTMLLossy(o) { return this._withJSDOM(async () => T( this.editor.pmSchema, this.editor ).exportBlocks(o, { document: this.jsdom.window.document })); } /** * Serializes blocks into an HTML string in the format that would normally be rendered by the editor. * * Use this method if you want to server-side render HTML (for example, a blog post that has been edited in BlockNote) * and serve it to users without loading the editor on the client (i.e.: displaying the blog post) * * @param blocks An array of blocks that should be serialized into HTML. * @returns The blocks, serialized as an HTML string. */ async blocksToFullHTML(o) { return this._withJSDOM(async () => u( this.editor.pmSchema, this.editor ).serializeBlocks(o, { document: this.jsdom.window.document })); } /** * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and * `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote * doesn't recognize an HTML element's tag, it will parse it as a paragraph or plain text. * @param html The HTML string to parse blocks from. * @returns The blocks parsed from the HTML string. */ async tryParseHTMLToBlocks(o) { return this._withJSDOM(async () => this.editor.tryParseHTMLToBlocks(o)); } /** MARKDOWN / BLOCKNOTE conversions */ /** * Serializes blocks into a Markdown string. The output is simplified as Markdown does not support all features of * BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed. * @param blocks An array of blocks that should be serialized into Markdown. * @returns The blocks, serialized as a Markdown string. */ async blocksToMarkdownLossy(o) { return this._withJSDOM(async () => p(o, this.editor.pmSchema, this.editor, { document: this.jsdom.window.document })); } /** * Creates a list of blocks from a Markdown string. Tries to create `Block` and `InlineNode` objects based on * Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it * as text. * @param markdown The Markdown string to parse blocks from. * @returns The blocks parsed from the Markdown string. */ async tryParseMarkdownToBlocks(o) { return this._withJSDOM(() => this.editor.tryParseMarkdownToBlocks(o)); } /** * If you're using React Context in your blocks, you can use this method to wrap editor calls for importing / exporting / block manipulation * with the React Context Provider. * * Example: * * ```tsx const html = await editor.withReactContext( ({ children }) => ( <YourContext.Provider value={true}>{children}</YourContext.Provider> ), async () => editor.blocksToFullHTML(blocks) ); */ async withReactContext(o, r) { return this._withJSDOM(async () => { const t = S( this.jsdom.window.document.createElement("div") ); y(() => { t.render( i( o, {}, i(w, { editor: this.editor }) ) ); }); try { return await r(); } finally { t.unmount(); } }); } } export { n as ServerBlockNoteEditor }; //# sourceMappingURL=blocknote-server-util.js.map