Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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


```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 @@ -97,7 +97,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
10 changes: 10 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,10 @@
{
"playground": true,
"docs": true,
"author": "matthewlipski",
"tags": ["Intermediate", "Blocks", "Custom Schemas"],
"dependencies": {
"@blocknote/code-block": "latest",
"@blocknote/math-block": "latest"
}
}
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>,
);
32 changes: 32 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,32 @@
{
"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"
},
"devDependencies": {
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"vite-plus": "catalog:"
}
}
46 changes: 46 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,46 @@
import "@blocknote/core/fonts/inter.css";
import { BlockNoteSchema } from "@blocknote/core";
import { createHighlighter } from "@blocknote/code-block";
import { createMathBlockSpec } from "@blocknote/math-block";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";

export default function App() {
// The math block isn't a default block, so we register it in a custom schema.
const editor = useCreateBlockNote({
// The Shiki highlighter (from @blocknote/code-block) syntax-highlights the
// math block's editable LaTeX source popup. `highlightBlock` enables it for
// the math block and highlights it as LaTeX.
syntaxHighlighting: {
createHighlighter,
highlightBlock: (block) =>
block.type === "math" ? "latex" : block.props.language,
},
schema: BlockNoteSchema.create().extend({
blockSpecs: {
math: createMathBlockSpec(),
},
}),
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",
},
],
});

// Renders the editor instance using a React component.
return <BlockNoteView editor={editor} />;
}
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/"
}
]
}
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
6 changes: 3 additions & 3 deletions packages/code-block/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vite-plus/test";
import { codeBlockOptions } from "./index.js";
import { codeBlockOptions, createHighlighter } from "./index.js";

describe("codeBlock", () => {
it("should exist", () => {
Expand All @@ -11,7 +11,7 @@ describe("codeBlock", () => {
it("should have supportedLanguages", () => {
expect(codeBlockOptions.supportedLanguages).toBeDefined();
});
it("should have createHighlighter", () => {
expect(codeBlockOptions.createHighlighter).toBeDefined();
it("exports a separate createHighlighter", () => {
expect(createHighlighter).toBeDefined();
});
});
14 changes: 8 additions & 6 deletions packages/code-block/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import type { CodeBlockOptions } from "@blocknote/core";
import { createHighlighter } from "./shiki.bundle.js";
import { createHighlighter as createShikiHighlighter } from "./shiki.bundle.js";

export const createHighlighter = () =>
createShikiHighlighter({
themes: ["github-dark", "github-light"],
langs: [],
});

// TODO: Should this be here or in the core code block?
export const codeBlockOptions = {
defaultLanguage: "javascript",
supportedLanguages: {
Expand Down Expand Up @@ -197,9 +204,4 @@ export const codeBlockOptions = {
aliases: ["objective-c", "objc"],
},
},
createHighlighter: () =>
createHighlighter({
themes: ["github-dark", "github-light"],
langs: [],
}),
} satisfies CodeBlockOptions;
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
},
"dependencies": {
"@emoji-mart/data": "^1.2.1",
"@floating-ui/dom": "^1.7.6",
"@handlewithcare/prosemirror-inputrules": "^0.1.4",
"@shikijs/types": "^4",
"@tanstack/store": "^0.7.7",
Expand Down
Loading
Loading