Skip to content

Commit 503c796

Browse files
nperez0111matthewlipski
authored andcommitted
feat(core): source-with-preview, syntax highlighting & exporter images
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.
1 parent 99253c3 commit 503c796

195 files changed

Lines changed: 7888 additions & 1662 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/04-theming/06-code-block/src/App.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import "@blocknote/core/fonts/inter.css";
33
import { BlockNoteView } from "@blocknote/mantine";
44
import "@blocknote/mantine/style.css";
55
import { useCreateBlockNote } from "@blocknote/react";
6-
// This packages some of the most used languages in on-demand bundle
7-
import { codeBlockOptions } from "@blocknote/code-block";
6+
// This packages some of the most used languages in on-demand bundle, and a
7+
// ready-to-use syntax highlighter extension configured with them.
8+
import { codeBlockOptions, syntaxHighlighter } from "@blocknote/code-block";
89

910
export default function App() {
1011
// Creates a new editor instance.
1112
const editor = useCreateBlockNote({
13+
// Adding the syntax highlighter extension enables syntax highlighting for
14+
// the code block. Without it, code renders as plain text.
15+
extensions: [syntaxHighlighter],
1216
schema: BlockNoteSchema.create().extend({
1317
blockSpecs: {
1418
codeBlock: createCodeBlockSpec(codeBlockOptions),

examples/04-theming/07-custom-code-block/.bnexample.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
"tags": ["Basic"],
66
"dependencies": {
77
"@blocknote/code-block": "latest",
8-
"@shikijs/core": "^4",
9-
"@shikijs/engine-javascript": "^4",
10-
"@shikijs/langs-precompiled": "^4",
11-
"@shikijs/themes": "^4",
12-
"@shikijs/types": "^4"
8+
"@shikijs/core": "^4.4.3",
9+
"@shikijs/engine-javascript": "^4.4.3",
10+
"@shikijs/langs-precompiled": "^4.4.3",
11+
"@shikijs/themes": "^4.4.3",
12+
"@shikijs/types": "^4.4.3"
1313
}
1414
}

examples/04-theming/07-custom-code-block/src/App.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
1-
import { BlockNoteSchema, createCodeBlockSpec } from "@blocknote/core";
1+
import {
2+
BlockNoteSchema,
3+
createCodeBlockSpec,
4+
SyntaxHighlightingExtension,
5+
} from "@blocknote/core";
26
import "@blocknote/core/fonts/inter.css";
37
import { BlockNoteView } from "@blocknote/mantine";
48
import "@blocknote/mantine/style.css";
59
import { useCreateBlockNote } from "@blocknote/react";
610
// Bundle created from `npx shiki-codegen --langs typescript,javascript,react --themes light-plus,dark-plus --engine javascript --precompiled ./shiki.bundle.ts`
711
import { createHighlighter } from "./shiki.bundle";
812

13+
// Syntax highlighting is a separate extension, configured with a highlighter.
14+
// Here we build one from our own custom Shiki bundle (with `dark-plus` /
15+
// `light-plus` themes) and pass it to the editor's `extensions` below.
16+
const syntaxHighlighter = SyntaxHighlightingExtension({
17+
// This creates a highlighter, it can be asynchronous to load it afterwards
18+
createHighlighter: () =>
19+
createHighlighter({
20+
themes: ["dark-plus", "light-plus"],
21+
langs: [],
22+
}),
23+
});
24+
925
export default function App() {
1026
// Creates a new editor instance.
1127
const editor = useCreateBlockNote({
28+
extensions: [syntaxHighlighter],
1229
schema: BlockNoteSchema.create().extend({
1330
blockSpecs: {
1431
codeBlock: createCodeBlockSpec({
@@ -27,12 +44,6 @@ export default function App() {
2744
name: "Vue",
2845
},
2946
},
30-
// This creates a highlighter, it can be asynchronous to load it afterwards
31-
createHighlighter: () =>
32-
createHighlighter({
33-
themes: ["dark-plus", "light-plus"],
34-
langs: [],
35-
}),
3647
}),
3748
},
3849
}),

examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
"author": "yousefed",
55
"tags": ["Interoperability"],
66
"dependencies": {
7-
"@blocknote/xl-pdf-exporter": "latest",
7+
"@blocknote/diagram-block": "latest",
8+
"@blocknote/math-block": "latest",
89
"@blocknote/xl-multi-column": "latest",
9-
"@react-pdf/renderer": "^4.3.0"
10+
"@blocknote/xl-pdf-exporter": "latest",
11+
"@react-pdf/math": "^2.0.1",
12+
"@react-pdf/renderer": "^4.5.1",
13+
"mathjax-full": "^3.2.2"
1014
},
1115
"pro": true
1216
}

examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import "@blocknote/core/fonts/inter.css";
88
import * as locales from "@blocknote/core/locales";
99
import { BlockNoteView } from "@blocknote/mantine";
1010
import "@blocknote/mantine/style.css";
11+
import { createReactDiagramBlockSpec } from "@blocknote/diagram-block";
12+
import {
13+
createReactInlineMathSpec,
14+
createReactMathBlockSpec,
15+
} from "@blocknote/math-block";
1116
import {
1217
SuggestionMenuController,
1318
getDefaultReactSlashMenuItems,
@@ -24,6 +29,11 @@ import {
2429
PDFExporter,
2530
pdfDefaultSchemaMappings,
2631
} from "@blocknote/xl-pdf-exporter";
32+
import { diagramBlockMapping } from "@blocknote/diagram-block/pdf-exporter";
33+
import {
34+
inlineMathMapping,
35+
mathBlockMapping,
36+
} from "@blocknote/math-block/pdf-exporter";
2737
import { pdf, PDFViewer } from "@react-pdf/renderer";
2838
import { JSX, useEffect, useMemo, useReducer, useState } from "react";
2939

@@ -38,7 +48,16 @@ export default function App() {
3848
// Creates a new editor instance.
3949
const editor = useCreateBlockNote({
4050
// Adds support for page breaks & multi-column blocks.
41-
schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())),
51+
// Adds support for math & diagram blocks.
52+
schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())).extend({
53+
blockSpecs: {
54+
mathBlock: createReactMathBlockSpec(),
55+
diagram: createReactDiagramBlockSpec(),
56+
},
57+
inlineContentSpecs: {
58+
math: createReactInlineMathSpec(),
59+
},
60+
}),
4261
dropCursor: multiColumnDropCursor,
4362
dictionary: {
4463
...locales.en,
@@ -330,6 +349,31 @@ export default function App() {
330349
console.log("Hello World", message);
331350
};`,
332351
},
352+
{
353+
type: "mathBlock",
354+
content: "a^2 = \\sqrt{b^2 + c^2}",
355+
},
356+
{
357+
type: "diagram",
358+
content: `graph TD
359+
A[Start] --> B{Works?}
360+
B -->|Yes| C[Ship it]
361+
B -->|No| A`,
362+
},
363+
{
364+
type: "paragraph",
365+
content: [
366+
{
367+
type: "text",
368+
text: "Inline math: ",
369+
styles: {},
370+
},
371+
{
372+
type: "math",
373+
content: "e^{i\\pi} + 1 = 0",
374+
},
375+
],
376+
},
333377
{
334378
type: "columnList",
335379
children: [
@@ -402,7 +446,21 @@ export default function App() {
402446

403447
// Exports the editor document to PDF whenever it changes.
404448
const onChange = async () => {
405-
const exporter = new PDFExporter(editor.schema, pdfDefaultSchemaMappings);
449+
const exporter = new PDFExporter(editor.schema, {
450+
...pdfDefaultSchemaMappings,
451+
blockMapping: {
452+
...pdfDefaultSchemaMappings.blockMapping,
453+
// Embeds diagrams as images instead of their Mermaid source.
454+
diagram: diagramBlockMapping,
455+
// Renders math blocks as formulas instead of their LaTeX source.
456+
mathBlock: mathBlockMapping,
457+
},
458+
inlineContentMapping: {
459+
...pdfDefaultSchemaMappings.inlineContentMapping,
460+
// Renders inline math as formula images instead of its LaTeX source.
461+
math: inlineMathMapping,
462+
},
463+
});
406464
const pdfDocument = await exporter.toReactPDFDocument(editor.document);
407465
setPDFDocument(pdfDocument);
408466
forceRerender();

examples/05-interoperability/06-converting-blocks-to-docx/.bnexample.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
"author": "yousefed",
55
"tags": [""],
66
"dependencies": {
7+
"@blocknote/diagram-block": "latest",
8+
"@blocknote/math-block": "latest",
79
"@blocknote/xl-docx-exporter": "latest",
8-
"@blocknote/xl-multi-column": "latest"
10+
"@blocknote/xl-multi-column": "latest",
11+
"katex": "^0.16.11"
912
},
1013
"pro": true
1114
}

examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import "@blocknote/core/fonts/inter.css";
88
import * as locales from "@blocknote/core/locales";
99
import { BlockNoteView } from "@blocknote/mantine";
1010
import "@blocknote/mantine/style.css";
11+
import {
12+
createReactInlineMathSpec,
13+
createReactMathBlockSpec,
14+
} from "@blocknote/math-block";
15+
import { createReactDiagramBlockSpec } from "@blocknote/diagram-block";
1116
import {
1217
SuggestionMenuController,
1318
getDefaultReactSlashMenuItems,
@@ -18,6 +23,11 @@ import {
1823
DOCXExporter,
1924
docxDefaultSchemaMappings,
2025
} from "@blocknote/xl-docx-exporter";
26+
import { diagramBlockMapping } from "@blocknote/diagram-block/docx-exporter";
27+
import {
28+
inlineMathMapping,
29+
mathBlockMapping,
30+
} from "@blocknote/math-block/docx-exporter";
2131
import {
2232
getMultiColumnSlashMenuItems,
2333
multiColumnDropCursor,
@@ -32,7 +42,16 @@ export default function App() {
3242
// Creates a new editor instance.
3343
const editor = useCreateBlockNote({
3444
// Adds support for page breaks & multi-column blocks.
35-
schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())),
45+
// Adds support for math & diagram blocks.
46+
schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())).extend({
47+
blockSpecs: {
48+
mathBlock: createReactMathBlockSpec(),
49+
diagram: createReactDiagramBlockSpec(),
50+
},
51+
inlineContentSpecs: {
52+
math: createReactInlineMathSpec(),
53+
},
54+
}),
3655
dropCursor: multiColumnDropCursor,
3756
dictionary: {
3857
...locales.en,
@@ -324,6 +343,31 @@ export default function App() {
324343
console.log("Hello World", message);
325344
};`,
326345
},
346+
{
347+
type: "mathBlock",
348+
content: "a^2 = \\sqrt{b^2 + c^2}",
349+
},
350+
{
351+
type: "diagram",
352+
content: `graph TD
353+
A[Start] --> B{Works?}
354+
B -->|Yes| C[Ship it]
355+
B -->|No| A`,
356+
},
357+
{
358+
type: "paragraph",
359+
content: [
360+
{
361+
type: "text",
362+
text: "Inline math: ",
363+
styles: {},
364+
},
365+
{
366+
type: "math",
367+
content: "e^{i\\pi} + 1 = 0",
368+
},
369+
],
370+
},
327371

328372
{
329373
type: "columnList",
@@ -396,7 +440,23 @@ export default function App() {
396440

397441
// Exports the editor content to DOCX and downloads it.
398442
const onDownloadClick = async () => {
399-
const exporter = new DOCXExporter(editor.schema, docxDefaultSchemaMappings);
443+
const exporter = new DOCXExporter(editor.schema, {
444+
...docxDefaultSchemaMappings,
445+
blockMapping: {
446+
...docxDefaultSchemaMappings.blockMapping,
447+
// Embeds diagrams as images instead of their Mermaid source.
448+
diagram: diagramBlockMapping,
449+
// Renders math blocks as native equations instead of their LaTeX
450+
// source.
451+
mathBlock: mathBlockMapping,
452+
},
453+
inlineContentMapping: {
454+
...docxDefaultSchemaMappings.inlineContentMapping,
455+
// Renders inline math as native equations instead of its LaTeX
456+
// source.
457+
math: inlineMathMapping,
458+
},
459+
});
400460
const blob = await exporter.toBlob(editor.document);
401461

402462
const link = document.createElement("a");

examples/05-interoperability/07-converting-blocks-to-odt/.bnexample.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
"author": "areknawo",
55
"tags": [""],
66
"dependencies": {
7+
"@blocknote/diagram-block": "latest",
8+
"@blocknote/math-block": "latest",
9+
"@blocknote/xl-multi-column": "latest",
710
"@blocknote/xl-odt-exporter": "latest",
8-
"@blocknote/xl-multi-column": "latest"
11+
"katex": "^0.16.11"
912
},
1013
"pro": true
1114
}

0 commit comments

Comments
 (0)