Skip to content

refactor: move all blocks to use the custom blocks API #1904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

nperez0111
Copy link
Contributor

@nperez0111 nperez0111 commented Aug 1, 2025

TODO:

  • Move the main BlockNoteSchema to use these new blocks (using a base schema which does not include the default blocks so that tree-shaking can drop the blocks if needed)
    • sub package?
  • Merge BlockConfig, BlockImplementation into actual schema
  • Figure out what to do with blocks that cannot be migrated over (table blocks only?)
  • Check compatibility with: html/markdown, multi-column
  • File blocks, why are they special? Try to streamline them into "just" a custom block. Remove type guards for them, and try to replace them with something more generic or whatever they actually need.

Copy link

vercel bot commented Aug 1, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
blocknote ❌ Failed (Inspect) Aug 4, 2025 4:13pm
blocknote-website ❌ Failed (Inspect) Aug 4, 2025 4:13pm

Copy link
Collaborator

@matthewlipski matthewlipski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just kind of glanced over everything for now with mostly questions

@@ -111,6 +117,37 @@ export function getParseRules(

return props;
},
getContent:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would be possible to return the content element in the parse function, instead of having a separate parseContent? Because I know they're split in the PM API, but I think it would be more intuitive to merge them into 1 function for the custom block API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I thought about this too. Right now, it is pretty easy to do a mapping of HTML -> BlockNote node. But what isn't clear is how to do that mapping for parsing of inner content. We've been going through Prosemirror DOMParser & doing this low-level. I tried a couple of different approaches to do it more high-level & couldn't figure out anything satisfactory.

So, I went with the approach of a "good default", (merge paragraphs to preserve content within the parsed element), and left the rest as an exercise for later. I think what will be useful in this is when we separate out the HTML parsing & exporting from the editor instance, then maybe it will be easier for consumers to parse HTML into the shape that they need 🤷. I marked it as an "advanced" API so that it is clear we are not happy with it. Right now the only time that any of the default blocks need to use parseContent is the list item types, because we added better handling of HTML -> list items.

);

if (existingDecorations.length === 0) {
// Create a widget decoration to display the index
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment needs updating

@@ -32,8 +32,8 @@ export function checkDefaultBlockTypeInSchema<
S
> {
return (
blockType in editor.schema.blockSchema &&
editor.schema.blockSchema[blockType] === defaultBlockSchema[blockType]
blockType in editor.schema.blockSchema //&&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a temp change while the updated blocks are not yet in the default schema right?

name: key,
priority: ext.priority,
addProseMirrorPlugins: () => ext.plugins,
// TODO maybe collect all input rules from all extensions into one plugin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's more intuitive to keep logic for a block/extension within the same extension

@@ -105,3 +105,6 @@ export class BlockNoteSchema<
this.styleSchema = getStyleSchemaFromSpecs(this.styleSpecs) as any;
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Remove?

@@ -21,6 +21,8 @@ export type BlockNoteDOMAttributes = Partial<{
[DOMElement in BlockNoteDOMElement]: Record<string, string>;
}>;

// TODO we should remove FileBlockConfig, and only use BlockConfig
// Ideally something like this would be represented via `groups: ["file"]` or similar
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a file field to the new meta property in the config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure on this. It feels wrong to me that files are somehow different. Like why is there a file field & not a listItem field? I am not in favor of adding more properties than needed, since it adds complexity to each custom block that people can add.

I don't fully understand why this is even required at this point

@@ -0,0 +1,154 @@
// Based on https://github.com/n1ru4l/toposort/blob/main/src/toposort.ts (MIT)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment for why this file was added?

Copy link
Collaborator

@YousefED YousefED left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice. posted some first thoughts / questions!

return true;
}),
},
inputRules: [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool that there's a BN-style API for this. Can we do the same for keyboardshortcuts you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about what that would look like for keyboard shortcuts, but the problem is that keyboard shortcuts can sort of just do anything. Some usecases I thought of were:

  • bringing up some menu items
  • triggering some action (e.g. stop AI generation)
  • inserting a new element
  • modifying an existing block

These sorts of cases are too disparate to really "unify" into a sensible API. Fundamental tension between composability & centralization.

* Advanced parsing function that controls how content within the block is parsed.
* This is not recommended to use, and is only useful for advanced use cases.
*/
parseContent?: (options: { el: HTMLElement; schema: Schema }) => Fragment;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to do this in a way that we don't expose Prosemirror APIs (i.e.: not expose Fragment) - not sure this is possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1904 (comment)

Yea I couldn't find a great way to do this. Especially given that the only case that really needs this are list items

* The BlockNote editor instance
*/
editor: BlockNoteEditor<Record<TName, BlockConfig<TName, TProps>>>,
) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions (dom / contentDOM / ignoreMutation) are very prosemirror specific. For now out of scope (as they're also in the existing API and this PR is about moving blocks over), but don't think this is ideal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, agreed, but not looking to rock that boat right now

},
}),
() => [
createBlockNoteExtension({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the reasoning again to define keyboardshortcuts / inputrules in a separate argument + createBlockNoteExtension?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces keyboard shortcuts & inputrules to the extension API because we didn't have a way of doing them before.

I considered exposing them to both APIs (e.g. a custom block has an input rule API & so does the extension API), but that only added complexity.

Allowing extensions to be included to the block API is useful in that some things may require an extension to function (e.g. code block syntax highlighting, numbered list decorations).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants