Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions docs/content/docs/features/blocks/code-blocks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ type CodeBlockOptions = {
aliases?: string[];
}
>;
createHighlighter?: () => Promise<HighlighterGeneric<any, any>>;
};
```

Expand All @@ -44,15 +43,38 @@ type CodeBlockOptions = {

`supportedLanguages:` The syntax highlighting languages supported by the code block, which is an empty array by default.

`createHighlighter:` The [Shiki highliter](https://shiki.style/guide/load-theme) to use for syntax highlighting.
**Syntax Highlighting**

BlockNote also provides a generic set of options for syntax highlighting in the `@blocknote/code-block` package, which support a wide range of languages:
Syntax highlighting is handled by a separate editor extension, configured at the editor level via the `syntaxHighlighting` option (not on the code block itself), so it can highlight any block's content:

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.

Curious if we can find a better way to do this. This will make it difficult to create easily installable block-plugins later I think

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.

I don't think syntaxHighlighting should be a top-level option like this. But as a single extension, maybe yea.
What's annoying is that it wants to be a singleton, but multiple places need to configure it. I'll think on it.


```ts
import { createCodeBlockSpec } from "@blocknote/core";
import { codeBlockOptions } from "@blocknote/code-block";
type SyntaxHighlightingOptions = {
createHighlighter?: () => Promise<HighlighterGeneric<any, any>>;
highlightBlock?: (block: {
type: string;
props: Record<string, any>;
}) => string | undefined;
};
```

`createHighlighter:` The [Shiki highlighter](https://shiki.style/guide/load-theme) to use for syntax highlighting.

const codeBlock = createCodeBlockSpec(codeBlockOptions);
`highlightBlock:` Picks the language to highlight a block's content as (return the language key, or `undefined` to leave it un-highlighted). This is how you enable highlighting for specific blocks. Defaults to the block's `language` prop (`(block) => block.props.language`), which covers the code block. For a block with a fixed language, return it directly — e.g. for a math block: `(block) => (block.type === "math" ? "latex" : block.props.language)`.

BlockNote provides a generic, ready-to-use set of these in the `@blocknote/code-block` package, which supports a wide range of languages. The code block options and the highlighter are exported separately:

```ts
import { createCodeBlockSpec } from "@blocknote/core";
import { codeBlockOptions, createHighlighter } from "@blocknote/code-block";

const editor = useCreateBlockNote({
syntaxHighlighting: { createHighlighter },
schema: BlockNoteSchema.create().extend({
blockSpecs: {
codeBlock: createCodeBlockSpec(codeBlockOptions),
},
}),
});
```

See [this example](/examples/theming/code-block) to see it in action.
Expand Down Expand Up @@ -92,6 +114,15 @@ import { createHighlighter } from "./shiki.bundle.js";

export default function App() {
const editor = useCreateBlockNote({
// The highlighter is configured at the editor level, separately from the
// code block's own options.
syntaxHighlighting: {
createHighlighter: () =>
createHighlighter({
themes: ["light-plus", "dark-plus"],
langs: [],
}),
},
schema: BlockNoteSchema.create().extend({
blockSpecs: {
codeBlock: createCodeBlockSpec({
Expand All @@ -103,11 +134,6 @@ export default function App() {
aliases: ["ts"],
},
},
createHighlighter: () =>
createHighlighter({
themes: ["light-plus", "dark-plus"],
langs: [],
}),
}),
},
}),
Expand Down
3 changes: 2 additions & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@
"tailwind-merge": "^3.4.0",
"y-partykit": "^0.0.25",
"yjs": "^13.6.27",
"zod": "^4.3.5"
"zod": "^4.3.5",
"@blocknote/math-block": "workspace:*"
},
"devDependencies": {
"@blocknote/code-block": "workspace:*",
Expand Down
5 changes: 4 additions & 1 deletion examples/04-theming/06-code-block/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
// This packages some of the most used languages in on-demand bundle
import { codeBlockOptions } from "@blocknote/code-block";
import { codeBlockOptions, createHighlighter } from "@blocknote/code-block";

export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
// The Shiki highlighter is configured at the editor level, separately from
// the code block's own options (default language & language menu).
syntaxHighlighting: { createHighlighter },
schema: BlockNoteSchema.create().extend({
blockSpecs: {
codeBlock: createCodeBlockSpec(codeBlockOptions),
Expand Down
16 changes: 10 additions & 6 deletions examples/04-theming/07-custom-code-block/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import { createHighlighter } from "./shiki.bundle";
export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
// The Shiki highlighter is configured at the editor level, separately from
// the code block's own options (default language & language menu).
syntaxHighlighting: {
// This creates a highlighter, it can be asynchronous to load it afterwards
createHighlighter: () =>
createHighlighter({
themes: ["dark-plus", "light-plus"],
langs: [],
}),
},
schema: BlockNoteSchema.create().extend({
blockSpecs: {
codeBlock: createCodeBlockSpec({
Expand All @@ -27,12 +37,6 @@ export default function App() {
name: "Vue",
},
},
// This creates a highlighter, it can be asynchronous to load it afterwards
createHighlighter: () =>
createHighlighter({
themes: ["dark-plus", "light-plus"],
langs: [],
}),
}),
},
}),
Expand Down
17 changes: 17 additions & 0 deletions examples/06-custom-schema/09-math-block/.bnexample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"playground": true,
"docs": true,
"author": "matthewlipski",
"tags": [
"Intermediate",
"Blocks",
"Custom Schemas",
"Suggestion Menus",
"Slash Menu"
],
"dependencies": {
"@blocknote/code-block": "latest",
"@blocknote/math-block": "latest",
"react-icons": "^5.5.0"
}
}
10 changes: 10 additions & 0 deletions examples/06-custom-schema/09-math-block/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Math Block

In this example, we register the `@blocknote/math-block` block in a custom schema. The math block renders LaTeX as MathML (using Temml) for the browser to display natively, and reveals an editable LaTeX source popup when selected. Exporting to HTML produces a MathML `<math>` element, and pasting MathML back in is converted to LaTeX.

**Try it out:** Click a formula to edit its LaTeX!

**Relevant Docs:**

- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)
- [Editor Setup](/docs/getting-started/editor-setup)
14 changes: 14 additions & 0 deletions examples/06-custom-schema/09-math-block/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html lang="en">
Comment thread
matthewlipski marked this conversation as resolved.
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Math Block</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions examples/06-custom-schema/09-math-block/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./src/App.jsx";
Comment thread
matthewlipski marked this conversation as resolved.

const root = createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
33 changes: 33 additions & 0 deletions examples/06-custom-schema/09-math-block/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@blocknote/example-custom-schema-math-block",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"type": "module",
"private": true,
"version": "0.12.4",
"scripts": {
"start": "vp dev",
"dev": "vp dev",
"build:prod": "tsc && vp build",
"preview": "vp preview"
},
"dependencies": {
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
"@mantine/core": "^9.0.2",
"@mantine/hooks": "^9.0.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"@blocknote/code-block": "latest",
"@blocknote/math-block": "latest",
"react-icons": "^5.5.0"
},
"devDependencies": {
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"vite-plus": "catalog:"
}
}
92 changes: 92 additions & 0 deletions examples/06-custom-schema/09-math-block/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import "@blocknote/core/fonts/inter.css";
import { BlockNoteSchema } from "@blocknote/core";
import {
filterSuggestionItems,
insertOrUpdateBlockForSlashMenu,
} from "@blocknote/core/extensions";
import { createHighlighter } from "@blocknote/code-block";
import { createMathBlockSpec } from "@blocknote/math-block";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
getDefaultReactSlashMenuItems,
SuggestionMenuController,
useCreateBlockNote,
} from "@blocknote/react";
import { TbMathFunction } from "react-icons/tb";

// Our schema with block specs, which contain the configs and implementations for blocks
// that we want our editor to use.
const schema = BlockNoteSchema.create().extend({
blockSpecs: {
// Creates an instance of the Math block and adds it to the schema.
math: createMathBlockSpec(),
},
});

// Slash menu item to insert a Math block.
const insertMath = (editor: typeof schema.BlockNoteEditor) => ({
title: "Math",
subtext: "Insert a LaTeX math formula",
onItemClick: () =>
insertOrUpdateBlockForSlashMenu(editor, {
type: "math",
}),
aliases: ["math", "latex", "formula", "equation"],
group: "Basic blocks",
icon: <TbMathFunction />,
});

export default function App() {
const editor = useCreateBlockNote({
// Configures the syntax highlighting extension to always use LaTeX syntax highlighting in the
// Math block.
syntaxHighlighting: {
createHighlighter,
highlightBlock: (block) =>
block.type === "math" ? "latex" : block.props.language,
},
schema,
initialContent: [
{
type: "paragraph",
content: "Click a formula to edit its LaTeX source:",
},
{
type: "math",
content: "a^2 = \\sqrt{b^2 + c^2}",
},
{
type: "math",
content: "\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}",
},
{
type: "paragraph",
content: "Press the '/' key to open the Slash Menu and add another",
},
],
});

// Renders the editor instance using a React component.
return (
<BlockNoteView editor={editor} slashMenu={false}>
{/* Replaces the default Slash Menu. */}
<SuggestionMenuController
triggerCharacter={"/"}
getItems={async (query) => {
// Gets all default slash menu items.
const defaultItems = getDefaultReactSlashMenuItems(editor);
// Finds index of last item in "Basic blocks" group.
const lastBasicBlockIndex = defaultItems.findLastIndex(
(item) => item.group === "Basic blocks",
);
// Inserts the Math item as the last item in the "Basic blocks" group.
defaultItems.splice(lastBasicBlockIndex + 1, 0, insertMath(editor));

// Returns filtered items based on the query.
return filterSuggestionItems(defaultItems, query);
}}
/>
</BlockNoteView>
);
}
29 changes: 29 additions & 0 deletions examples/06-custom-schema/09-math-block/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true
},
"include": ["."],
"__ADD_FOR_LOCAL_DEV_references": [
{
"path": "../../../packages/core/"
},
{
"path": "../../../packages/react/"
}
]
}
1 change: 1 addition & 0 deletions examples/06-custom-schema/09-math-block/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite-plus/client" />
31 changes: 31 additions & 0 deletions examples/06-custom-schema/09-math-block/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import react from "@vitejs/plugin-react";
import * as fs from "fs";
import * as path from "path";
import { defineConfig } from "vite-plus";
// https://vitejs.dev/config/
export default defineConfig(((conf: { command: string }) => ({
plugins: [react()],
optimizeDeps: {},
build: {
sourcemap: true,
},
resolve: {
alias:
conf.command === "build" ||
!fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
? {}
: ({
// Comment out the lines below to load a built version of blocknote
// or, keep as is to load live from sources with live reload working
"@blocknote/core": path.resolve(
__dirname,
"../../packages/core/src/",
),
"@blocknote/react": path.resolve(
__dirname,
"../../packages/react/src/",
Comment thread
matthewlipski marked this conversation as resolved.
),
} as any),
},
})) as Parameters<typeof defineConfig>[0]);
4 changes: 3 additions & 1 deletion packages/code-block/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@
"@shikijs/core": "^4",
"@shikijs/engine-javascript": "^4",
"@shikijs/langs-precompiled": "^4",
"@shikijs/themes": "^4"
"@shikijs/themes": "^4",
"katex": "^0.16.11"
},
"optionalDependencies": {
"@shikijs/types": "^4"
},
"devDependencies": {
"@types/katex": "^0.16.7",
"rimraf": "^5.0.10",
"rollup-plugin-webpack-stats": "^0.2.6",
"typescript": "^5.9.3",
Expand Down
Loading
Loading