Skip to content

Commit d037aeb

Browse files
authored
feature: file exporters (docx and pdf) (#1143)
* cleaned serialization code * comments * update snapshot * add comment * address comments * fix lint * fix build * fix server test * wip * wip * wip * react pdf * misc * move files and clean partialBlocksToBlocksForTesting * wip * refactor * fix build * update packagejson * fix lint * fix lint * wip * wip * wip * fix numbered list bug * wip * small fixes * fix build * fix lint * wip * fix build * fix numbering, image * fix build * docx font * update template * full doc * fix examples * refactor * fix build * add option for resolveFile * add resolveFileUrl_DEV_ONLY to examples * fix build * fix emoji pdf * fix pdf numbering and checkmarks * support colors * fix build * fix package lock * fix tables * fix word hyperlink / caption styles * file handling docx, and start of code block * file blocks pdf * split packages * wip * fix 2 pdf bugs * tests * fix lint * fix build * fix test * remove example pdf * remove react email * header / footer support * don't write files in tests * corsproxy + docs * add license * fix build * update docs * small fix
1 parent 8aa89ab commit d037aeb

File tree

177 files changed

+12188
-403
lines changed

Some content is hidden

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

177 files changed

+12188
-403
lines changed

.vscode/settings.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@
1111
"packages/website/docs/.vitepress": false,
1212
"**/.*": false
1313
},
14-
"typescript.tsdk": "node_modules/typescript/lib"
14+
"typescript.tsdk": "node_modules/typescript/lib",
15+
"[xml]": {
16+
"editor.defaultFormatter": "redhat.vscode-xml"
17+
}
1518
}

docs/components/pages/pricing/faq.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const faqs = [
1717
question:
1818
"What License is BlockNote using? Can I use it for commercial projects?",
1919
answer: `BlockNote is open source software licensed under the MPL 2.0 license, which allows you to use BlockNote in commercial (and closed-source) applications - even without a subscription.
20-
If you make changes to the BlockNote source files, you're expected to publish these changes so the wider community can benefit as well.`,
20+
If you make changes to the BlockNote source files, you're expected to publish these changes so the wider community can benefit as well. \nThe XL packages are dual-licensed and available under AGPL-3.0 or a commercial license as part of the BlockNote Business subscription or above.`,
2121
},
2222
// More questions...
2323
];

