TypeCellOS / BlockNote

A React Rich Text Editor that's block-based (Notion style) and extensible. Built on top of Prosemirror and Tiptap.

Home Page:https://www.blocknotejs.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Can't call `blocksToHTMLLossy` on document with React custom blocks without instantiating a BlockNoteView

blx opened this issue · comments

Describe the bug
<what's going wrong!?>

I want to render a document of blocks to HTML, "headlessly" ie. without showing the editor. But when some of my custom blocks are React-rendered (defined via createReactBlockSpec), an error is thrown when exporting to HTML without having instantiated a BlockNoteView.

blocksToHTMLLossy(), somewhere down the stack, depends on contentComponent being set on the inner editor._tiptapEditor (here), which is done by a useEffect in EditorContent that mutates the editor passed in , and EditorContent doesn't come into play without having a BlockNoteView.

I think this specific state of affairs was introduced in #641, but I haven't checked whether this would have worked before that.

For now, I'm just rendering a BlockNoteView on a throwaway React root, in order to get the mutation from EditorContent applied, which is fine, but a bit clunky.

I appreciate this is probably not a common use case 🙃

To Reproduce

const toHTML = async (blocks, schema=null) => BlockNoteEditor.create({
  initialContent: blocks,
  ...(schema ? { schema } : {})
}).blocksToHTMLLossy();

// Default schema block works fine:
console.log(await toHTML([{ type: 'paragraph', content: 'heya' }]));

// React-based custom element doesn't work...
const somethingBlock = createReactBlockSpec(
  { type: 'something', content: 'none', propSchema: {} },
  { render() { return <i>A constant something</i> } }
)
const customSchema = BlockNoteSchema.create({ blockSpecs: { something: somethingBlock } })

// ...Throws error:
console.log(await toHTML([{type: 'something'}], customSchema))

// But this works:
const editor = BlockNoteEditor.create({ initialContent: [{type: 'something'}], schema: customSchema });
await new Promise((resolve, reject) => {
  const tmpRoot = ReactDOMClient.createRoot(document.createElement('div'));
  tmpRoot.render(React.createElement(() => {
    useEffect(() => { resolve(); }, []);
    return React.createElement(BlockNoteView, { editor });
  }));
});
console.log(await editor.blocksToHTMLLossy());

The error thrown is:

Uncaught (in promise) TypeError: t._tiptapEditor.contentComponent is undefined
    Ce ReactRenderUtil.ts:13
    toExternalHTML ReactBlockSpec.tsx:213
    Kt sharedHTMLConversion.ts:70
    serializeNodeInner externalHTMLExporter.ts:66
    serializeFragment index.js:3273
    forEach index.js:250
    serializeFragment index.js:3247
    Kt sharedHTMLConversion.ts:109
    serializeNodeInner externalHTMLExporter.ts:66
    serializeFragment index.js:3273
    forEach index.js:250
    serializeFragment index.js:3247
    Jt sharedHTMLConversion.ts:124
    exportProseMirrorFragment externalHTMLExporter.ts:79
    exportBlocks externalHTMLExporter.ts:90
    blocksToHTMLLossy BlockNoteEditor.ts:907

<clear steps to reproduce are super helpful! Best is to provide an online sandbox, click to create one>

Misc

  • Node version:
  • Package manager:
  • Browser:
  • I'm a sponsor and would appreciate if you could look into this sooner than later 💖

Thanks for reporting this! Although your workaround should work we definitely need to fix this. When implementing #451, we should take this into account. I think the ideal solution would:

a) Make sure that for simple React blocks, setting up the React Root is not required. blocksToHTMLLossy should just work in this case. I think ReactRenderUtil should already cover this, so I'm not sure why this error is actually thrown (might also be recently fixed in #788 )

b) Make it still possible to call blocksToHTMLLossy (and related functions like blocksToMarkdown) in a React tree. This is because custom react blocks might depend on a React Context, so if the consumer wants it should be possible to make these available