Skip to content

feat: math block - #2857

Merged
nperez0111 merged 5 commits into
mainfrom
code-block-previews
Aug 13, 2026
Merged

feat: math block#2857
nperez0111 merged 5 commits into
mainfrom
code-block-previews

Conversation

@matthewlipski

@matthewlipski matthewlipski commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR adds a math block which renders LaTeX, math inline content which does the same, and a diagram block which renders Mermaid diagrams. These are under the xl-math-block and xl-diagram-block packages respectively.

To facilitate the new blocks, some changes to the core editor and syntax highlighting have been made.

Plain Text Content

Blocks and inline content can now hold plain text instead of rich text. Internally, this is represented as an array of StyledText objects, but with an empty styles field:

type PlainContent = {
  type: "text";
  text: string;
  styles: {};
}[];

All of the new blocks/inline content use this, and the code block has also been updated to use plain content as well.

Syntax Highlighting

Rather than syntax highlighting being bundled with the code block, it's now a separate configurable extension. Configuration works mostly the same, and if multiple instances of the extension are passed, they are deduplicated automatically. This change was necessary as the new blocks/inline content also need syntax highlighting, so it no longer makes sense to couple that to the code block.

Code Block

The code block has received a small refactor in addition to the already mentioned changes, splitting it into smaller, more composable pieces.

Rationale

There has been demand for a math block for a while, and to a lesser extent a diagram block too. This PR implements both as well as making more foundational changes to do so properly.

Changes

  • Updated docs.
  • Added math/diagram blocks/inline content to existing interoperability examples.
  • Added examples for adding math/diagram blocks/inline content to the editor.
  • Refactored core code block.
  • Added InlineContentBoundaryEditExtension for better keyboard handling around empty inline content.
  • Added SourceBlockWithPreviewExtension/SourceInlineContentWithPreviewExtension. These work in tandem with the SourceBlockWithPreview/SourceInlineContentWithPreview components, which show a preview of source code in the block's text content. Clicking/hitting enter on the preview opens a popup which contains the source code. The extensions handle mostly keyboard navigation, and are added to the editor by default. In order to work properly, blocks using the components must set the meta.hasPreview flag.
  • Added SyntaxHighlightingExtension, which is used for syntax highlighting and automatically deduplicates other instances of itself. Blocks/inline content can declare which language they need to be highlighted in using meta.highlight in their config.
  • Added support for "plain" content for both blocks and inline content.
  • Added math/diagram blocks/inline content, alongside slash menu and block type select items to use them in the editor.
  • Added support for math/diagram exporters. For math, the blocks/inline content are converted to an editable equation in the target format, and fallback to an image of the equation if the target format doesn't support that. Diagram blocks are always converted to images.
  • Adjusted styling for selected blocks.

Impact

N/A

Testing

  • Added unit tests for SyntaxHighlighting extension & math block.
  • Added math/diagram test cases for math/diagram import/export.

Screenshots/Video

TODO

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

N/A

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a math block that renders LaTeX formulas as MathML with an editable source editor popup
    • Added code block preview functionality with interactive source code popup
    • Syntax highlighting configuration moved to the editor level for simplified setup
  • Documentation

    • Updated code block documentation to clarify syntax highlighting configuration
  • Internationalization

    • Added "Add source code" button text translations across 24+ languages

@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Aug 13, 2026 2:30pm
blocknote-website Ready Ready Preview Aug 13, 2026 2:30pm

Request Review

@matthewlipski
matthewlipski requested a review from nperez0111 June 16, 2026 10:21
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors the code block implementation in @blocknote/core into reusable helper modules, moves Shiki syntax highlighting to a dedicated SyntaxHighlightingExtension configured at the editor level via BlockNoteEditorOptions, and introduces a new @blocknote/math-block package with KaTeX-based preview rendering, MathML import/export, and a popup source editor using FloatingUI.

Changes

Code Block Refactor and SyntaxHighlighting Extension

