Skip to content

refactor: blocknote API uses lowest possible level prosemirror primitives #1609

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

Merged
merged 27 commits into from
Apr 16, 2025

Conversation

nperez0111
Copy link
Contributor

@nperez0111 nperez0111 commented Apr 14, 2025

This refactors all of the blocknote APIs to use low-level prosemirror APIs rather than passing around editor instances and dispatching transactions themselves.
Now, block manipulation functions will only update the current transaction it was provided rather than trying to dispatch it. This allows these commands to be chained without having to resort to any smarts for the transaction handling.

supercedes: #1147 #1604 #1584

Resolves: #829

Copy link

vercel bot commented Apr 14, 2025

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

Name Status Preview Updated (UTC)
blocknote ✅ Ready (Inspect) Visit Preview Apr 16, 2025 11:50am
blocknote-website ✅ Ready (Inspect) Visit Preview Apr 16, 2025 11:50am

Copy link

pkg-pr-new bot commented Apr 14, 2025

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/ariakit@1609

@blocknote/code-block

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/code-block@1609

@blocknote/core

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/core@1609

@blocknote/mantine

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/mantine@1609

@blocknote/server-util

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/server-util@1609

@blocknote/react

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/react@1609

@blocknote/shadcn

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/shadcn@1609

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-docx-exporter@1609

@blocknote/xl-multi-column

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-multi-column@1609

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-odt-exporter@1609

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-pdf-exporter@1609

commit: 6e6c5fd

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.

Cool! Do you think you have a preference after these explorations? Specifically looking at which codebase / architecture would be the most maintainable / strongest to work with down the line?

@@ -1073,18 +1076,28 @@ export class BlockNoteEditor<
targetBlock: BlockIdentifier,
placement: "start" | "end" = "start"
) {
setTextCursorPosition(this, targetBlock, placement);
return this.transact((tr) =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

why are some of the methods like this one wrapped in transact?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

transact is convenient in that you don't have to create a transaction & then dispatch it. It also is clear that it would work within a blocknote transaction.

If everything were to use this pattern, then you wouldn't even need to have editor.transaction hold the activeTransaction state, it would just be passed around through the transact calls. So, it ends up mapping more closely to what actually happens

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah that makes sense! I was looking at it from the angle of "only call transact if you're doing multiple BlockNote API calls in a method.

But you're right, we could also use it for convenience + make the methods consistent. I suppose in that case we need to do it for all usages of dispatch, and perhaps even make dispatch private?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call on standardizing it, will refactor to that pattern. I think it will come out nice

@nperez0111
Copy link
Contributor Author

Cool! Do you think you have a preference after these explorations? Specifically looking at which codebase / architecture would be the most maintainable / strongest to work with down the line?

I prefer the explicitness of what we have here, but I think the tradeoff that it makes are methods that need to take a ton of arguments, which is a code smell imo.

Take a look at this method:

const updateBlockTr = <
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema
>(
tr: Transaction,
pmSchema: Schema,
schema: BlockNoteSchema<BSchema, I, S>,
posBeforeBlock: number,
block: PartialBlock<BSchema, I, S>,
blockCache?: BlockCache
) => {

It's annoying that it needs to take 2 schemas (blocknote & prosemirror) and the blockCache. I think that this could be aleviated though. The prosemirror schema has a cached object which can store the blockCache & blocknote schema inside of it. This would make most methods only ever need a tr and schema

@YousefED
Copy link
Collaborator

I prefer the explicitness of what we have here, but I think the tradeoff that it makes are methods that need to take a ton of arguments, which is a code smell imo.

ok! let's go for this approach then 👍

It's annoying that it needs to take 2 schemas (blocknote & prosemirror) and the blockCache. I think that this could be aleviated though. The prosemirror schema has a cached object which can store the blockCache & blocknote schema inside of it. This would make most methods only ever need a tr and schema

We could also just create a type { schema, pmSchema, blockCache }? we could even pass in this from the editor if we'd like (or we create a wrapper object). Not sure if pm cached has any benefits to this (and downside is that it's untyped)

@nperez0111
Copy link
Contributor Author

We could also just create a type { schema, pmSchema, blockCache }? we could even pass in this from the editor if we'd like (or we create a wrapper object). Not sure if pm cached has any benefits to this (and downside is that it's untyped)

I think I like this better. It turns out that from a transaction's document, you can get a schema. So storing it into the schema makes it really convenient to reach for values you may need. Just as a POC, I cleaned up the insertBlocks method with this. I think the API is a lot cleaner, and nodeToBlock & blockToNode become much simpler too: 2980c6c

@YousefED
Copy link
Collaborator

We could also just create a type { schema, pmSchema, blockCache }? we could even pass in this from the editor if we'd like (or we create a wrapper object). Not sure if pm cached has any benefits to this (and downside is that it's untyped)

I think I like this better. It turns out that from a transaction's document, you can get a schema. So storing it into the schema makes it really convenient to reach for values you may need. Just as a POC, I cleaned up the insertBlocks method with this. I think the API is a lot cleaner, and nodeToBlock & blockToNode become much simpler too: 2980c6c

ok! Let's go and see how this works in practice :)

@nperez0111 nperez0111 merged commit 5edf299 into main Apr 16, 2025
4 of 6 checks passed
@nperez0111 nperez0111 deleted the feat/blocknote-transactions-3 branch April 16, 2025 11:36
Copy link

sentry-io bot commented Apr 16, 2025

Suspect Issues

This pull request was deployed and Sentry observed the following issues:

  • ‼️ TypeError: Cannot read properties of undefined (reading 'isInGroup') /examples/ui-components/custom-ui View Issue
  • ‼️ Error: Node should be a bnBlock, but is instead: quote / View Issue
  • ‼️ Error: Block with ID 00f3a57a-f939-430a-b802-a8a9a18362c3 not found / View Issue
  • ‼️ Error: Blocks with the following IDs could not be found in the editor: 0bf17f06-7ab5-459c-9a74-51f1b144f51b / View Issue
  • ‼️ Error: Block with ID 78540072-d0cf-48a1-a7bd-d50696e798f9 not found / View Issue

Did you find this useful? React with a 👍 or 👎

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.

Calling editor.updateBlock() in quick succession breaks the editor's undo/redo functionality
2 participants