docs/pages/docs/editor-api/_meta.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
"manipulating-inline-content": "",
44
"cursor-selections": "",
55
"converting-blocks": "",
6-
"server-processing": ""
6+
"server-processing": "",
7+
"export-to-pdf": "",
8+
"export-to-docx": ""
79
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
title: Export to docx (Office Open XML)
3+
description: Export BlockNote documents to a docx word (Office Open XML) file.
4+
imageTitle: Export to docx
5+
path: /docs/export-to-docx
6+
---
7+
8+
import { Example } from "@/components/example";
9+
import { Callout } from "nextra/components";
10+
11+
# Exporting blocks to docx
12+
13+
It's possible to export BlockNote documents to docx, completely client-side.
14+
15+
<Callout type={"info"}>
16+
This feature is provided by the `@blocknote/xl-docx-exporter`. `xl-` packages
17+
are fully open source, but released under a copyleft license. A commercial
18+
license for usage in closed source, proprietary products comes as part of the
19+
[Business subscription](/pricing).
20+
</Callout>
21+
22+
First, install the `@blocknote/xl-docx-exporter` and `docx` packages:
23+
24+
```bash
25+
npm install @blocknote/xl-docx-exporter docx
26+
```
27+
28+
Then, create an instance of the `DOCXExporter` class. This exposes the following methods:
29+
30+
```typescript
31+
import {
32+
DOCXExporter,
33+
docxDefaultSchemaMappings,
34+
} from "@blocknote/xl-docx-exporter";
35+
import { Packer } from "docx";
36+
37+
// Create the exporter
38+
const exporter = new DOCXExporter(editor.schema, docxDefaultSchemaMappings);
39+
40+
// Convert the blocks to a docxjs document
41+
const docxDocument = await exporter.toDocxJsDocument(editor.document);
42+
43+
// Use docx to write to file:
44+
await Packer.toBuffer(docxDocument);
45+
```
46+
47+
See the [full example](/examples/interoperability/converting-blocks-to-docx) below:
48+
49+
<Example name="interoperability/converting-blocks-to-docx" />
50+
51+
### Customizing the Docx output file
52+
53+
`toDocxJsDocument` takes an optional `options` parameter, which allows you to customize document metadata (like the author) and section options (like headers and footers).
54+
55+
Example usage:
56+
57+
```typescript
58+
import { Paragraph, TextRun } from "docx";
59+
60+
const doc = await exporter.toDocxJsDocument(testDocument, {
61+
documentOptions: {
62+
creator: "John Doe",
63+
},
64+
sectionOptions: {
65+
headers: {
66+
default: {
67+
options: {
68+
children: [new Paragraph({ children: [new TextRun("Header")] })],
69+
},
70+
},
71+
},
72+
footers: {
73+
default: {
74+
options: {
75+
children: [new Paragraph({ children: [new TextRun("Footer")] })],
76+
},
77+
},
78+
},
79+
},
80+
});
81+
```
82+
83+
### Custom mappings / custom schemas
84+
85+
The `DOCXExporter` constructor takes a `schema`, `mappings` and `options` parameter.
86+
A _mapping_ defines how to convert a BlockNote schema element (a Block, Inline Content, or Style) to a [docxjs](https://docx.js.org/) element.
87+
If you're using a [custom schema](/docs/custom-schemas) in your editor, or if you want to overwrite how default BlockNote elements are converted to docx, you can pass your own `mappings`:
88+
89+
For example, use the following code in case your schema has an `extraBlock` type:
90+
91+
```typescript
92+
import {
93+
DOCXExporter,
94+
docxDefaultSchemaMappings,
95+
} from "@blocknote/xl-docx-exporter";
96+
import { Paragraph, TextRun } from "docx";
97+
98+
new DOCXExporter(schema, {
99+
blockMapping: {
100+
...docxDefaultSchemaMappings.blockMapping,
101+
myCustomBlock: (block, exporter) => {
102+
return new Paragraph({
103+
children: [
104+
new TextRun({
105+
text: "My custom block",
106+
}),
107+
],
108+
});
109+
},
110+
},
111+
inlineContentMapping: docxDefaultSchemaMappings.inlineContentMapping,
112+
styleMapping: docxDefaultSchemaMappings.styleMapping,
113+
});
114+
```
115+
116+
### Exporter options
117+
118+
The `DOCXExporter` constructor takes an optional `options` parameter.
119+
While conversion happens on the client-side, the default setup uses a server hosted proxy to resolve files:
120+
121+
```typescript
122+
const defaultOptions = {
123+
// a function to resolve external resources in order to avoid CORS issues
124+
// by default, this calls a BlockNote hosted server-side proxy to resolve files
125+
resolveFileUrl: corsProxyResolveFileUrl,
126+
// the colors to use in the Docx for things like highlighting, background colors and font colors.
127+
colors: COLORS_DEFAULT, // defaults from @blocknote/core
128+
};
129+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
title: Export to PDF
3+
description: Export BlockNote documents to a PDF.
4+
imageTitle: Export to PDF
5+
path: /docs/export-to-pdf
6+
---
7+
8+
import { Example } from "@/components/example";
9+
import { Callout } from "nextra/components";
10+
11+
# Exporting blocks to PDF
12+
13+
It's possible to export BlockNote documents to PDF, completely client-side.
14+
15+
<Callout type={"info"}>
16+
This feature is provided by the `@blocknote/xl-pdf-exporter`. `xl-` packages
17+
are fully open source, but released under a copyleft license. A commercial
18+
license for usage in closed source, proprietary products comes as part of the
19+
[Business subscription](/pricing).
20+
</Callout>
21+
22+
First, install the `@blocknote/xl-pdf-exporter` and `@react-pdf/renderer` packages:
23+
24+
```bash
25+
npm install @blocknote/xl-pdf-exporter @react-pdf/renderer
26+
```
27+
28+
Then, create an instance of the `PDFExporter` class. This exposes the following methods:
29+
30+
```typescript
31+
import {
32+
PDFExporter,
33+
pdfDefaultSchemaMappings,
34+
} from "@blocknote/xl-pdf-exporter";
35+
import * as ReactPDF from "@react-pdf/renderer";
36+
37+
// Create the exporter
38+
const exporter = new PDFExporter(editor.schema, pdfDefaultSchemaMappings);
39+
40+
// Convert the blocks to a react-pdf document
41+
const pdfDocument = await exporter.toReactPDFDocument(editor.document);
42+
43+
// Use react-pdf to write to file:
44+
await ReactPDF.render(pdfDocument, `filename.pdf`);
45+
```
46+
47+
See the [full example](/examples/interoperability/converting-blocks-to-pdf) with live PDF preview below:
48+
49+
<Example name="interoperability/converting-blocks-to-pdf" />
50+
51+
### Customizing the PDF
52+
53+
`toReactPDFDocument` takes an optional `options` parameter, which allows you to customize the header and footer of the PDF:
54+
55+
Example usage:
56+
57+
```typescript
58+
import { Text } from "@react-pdf/renderer";
59+
const pdfDocument = await exporter.toReactPDFDocument(editor.document, {
60+
header: <Text>Header</Text>,
61+
footer: <Text>Footer</Text>,
62+
});
63+
```
64+
65+
### Custom mappings / custom schemas
66+
67+
The `PDFExporter` constructor takes a `schema` and `mappings` parameter.
68+
A _mapping_ defines how to convert a BlockNote schema element (a Block, Inline Content, or Style) to a React-PDF element.
69+
If you're using a [custom schema](/docs/custom-schemas) in your editor, or if you want to overwrite how default BlockNote elements are converted to PDF, you can pass your own `mappings`:
70+
71+
For example, use the following code in case your schema has an `extraBlock` type:
72+
73+
```typescript
74+
import { PDFExporter, pdfDefaultSchemaMappings } from "@blocknote/xl-pdf-exporter";
75+
import { Text } from "@react-pdf/renderer";
76+
77+
new PDFExporter(schema, {
78+
blockMapping: {
79+
...pdfDefaultSchemaMappings.blockMapping,
80+
myCustomBlock: (block, exporter) => {
81+
return <Text>My custom block</Text>;
82+
},
83+
},
84+
inlineContentMapping: pdfDefaultSchemaMappings.inlineContentMapping,
85+
styleMapping: pdfDefaultSchemaMappings.styleMapping,
86+
});
87+
```
88+
89+
### Exporter options
90+
91+
The `PDFExporter` constructor takes an optional `options` parameter.
92+
While conversion happens on the client-side, the default setup uses two server based resources:
93+
94+
```typescript
95+
const defaultOptions = {
96+
// emoji source, this is passed to the react-pdf library (https://react-pdf.org/fonts#registeremojisource)
97+
// these are loaded from cloudflare + twemoji by default
98+
emojiSource: {
99+
format: "png",
100+
url: "https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/",
101+
},
102+
// a function to resolve external resources in order to avoid CORS issues
103+
// by default, this calls a BlockNote hosted server-side proxy to resolve files
104+
resolveFileUrl: corsProxyResolveFileUrl,
105+
// the colors to use in the PDF for things like highlighting, background colors and font colors.
106+
colors: COLORS_DEFAULT, // defaults from @blocknote/core
107+
};
108+
```

docs/pages/pricing.mdx

+8-4
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ export default function Pricing() {
2424
price: { month: 90, year: 24 },
2525
features: [
2626
"Access to all Pro Examples",
27-
"Prioritized Bug Reports and Feature Requests on GitHub",
27+
"Prioritized Bug Reports on GitHub",
2828
"Keep the open source library running and maintained",
29+
"XL packages available for open source projects under AGPL-3.0"
2930
],
3031
githubTierId: "291733",
3132
},
@@ -35,16 +36,19 @@ export default function Pricing() {
3536
mostPopular: true,
3637
description:
3738
"Best for companies that want a direct line to the team.",
38-
price: { month: 189, year: 48 },
39+
price: { month: 390, year: 48 },
3940
features: [
41+
"Commercial license for XL packages:",
42+
"XL: Multi-column layouts",
43+
"XL: Export to PDF, Docx",
4044
"Access to all Pro Examples",
41-
"Prioritized Bug Reports and Feature Requests on GitHub",
45+
"Prioritized Bug Reports on GitHub",
4246
"Keep the open source library running and maintained",
4347
"Logo on our website and repositories",
4448
"Access to a private Discord channel with the maintainers",
4549
"Up to 2 hours of individual support per month",
4650
],
47-
githubTierId: "413994",
51+
githubTierId: "440968",
4852
},
4953
{
5054
id: "tier-enterprise",

examples/01-basic/05-removing-default-blocks/App.tsx

+5-6
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ import { useCreateBlockNote } from "@blocknote/react";
66

77
export default function App() {
88
// Disable the Audio and Image blocks from the built-in schema
9+
// This is done by picking out the blocks you want to disable
10+
const { audio, image, ...remainingBlockSpecs } = defaultBlockSpecs;
11+
912
const schema = BlockNoteSchema.create({
1013
blockSpecs: {
11-
//first pass all the blockspecs from the built in, default block schema
12-
...defaultBlockSpecs,
13-
14-
// disable blocks you don't want
15-
audio: undefined as any,
16-
image: undefined as any,
14+
// remainingBlockSpecs contains all the other blocks
15+
...remainingBlockSpecs,
1716
},
1817
});
1918

examples/05-interoperability/01-converting-blocks-to-html/App.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import "@blocknote/core/fonts/inter.css";
2-
import { useCreateBlockNote } from "@blocknote/react";
32
import { BlockNoteView } from "@blocknote/mantine";
43
import "@blocknote/mantine/style.css";
5-
import { useState } from "react";
4+
import { useCreateBlockNote } from "@blocknote/react";
5+
import { useEffect, useState } from "react";
66

77
import "./styles.css";
88

@@ -35,6 +35,12 @@ export default function App() {
3535
setHTML(html);
3636
};
3737

38+
useEffect(() => {
39+
// on mount, trigger initial conversion of the initial content to html
40+
onChange();
41+
// eslint-disable-next-line react-hooks/exhaustive-deps
42+
}, []);
43+
3844
// Renders the editor instance, and its contents as HTML below.
3945
return (
4046
<div className="wrapper">

examples/05-interoperability/03-converting-blocks-to-md/App.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import "@blocknote/core/fonts/inter.css";
2-
import { useCreateBlockNote } from "@blocknote/react";
32
import { BlockNoteView } from "@blocknote/mantine";
43
import "@blocknote/mantine/style.css";
5-
import { useState } from "react";
4+
import { useCreateBlockNote } from "@blocknote/react";
5+
import { useEffect, useState } from "react";
66

77
import "./styles.css";
88

@@ -35,6 +35,12 @@ export default function App() {
3535
setMarkdown(markdown);
3636
};
3737

38+
useEffect(() => {
39+
// on mount, trigger initial conversion of the initial content to md
40+
onChange();
41+
// eslint-disable-next-line react-hooks/exhaustive-deps
42+
}, []);
43+
3844
// Renders the editor instance, and its contents as Markdown below.
3945
return (
4046
<div className={"wrapper"}>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"playground": true,
3+
"docs": true,
4+
"author": "yousefed",
5+
"tags": [""],
6+
"dependencies": {
7+
"@blocknote/xl-pdf-exporter": "latest",
8+
"@react-pdf/renderer": "^4.0.0"
9+
},
10+
"pro": true
11+
}

0 commit comments

Comments
 (0)