Layer / File(s) Summary
Code block and node-view type contracts
packages/core/src/blocks/Code/CodeBlockOptions.ts, packages/core/src/schema/blocks/types.ts, packages/core/src/schema/blocks/createSpec.ts, packages/core/src/index.ts
Introduces CodeBlockOptions, CodeBlockPreview, and getLanguageId in a dedicated module; adds optional update?(node): boolean to BlockImplementation render return; re-exports new types from the core public index.
Editor-level syntax highlighting pipeline
packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.ts, .../shiki.ts, .../SyntaxHighlighting.test.ts, packages/core/src/editor/BlockNoteEditor.ts, .../extensions.ts, packages/core/src/extensions/index.ts, packages/code-block/src/index.ts, packages/code-block/src/index.test.ts, packages/code-block/package.json, packages/core/package.json
Adds SyntaxHighlightingOptions, SyntaxHighlightingExtension, and a lazy Shiki plugin with cached per-language loading; wires the new syntaxHighlighting option into BlockNoteEditorOptions; promotes createHighlighter to a top-level export from @blocknote/code-block.
Code block parse, render helpers, and CSS
packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts, .../render/createSourceBlock.ts, .../render/createSourceBlockWithPreview.ts, .../toExternalHTML/createPreCode.ts, packages/core/src/editor/Block.css
Introduces helpers for PRE/CODE HTML parsing, source-block rendering with optional language select, and a preview-with-source-popup built on FloatingUI; adds all CSS for the popup layout, visibility toggling, error state, and "add source" placeholder button.
Code block keyboard shortcuts and input rules
packages/core/src/blocks/Code/helpers/extensions/createCodeKeyboardShortcutsExtension.ts
Implements createCodeKeyboardShortcutsExtension with Delete/Tab/Enter/Shift-Enter handlers and a fenced-code input rule that converts triple-backtick syntax into code block nodes.
Block spec wiring, barrel exports, i18n, and example/doc updates
packages/core/src/blocks/Code/block.ts, packages/core/src/blocks/index.ts, packages/core/src/i18n/locales/*, examples/04-theming/06-code-block/..., examples/04-theming/07-custom-code-block/..., docs/content/docs/features/blocks/code-blocks.mdx
Rewires createCodeBlockSpec to delegate all logic to helpers; adds helper modules to the blocks barrel; adds add_source_button_text to all 22 locales; updates theming examples and docs to configure createHighlighter at the editor level via syntaxHighlighting.

New @blocknote/math-block Package

Layer / File(s) Summary
Math block package scaffolding
packages/math-block/package.json, packages/math-block/LICENSE, packages/math-block/.gitignore, packages/math-block/tsconfig.json, packages/math-block/vite.config.ts, packages/math-block/vitestSetup.ts, packages/math-block/src/vite-env.d.ts
Adds the full package manifest (MPL-2.0, @blocknote/math-block), build config with conditional monorepo source aliases, TypeScript strict config, and Vitest setup hooks.
Math block schema, helpers, and exports
packages/math-block/src/block.ts, packages/math-block/src/helpers/getMathSource.ts, packages/math-block/src/helpers/parse/parseMathML.ts, packages/math-block/src/helpers/render/createMathPreview.ts, packages/math-block/src/helpers/toExternalHTML/createMathML.ts, packages/math-block/src/index.ts
Defines the math block config/spec with inline content; implements LaTeX source extraction, MathML-to-LaTeX parsing (with TeX annotation preference), KaTeX two-pass preview rendering with error capture, MathML external HTML export, and package-level re-exports.
Math block behavior tests
packages/math-block/src/block.test.ts
Tests keyboard navigation (Enter/Escape popup toggling, arrow-key behavior with popup open/closed, character and deletion swallowing vs. deferral, Ctrl/Cmd passthrough), document-edge arrow handling, and mousedown-triggered popup with cursor positioned at end of source.
Math block example app and playground/docs wiring
examples/06-custom-schema/09-math-block/..., playground/src/examples.gen.tsx, playground/vite.config.ts, docs/package.json
Adds a complete custom-schema math-block example (HTML shell, React entrypoint, App component with custom slash menu, package/TS/Vite configs); registers the example in the playground; adds @blocknote/math-block as a docs workspace dependency.

Sequence Diagram

sequenceDiagram
  participant App
  participant BlockNoteEditor
  participant SyntaxHighlightingExtension
  participant lazyShikiPlugin
  participant createSourceBlockWithPreview
  participant FloatingUI

  App->>BlockNoteEditor: useCreateBlockNote({ syntaxHighlighting: { createHighlighter }, schema })
  BlockNoteEditor->>SyntaxHighlightingExtension: scan blockSpecs for inline content types
  SyntaxHighlightingExtension->>lazyShikiPlugin: build ProseMirror highlight plugin
  lazyShikiPlugin-->>SyntaxHighlightingExtension: plugin (lazy per-language load on parse)

  App->>createSourceBlockWithPreview: render math/code block
  createSourceBlockWithPreview->>FloatingUI: autoUpdate + computePosition for popup
  FloatingUI-->>createSourceBlockWithPreview: positioned popup DOM
  createSourceBlockWithPreview-->>App: { dom, contentDOM, update, ignoreMutation, destroy }
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • nperez0111

Poem

🐇 A rabbit hops through syntax trees so bright,
Math formulas rendered — LaTeX delight!
The shiki highlighter now lives up above,
Code blocks refactored with modular love.
A popup blooms where formulas appear,
New packages hop in — the monorepo cheers! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the primary feature introduced by the pull request: a math block.
Description check ✅ Passed The description covers all required sections and explains the feature, rationale, changes, impact, testing, and checklist status.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch code-block-previews

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-13 14:55 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (1)
packages/math-block/src/helpers/toExternalHTML/createMathML.ts (1)

8-17: Consider explicitly pinning the Temml trust mode.

At lines 8–13, while Temml's default trust value is false (which is secure), explicitly setting trust: false makes the security intent clear and protects against potential upstream default changes.

Suggested change
  const mathml = temml.renderToString(getMathSource(block), {
    displayMode: true,
    annotate: true,
+   trust: false,
    // Export gracefully renders invalid LaTeX rather than throwing.
    throwOnError: false,
  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/math-block/src/helpers/toExternalHTML/createMathML.ts` around lines
8 - 17, The temml.renderToString() call on line 8 does not explicitly set the
trust property in its options object. Add trust: false to the options passed to
temml.renderToString() alongside the existing displayMode, annotate, and
throwOnError properties to explicitly document the security intent and protect
against potential upstream default changes to Temml's trust setting.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/06-custom-schema/09-math-block/index.html`:
- Line 1: Add the HTML5 DOCTYPE declaration as the very first line of the file
before the <html> tag. Insert `<!doctype html>` on line 1, which will move the
existing `<html lang="en">` tag to line 2. This ensures the browser renders in
standards mode instead of triggering quirks mode, and makes the HTML document
compliant with proper HTML5 structure requirements.

In `@examples/06-custom-schema/09-math-block/main.tsx`:
- Line 4: The import statement for App specifies the wrong file extension (.jsx
instead of .tsx). Change the import path from ./src/App.jsx to ./src/App.tsx to
match the actual file name and allow TypeScript module resolution to succeed
with the allowJs: false configuration.

In `@examples/06-custom-schema/09-math-block/vite.config.ts`:
- Around line 16-27: The vite alias configuration in the conditional block is
using `../../` in the path.resolve calls for both `@blocknote/core` and
`@blocknote/react`, but it should use `../../../` to resolve the packages from the
correct directory level. Update each path string passed to path.resolve (for
"`@blocknote/core`" and "`@blocknote/react`") to include one additional `../` in the
relative path so the source-alias branch activates correctly when the packages
source directory exists.

In `@packages/core/src/blocks/Code/CodeBlockOptions.ts`:
- Around line 72-80: The getLanguageId function performs case-sensitive
comparisons when matching the languageName parameter against language aliases
and IDs, causing inputs like `TS` or `Js` to fail to map to their canonical
lowercase configured IDs. Fix this by normalizing all comparisons to be
case-insensitive: convert the incoming languageName parameter to lowercase, and
convert both the aliases from the supportedLanguages configuration and the id
values to lowercase before performing the comparison checks in the find
callback.

In `@packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts`:
- Around line 19-22: The language-class detection in the code is too broad
because it uses includes("language-") which matches the substring anywhere in
the class name, potentially capturing false positives like classes containing
"language-" in the middle of the string. Replace the includes("language-") check
with startsWith("language-") to ensure only class names that actually begin with
"language-" are considered valid language identifiers, restricting the match to
the beginning of the string only.

In `@packages/core/src/blocks/Code/helpers/render/createCodeBlockWrapper.ts`:
- Around line 10-12: The language lookup on line 11 does not normalize language
aliases before accessing the supportedLanguages map. If the language value is an
alias such as "ts", the lookup will fail and createPreview will be skipped,
causing fallback to source-only rendering. Resolve the language string through a
shared language-ID resolver to normalize it to its canonical form before using
it as the lookup key in supportedLanguages on line 11.

In `@packages/core/src/blocks/Code/helpers/render/createSourceBlock.ts`:
- Around line 8-25: The language value being assigned to select.value is not
validated against the supported languages, which can result in no option being
selected if the stored language is an alias or outdated identifier. Before
assigning select.value in the language dropdown initialization, add logic to
resolve the language variable to a canonical key from
options.supportedLanguages. Check if the language exists as a key in the
supportedLanguages object; if not, fall back to the default language or the
first available option. Only after normalizing the language should you assign it
to select.value.

In `@packages/core/src/editor/Block.css`:
- Around line 482-517: Remove the empty lines that appear before CSS property
declarations in the `.bn-code-block-source-popup` and its related child
selectors (including `.bn-code-block-source-popup > div > select`,
`.bn-code-block-source-popup > div > select > option`, and
`.bn-code-block-source-popup > pre`). These blank lines before declarations are
triggering Stylelint violations. Go through each CSS rule block and delete the
extra blank lines while maintaining proper formatting between distinct rule
blocks.

In `@packages/core/src/extensions/SyntaxHighlighting/shiki.ts`:
- Around line 29-32: The code caches the Shiki highlighter and parser on
globalThis, causing all editor instances to share the same cache regardless of
their individual syntaxHighlighting.createHighlighter configuration. This
violates the editor-level configuration contract. Remove the global caching
mechanism by eliminating the globalThis symbol-keyed properties and instead
store the highlighter and parser at the instance level (as properties on an
editor-specific object or context). Specifically, refactor the
globalThisForShiki type definition and related caching logic at the three
affected sites in packages/core/src/extensions/SyntaxHighlighting/shiki.ts
(lines 29-32, 44-46, and 74-76) to use instance-specific storage instead of
globalThis, ensuring each editor instance maintains its own separate highlighter
and parser cache.

In `@packages/math-block/src/helpers/getMathSource.ts`:
- Around line 8-11: In the getMathSource function's array mapping logic, add a
defensive check before using the `in` operator on the node variable. Since
block.content is of unknown type, the array items could be primitives, null, or
non-objects that would throw a TypeError when used with the `in` operator.
Modify the map callback to first verify that node is an object (using typeof
node === "object" && node !== null or a similar guard) before attempting to
access the "text" property, ensuring the function gracefully handles unexpected
data types without crashing.

In `@packages/math-block/vite.config.ts`:
- Around line 52-56: The vite.config.ts build configuration is currently
including devDependencies in the externals check along with dependencies and
peerDependencies. Remove the spread of pkg.devDependencies from the
Object.keys() call in the external configuration block so that only actual
runtime dependencies (dependencies and peerDependencies) are marked as external.
This ensures that accidental imports of dev-only packages will fail during the
build rather than being shipped unresolved to library consumers.

---

Nitpick comments:
In `@packages/math-block/src/helpers/toExternalHTML/createMathML.ts`:
- Around line 8-17: The temml.renderToString() call on line 8 does not
explicitly set the trust property in its options object. Add trust: false to the
options passed to temml.renderToString() alongside the existing displayMode,
annotate, and throwOnError properties to explicitly document the security intent
and protect against potential upstream default changes to Temml's trust setting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9b64910-b7e1-4084-b088-4b77233bd8c5

📥 Commits

Reviewing files that changed from the base of the PR and between a28f472 and 6ccc955.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (54)
  • docs/content/docs/features/blocks/code-blocks.mdx
  • docs/package.json
  • examples/04-theming/06-code-block/src/App.tsx
  • examples/04-theming/07-custom-code-block/src/App.tsx
  • examples/06-custom-schema/09-math-block/.bnexample.json
  • examples/06-custom-schema/09-math-block/README.md
  • examples/06-custom-schema/09-math-block/index.html
  • examples/06-custom-schema/09-math-block/main.tsx
  • examples/06-custom-schema/09-math-block/package.json
  • examples/06-custom-schema/09-math-block/src/App.tsx
  • examples/06-custom-schema/09-math-block/tsconfig.json
  • examples/06-custom-schema/09-math-block/vite.config.ts
  • packages/code-block/package.json
  • packages/code-block/src/index.test.ts
  • packages/code-block/src/index.ts
  • packages/core/package.json
  • packages/core/src/blocks/Code/CodeBlockOptions.ts
  • packages/core/src/blocks/Code/block.test.ts
  • packages/core/src/blocks/Code/block.ts
  • packages/core/src/blocks/Code/helpers/extensions/createCodeKeyboardShortcutsExtension.ts
  • packages/core/src/blocks/Code/helpers/extensions/createPreviewSourceNavigationExtension.ts
  • packages/core/src/blocks/Code/helpers/extensions/createPreviewSourceSelectionExtension.ts
  • packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts
  • packages/core/src/blocks/Code/helpers/render/createCodeBlockWrapper.ts
  • packages/core/src/blocks/Code/helpers/render/createPreviewWithSourcePopup.ts
  • packages/core/src/blocks/Code/helpers/render/createSourceBlock.ts
  • packages/core/src/blocks/Code/helpers/toExternalHTML/createPreCode.ts
  • packages/core/src/blocks/Code/shiki.ts
  • packages/core/src/blocks/index.ts
  • packages/core/src/editor/Block.css
  • packages/core/src/editor/BlockNoteEditor.ts
  • packages/core/src/editor/managers/ExtensionManager/extensions.ts
  • packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.test.ts
  • packages/core/src/extensions/SyntaxHighlighting/SyntaxHighlighting.ts
  • packages/core/src/extensions/SyntaxHighlighting/shiki.ts
  • packages/core/src/extensions/index.ts
  • packages/core/src/index.ts
  • packages/core/src/schema/blocks/createSpec.ts
  • packages/core/src/schema/blocks/types.ts
  • packages/math-block/.gitignore
  • packages/math-block/LICENSE
  • packages/math-block/package.json
  • packages/math-block/src/block.test.ts
  • packages/math-block/src/block.ts
  • packages/math-block/src/helpers/getMathSource.ts
  • packages/math-block/src/helpers/parse/parseMathML.ts
  • packages/math-block/src/helpers/render/createMathPreview.ts
  • packages/math-block/src/helpers/toExternalHTML/createMathML.ts
  • packages/math-block/src/index.ts
  • packages/math-block/src/vite-env.d.ts
  • packages/math-block/tsconfig.json
  • packages/math-block/vite.config.ts
  • packages/math-block/vitestSetup.ts
  • playground/src/examples.gen.tsx
💤 Files with no reviewable changes (1)
  • packages/core/src/blocks/Code/shiki.ts

Comment thread examples/06-custom-schema/09-math-block/index.html
Comment thread examples/06-custom-schema/09-math-block/main.tsx
Comment thread examples/06-custom-schema/09-math-block/vite.config.ts
Comment thread packages/core/src/blocks/Code/CodeBlockOptions.ts
Comment thread packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts
Comment thread packages/core/src/blocks/Code/helpers/render/createSourceBlock.ts Outdated
Comment thread packages/core/src/editor/Block.css Outdated
Comment thread packages/core/src/extensions/SyntaxHighlighting/shiki.ts
Comment thread packages/math-block/src/helpers/getMathSource.ts Outdated
Comment thread packages/math-block/vite.config.ts

@YousefED YousefED left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Exciting!

Looking at the code from a high level (no user testing or deep dive into the functions), I have the following high-level questions:

  • Should we use React or Vanilla for the components? (especially thinking about customizability)
  • Curious if the current code design (with the keyboard handlers) also scales to interfaces where the editor is not in a pop-up (e.g.: Notion / TypeCell style editors that can collapse)

Comment thread docs/content/docs/features/blocks/code-blocks.mdx Outdated
Comment thread packages/core/src/blocks/Code/helpers/render/createPreviewWithSourcePopup.ts Outdated
Comment thread packages/core/src/blocks/Code/CodeBlockOptions.ts Outdated

@YousefED YousefED left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Here's some feedback after a super early round of user-testing:

  • pressing up after “s” doesn’t go to the first popup:
Image
  • esc should close popup

  • formattingtoolbar appears when highlighting text.

  • I’m allowed to “bold” things. I think we should see if we can make sure at a lower level code blocks cannot allow formatted text. This probably also caused the issue with the exporters recently (remember?)

  • preview opens when dragging block

  • keyboard is buggy when two math blocks sit above/below combined with the “gapcursor”

  • this is a bit misaligned:

Image

This might also be nice for testing:

  • slashmenu support in demos
  • collaboration example

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/math-block/src/block.test.ts (1)

762-762: ⚡ Quick win

Prefer semantic MathML assertions over exact full-string equality.

This assertion is fragile to harmless serialization differences. Assert the exported node structure and application/x-tex annotation content instead of matching one exact serialized string.

Suggested test hardening
- expect(serializedMathML).toBe(
-   `"<math xmlns="http://www.w3.org/1998/Math/MathML" ... </math>"`,
- );
+ const parsed = new DOMParser().parseFromString(serializedMathML, "text/html");
+ const math = parsed.querySelector("math");
+ expect(math).not.toBeNull();
+ const tex = math
+   ?.querySelector('annotation[encoding="application/x-tex"]')
+   ?.textContent
+   ?.trim();
+ expect(tex).toBe("a^2 + b^2 = c^2");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/math-block/src/block.test.ts` at line 762, The test assertion at
line 762 performs brittle exact full-string matching of the entire serialized
MathML output, which is fragile to harmless serialization differences. Instead
of comparing the complete serialized string, extract and assert specific
structural elements: validate the exported node structure and specifically check
the application/x-tex annotation content value rather than relying on
full-string equality to make the test more resilient to formatting or
serialization changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/core/src/blocks/Code/helpers/extensions/createPreviewSourceNavigationExtension.ts`:
- Around line 175-218: The Enter and Escape handlers are missing
scrollIntoView() calls after setting the selection, creating an inconsistency
with the arrow handlers (lines 127, 147) which do include these calls. Add
tr.scrollIntoView() after each tr.setSelection() call in both the Enter handler
and the Escape handler to ensure the viewport scrolls to keep the focused block
visible when the selection changes.

---

Nitpick comments:
In `@packages/math-block/src/block.test.ts`:
- Line 762: The test assertion at line 762 performs brittle exact full-string
matching of the entire serialized MathML output, which is fragile to harmless
serialization differences. Instead of comparing the complete serialized string,
extract and assert specific structural elements: validate the exported node
structure and specifically check the application/x-tex annotation content value
rather than relying on full-string equality to make the test more resilient to
formatting or serialization changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49fca915-9fa2-4f91-9469-351dd45a6d53

📥 Commits

Reviewing files that changed from the base of the PR and between ae49edc and de7b1df.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tests/src/unit/core/schema/__snapshots__/blocks.json is excluded by !**/__snapshots__/**
📒 Files selected for processing (14)
  • packages/core/src/blocks/Code/CodeBlockOptions.ts
  • packages/core/src/blocks/Code/helpers/extensions/createPreviewSourceNavigationExtension.ts
  • packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts
  • packages/core/src/blocks/Code/helpers/render/createCodeBlockWrapper.ts
  • packages/core/src/blocks/Code/helpers/render/createPreviewWithSourcePopup.ts
  • packages/core/src/blocks/Code/helpers/render/createSourceBlock.ts
  • packages/core/src/blocks/index.ts
  • packages/core/src/editor/Block.css
  • packages/math-block/package.json
  • packages/math-block/src/block.test.ts
  • packages/math-block/src/block.ts
  • packages/math-block/src/helpers/parse/parseMathML.ts
  • packages/math-block/src/helpers/render/createMathPreview.ts
  • packages/math-block/src/helpers/toExternalHTML/createMathML.ts
💤 Files with no reviewable changes (2)
  • packages/core/src/blocks/index.ts
  • packages/math-block/src/helpers/parse/parseMathML.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/core/src/blocks/Code/helpers/parse/parsePreCode.ts
  • packages/core/src/blocks/Code/helpers/render/createCodeBlockWrapper.ts
  • packages/core/src/blocks/Code/helpers/render/createSourceBlock.ts
  • packages/math-block/src/block.ts
  • packages/core/src/blocks/Code/CodeBlockOptions.ts
  • packages/core/src/editor/Block.css
  • packages/core/src/blocks/Code/helpers/render/createPreviewWithSourcePopup.ts

@matthewlipski

Copy link
Copy Markdown
Collaborator Author

Exciting!

Looking at the code from a high level (no user testing or deep dive into the functions), I have the following high-level questions:

  • Should we use React or Vanilla for the components? (especially thinking about customizability)
  • Curious if the current code design (with the keyboard handlers) also scales to interfaces where the editor is not in a pop-up (e.g.: Notion / TypeCell style editors that can collapse)
  • No strong preference for this, though admittedly React would be cleaner and these days I don't know if anyone is using BlockNote outside React.
  • Maybe worth extending the custom block API with handling when the text cursor moves in to/out of the block, as imo these are the biggest pain points in my experience with writing the keyboard handling for the math block. Would have to be a bit limited though without exposing ProseMirror APIs.

@pkg-pr-new

pkg-pr-new Bot commented Jun 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

@blocknote/ariakit

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

@blocknote/code-block

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

@blocknote/core

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

@blocknote/diagram-block

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/diagram-block@2857

@blocknote/mantine

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

@blocknote/math-block

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/math-block@2857

@blocknote/react

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

@blocknote/server-util

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

@blocknote/shadcn

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

@blocknote/xl-ai

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-ai@2857

@blocknote/xl-docx-exporter

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

@blocknote/xl-email-exporter

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

@blocknote/xl-multi-column

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

@blocknote/xl-odt-exporter

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

@blocknote/xl-pdf-exporter

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

commit: f4c747e

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/math-block/src/block.test.ts (1)

193-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert defaultPrevented in edge-arrow tests.

Both tests say the key handling “defers to the default,” but they only assert cursor position. Add expect(pressKey(...)).toBe(false) to verify no interception happened.

Suggested test tightening
   it("ArrowLeft with no previous block defers to the default", () => {
@@
-      pressKey("math", "ArrowLeft");
+      expect(pressKey("math", "ArrowLeft")).toBe(false);
       expect(editor.getTextCursorPosition().block.id).toBe("math");
   });

   it("ArrowRight with no next block defers to the default", () => {
@@
-      pressKey("math", "ArrowRight");
+      expect(pressKey("math", "ArrowRight")).toBe(false);
       expect(editor.getTextCursorPosition().block.id).toBe("math");
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/math-block/src/block.test.ts` around lines 193 - 217, In both tests
within the "at the document edges" describe block, you need to add assertions to
verify that the arrow key events were not intercepted. After each pressKey()
call (in "ArrowLeft with no previous block defers to the default" and
"ArrowRight with no next block defers to the default" tests), add an expectation
that pressKey returns false to confirm the default behavior was not prevented,
rather than only asserting the cursor position remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/blocks/Code/block.ts`:
- Around line 36-46: The render function in the Code block is directly accessing
createPreview property from options.supportedLanguages without checking if the
language key exists first. Add a guard condition to verify that
block.props.language is a valid key in options.supportedLanguages before
attempting to access its createPreview property. If the language is not
supported, the language configuration object should either be omitted or handled
gracefully to prevent throwing an error when unsupported languages are
encountered.

In `@packages/core/src/blocks/Code/helpers/render/createSourceBlock.ts`:
- Around line 29-39: The handleLanguageChange function unconditionally calls
editor.updateBlock without checking if the editor is currently editable at event
time. Although the listener attachment is guarded by editor.isEditable at mount,
this guard does not apply if the editor's editability changes after the listener
is attached. Move the editor.isEditable check inside the handleLanguageChange
function body to guard the editor.updateBlock call itself, ensuring that state
changes are prevented when the editor is read-only at the moment the event
fires.

In
`@packages/core/src/blocks/Code/helpers/render/createSourceBlockWithPreview.ts`:
- Line 297: The sourceBlockPopup element is set to inert at line 297, but the
setSourcePopupOpen function (lines 313-314) that controls opening and closing
the popup never toggles the inert state back to false when opening. Update the
setSourcePopupOpen function to conditionally set sourceBlockPopup.inert based on
whether the popup is being opened or closed, ensuring it becomes interactive
when open and inert when closed.

---

Nitpick comments:
In `@packages/math-block/src/block.test.ts`:
- Around line 193-217: In both tests within the "at the document edges" describe
block, you need to add assertions to verify that the arrow key events were not
intercepted. After each pressKey() call (in "ArrowLeft with no previous block
defers to the default" and "ArrowRight with no next block defers to the default"
tests), add an expectation that pressKey returns false to confirm the default
behavior was not prevented, rather than only asserting the cursor position
remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f917fcf8-de6f-4a16-9ac2-fd01f7f596b3

📥 Commits

Reviewing files that changed from the base of the PR and between 719fd7a and ed8c681.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (37)
  • examples/06-custom-schema/09-math-block/.bnexample.json
  • examples/06-custom-schema/09-math-block/package.json
  • examples/06-custom-schema/09-math-block/src/App.tsx
  • examples/06-custom-schema/09-math-block/vite-env.d.ts
  • packages/core/src/blocks/Code/CodeBlockOptions.ts
  • packages/core/src/blocks/Code/block.ts
  • packages/core/src/blocks/Code/helpers/render/createSourceBlock.ts
  • packages/core/src/blocks/Code/helpers/render/createSourceBlockWithPreview.ts
  • packages/core/src/blocks/index.ts
  • packages/core/src/editor/Block.css
  • packages/core/src/i18n/locales/ar.ts
  • packages/core/src/i18n/locales/de.ts
  • packages/core/src/i18n/locales/en.ts
  • packages/core/src/i18n/locales/es.ts
  • packages/core/src/i18n/locales/fa.ts
  • packages/core/src/i18n/locales/fr.ts
  • packages/core/src/i18n/locales/he.ts
  • packages/core/src/i18n/locales/hr.ts
  • packages/core/src/i18n/locales/is.ts
  • packages/core/src/i18n/locales/it.ts
  • packages/core/src/i18n/locales/ja.ts
  • packages/core/src/i18n/locales/ko.ts
  • packages/core/src/i18n/locales/nl.ts
  • packages/core/src/i18n/locales/no.ts
  • packages/core/src/i18n/locales/pl.ts
  • packages/core/src/i18n/locales/pt.ts
  • packages/core/src/i18n/locales/ru.ts
  • packages/core/src/i18n/locales/sk.ts
  • packages/core/src/i18n/locales/uk.ts
  • packages/core/src/i18n/locales/uz.ts
  • packages/core/src/i18n/locales/vi.ts
  • packages/core/src/i18n/locales/zh-tw.ts
  • packages/core/src/i18n/locales/zh.ts
  • packages/math-block/src/block.test.ts
  • packages/math-block/src/block.ts
  • playground/src/examples.gen.tsx
  • playground/vite.config.ts
✅ Files skipped from review due to trivial changes (17)
  • playground/vite.config.ts
  • examples/06-custom-schema/09-math-block/vite-env.d.ts
  • examples/06-custom-schema/09-math-block/.bnexample.json
  • packages/core/src/i18n/locales/es.ts
  • packages/core/src/i18n/locales/ko.ts
  • packages/core/src/i18n/locales/fr.ts
  • packages/core/src/i18n/locales/ru.ts
  • packages/core/src/i18n/locales/de.ts
  • packages/core/src/i18n/locales/ja.ts
  • packages/core/src/i18n/locales/pl.ts
  • packages/core/src/i18n/locales/it.ts
  • examples/06-custom-schema/09-math-block/package.json
  • packages/core/src/i18n/locales/uz.ts
  • packages/core/src/i18n/locales/nl.ts
  • packages/core/src/i18n/locales/vi.ts
  • packages/core/src/i18n/locales/en.ts
  • playground/src/examples.gen.tsx

Comment thread packages/core/src/blocks/Code/block.ts
Comment thread packages/core/src/blocks/Code/helpers/render/createCodeBlock.ts
Comment thread packages/core/src/blocks/Code/helpers/render/createSourceBlockWithPreview.ts Outdated
Comment thread docs/content/docs/features/blocks/code-blocks.mdx Outdated
Comment thread packages/core/src/blocks/Code/helpers/render/createSourceBlockWithPreview.ts Outdated
Comment thread packages/core/src/blocks/Code/helpers/render/createSourceBlockWithPreview.ts Outdated
Comment thread packages/core/src/blocks/Code/helpers/render/createSourceBlockWithPreview.ts Outdated
@nperez0111

Copy link
Copy Markdown
Contributor

Alright @matthewlipski, so we decided on the React topic that it is still undecided for now, but we would like to write an example using React for the math block and then see from there whether we would want it to be the default implementation or not.

I think we can move some of the logic that is currently in the render path right into the extension, but we can see how that plays out.

@YousefED

Copy link
Copy Markdown
Collaborator

Note on patches/katex@0.16.47.patch: prod builds (incl. the Vercel preview) rendered every formula as "Error" because rolldown/oxc mangles lone-surrogate escapes when re-emitting string literals ("\uD800"�d800), corrupting KaTeX's lexer token regex — \sqrt lexed as \s ("Undefined control sequence"). Dev builds don't rewrite the strings, so it only showed in builds.

The patch rewrites the affected regex strings to ASCII-only escapes ("[\\uD800-\\uDBFF]"), which is semantically identical for new RegExp() and immune to the bug. Temporary: oxc has this scheduled as a Vite 8 blocker for Q3 2026 (oxc-project/oxc#15524, oxc-project/oxc#16886) — the patch can be dropped once vite-plus ships a fixed oxc.

@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deployment failed for project blocknote-website with the following error:

The `vercel.json` schema validation failed with the following message: `buildCommand` should NOT be longer than 256 characters

Learn More: https://vercel.com/docs/concepts/projects/project-configuration

Toolchain and workspace plumbing, split out of the math/diagram block work
so the feature commits stay readable.

- Migrate the workspace to TypeScript 7 and upgrade the oxlint toolchain.
- Give twoslash a TypeScript 5 via an npm alias (`typescript-5`) injected
  into `transformerTwoslash`, since twoslash still needs the classic JS
  compiler API that TypeScript 7 dropped. Upgrade docs to Next 16.3 and
  enable `experimental.useTypeScriptCli` for the same reason.
- Make `@vitest/ui` a regular dependency of vite-plus via `packageExtensions`
  so it drops out of the pnpm peer key and the whole workspace shares one
  vitest instance.
- Consolidate `@blocknote/shared` behind the repo-wide `@shared` alias across
  packages, examples and the playground.
- Unify shiki on v4, declare missing workspace deps, and fix the
  `BrowserCommands` augmentation in the browser test utils.
- Give in-memory versioning snapshots strictly increasing timestamps so
  `sortSnapshotsNewestFirst` no longer depends on tie-breaking.
Core primitives that the math and diagram blocks are built on.

- De-duplicate and order extensions by key in `ExtensionManager`, and stop
  `editor.getExtension` from erasing extension instance types.
- Make syntax highlighting a user-provided singleton extension; code blocks
  and other blocks configure a shared highlighter instead of owning one.
- Add `SourceBlockWithPreview` / `SourceInlineContentWithPreview` extensions
  and their React counterparts: a reusable source/preview block and inline
  content pattern with popup editing, keyboard and selection handling.
- Support converting between plain and styled text in `updateBlock`, and add
  inline content boundary editing.
- Give exporters a typed `ExportImage` contract and dictionary-backed i18n so
  they never hardcode language strings.
Add `@blocknote/math-block`: a LaTeX math block and inline math content type
rendered with KaTeX, built on the source-with-preview primitives.

Includes exporter mappings for HTML, markdown, docx, odt, pdf and email, an
example, and documentation.
Add `@blocknote/diagram-block`: a Mermaid diagram block built on the
source-with-preview primitives.

Includes exporter mappings for HTML, markdown, docx, odt, pdf and email, an
example, and documentation.
Restructure the docs around the new blocks: document source-with-preview as a
custom schema pattern, refresh the code block and export pages, and make
`validate-links` work from any cwd.
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