Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: file exporters (docx and pdf) #1143

Merged
merged 70 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
1656b55
cleaned serialization code
YousefED Oct 7, 2024
53eba5b
comments
YousefED Oct 7, 2024
f99904a
update snapshot
YousefED Oct 7, 2024
fe52693
add comment
YousefED Oct 7, 2024
81e3758
address comments
YousefED Oct 7, 2024
968bc38
fix lint
YousefED Oct 7, 2024
d796a6d
fix build
YousefED Oct 7, 2024
c692d3d
fix server test
YousefED Oct 7, 2024
18ceda5
wip
YousefED Oct 8, 2024
6d37862
wip
YousefED Oct 9, 2024
bffab06
wip
YousefED Oct 9, 2024
947111e
react pdf
YousefED Oct 10, 2024
1d0142a
misc
YousefED Oct 10, 2024
3245a9a
move files and clean partialBlocksToBlocksForTesting
YousefED Oct 11, 2024
168b8a1
wip
YousefED Oct 11, 2024
7299878
refactor
YousefED Oct 11, 2024
ba09fff
fix build
YousefED Oct 11, 2024
8e3d28e
update packagejson
YousefED Oct 11, 2024
df16b28
Merge branch 'main' into feature/file-exporters
YousefED Oct 11, 2024
caec139
fix lint
YousefED Oct 12, 2024
0056dec
fix lint
YousefED Oct 12, 2024
5a48b21
wip
YousefED Oct 18, 2024
adf189d
wip
YousefED Oct 19, 2024
20a912b
wip
YousefED Oct 19, 2024
d252a5c
Merge branch 'main' into feature/file-exporters
YousefED Oct 19, 2024
3ba81ec
fix numbered list bug
YousefED Oct 19, 2024
ae49c67
wip
YousefED Oct 19, 2024
2d57dba
small fixes
YousefED Oct 19, 2024
123c5ff
fix build
YousefED Oct 19, 2024
5615c72
fix lint
YousefED Oct 19, 2024
faecc52
wip
YousefED Oct 21, 2024
9607e34
fix build
YousefED Oct 21, 2024
c19841b
fix numbering, image
YousefED Oct 22, 2024
eacd13a
fix build
YousefED Oct 22, 2024
a96c1f7
docx font
YousefED Oct 23, 2024
84fc6b9
update template
YousefED Oct 23, 2024
e0fe318
full doc
YousefED Oct 23, 2024
46630f8
fix examples
YousefED Oct 23, 2024
1a6c31d
refactor
YousefED Oct 23, 2024
4ccc350
fix build
YousefED Oct 23, 2024
97a5f76
add option for resolveFile
YousefED Oct 23, 2024
b196572
add resolveFileUrl_DEV_ONLY to examples
YousefED Oct 23, 2024
ff2c41a
fix build
YousefED Oct 23, 2024
39c6042
fix emoji pdf
YousefED Oct 23, 2024
d67d3d3
fix pdf numbering and checkmarks
YousefED Oct 24, 2024
381471c
support colors
YousefED Oct 24, 2024
456b70e
Merge branch 'main' into feature/file-exporters
YousefED Oct 31, 2024
a8f157f
fix build
YousefED Oct 31, 2024
44ad258
fix package lock
YousefED Nov 1, 2024
e6988ba
fix tables
YousefED Nov 1, 2024
19d50f4
fix word hyperlink / caption styles
YousefED Nov 1, 2024
6d6650d
file handling docx, and start of code block
YousefED Nov 2, 2024
6bc21c0
file blocks pdf
YousefED Nov 2, 2024
45be776
split packages
YousefED Nov 3, 2024
9246c5e
wip
YousefED Nov 3, 2024
576574b
fix 2 pdf bugs
YousefED Nov 4, 2024
ec0b2d2
tests
YousefED Nov 4, 2024
67abe39
fix lint
YousefED Nov 4, 2024
3bcf431
fix build
YousefED Nov 4, 2024
2b93040
fix test
YousefED Nov 4, 2024
c86c89b
remove example pdf
YousefED Nov 4, 2024
8f9e112
remove react email
YousefED Nov 4, 2024
01a6886
header / footer support
YousefED Nov 5, 2024
55a825b
don't write files in tests
YousefED Nov 5, 2024
7e4f05d
corsproxy + docs
YousefED Nov 5, 2024
29401bd
add license
YousefED Nov 5, 2024
59da8c2
Merge remote-tracking branch 'origin/main' into feature/file-exporters
YousefED Nov 6, 2024
fab5016
fix build
YousefED Nov 6, 2024
94d020a
update docs
YousefED Nov 6, 2024
24e26c0
small fix
YousefED Nov 6, 2024
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
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@
"packages/website/docs/.vitepress": false,
"**/.*": false
},
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"[xml]": {
"editor.defaultFormatter": "redhat.vscode-xml"
}
}
2 changes: 1 addition & 1 deletion docs/components/pages/pricing/faq.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const faqs = [
question:
"What License is BlockNote using? Can I use it for commercial projects?",
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.
If you make changes to the BlockNote source files, you're expected to publish these changes so the wider community can benefit as well.`,
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.`,
},
// More questions...
];
Expand Down
4 changes: 3 additions & 1 deletion docs/pages/docs/editor-api/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
"manipulating-inline-content": "",
"cursor-selections": "",
"converting-blocks": "",
"server-processing": ""
"server-processing": "",
"export-to-pdf": "",
"export-to-docx": ""
}
129 changes: 129 additions & 0 deletions docs/pages/docs/editor-api/export-to-docx.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
title: Export to docx (Office Open XML)
description: Export BlockNote documents to a docx word (Office Open XML) file.
imageTitle: Export to docx
path: /docs/export-to-docx
---

import { Example } from "@/components/example";
import { Callout } from "nextra/components";

# Exporting blocks to docx

It's possible to export BlockNote documents to docx, completely client-side.

<Callout type={"info"}>
This feature is provided by the `@blocknote/xl-docx-exporter`. `xl-` packages
are fully open source, but released under a copyleft license. A commercial
license for usage in closed source, proprietary products comes as part of the
[Business subscription](/pricing).
</Callout>

First, install the `@blocknote/xl-docx-exporter` and `docx` packages:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the docs are nice and clear in general! But I would add a sentence at the start that the backbone of the PDF and docx conversions are the React-PDF and docxjs packages, and that customizing the export functionality requires being familiar with their APIs.


```bash
npm install @blocknote/xl-docx-exporter docx
```

Then, create an instance of the `DOCXExporter` class. This exposes the following methods:

```typescript
import {
DOCXExporter,
docxDefaultSchemaMappings,
} from "@blocknote/xl-docx-exporter";
import { Packer } from "docx";

// Create the exporter
const exporter = new DOCXExporter(editor.schema, docxDefaultSchemaMappings);

// Convert the blocks to a docxjs document
const docxDocument = await exporter.toDocxJsDocument(editor.document);

// Use docx to write to file:
await Packer.toBuffer(docxDocument);
```

See the [full example](/examples/interoperability/converting-blocks-to-docx) below:

<Example name="interoperability/converting-blocks-to-docx" />

### Customizing the Docx output file

`toDocxJsDocument` takes an optional `options` parameter, which allows you to customize document metadata (like the author) and section options (like headers and footers).

Example usage:

```typescript
import { Paragraph, TextRun } from "docx";

const doc = await exporter.toDocxJsDocument(testDocument, {
documentOptions: {
creator: "John Doe",
},
sectionOptions: {
headers: {
default: {
options: {
children: [new Paragraph({ children: [new TextRun("Header")] })],
},
},
},
footers: {
default: {
options: {
children: [new Paragraph({ children: [new TextRun("Footer")] })],
},
},
},
},
});
```

### Custom mappings / custom schemas

The `DOCXExporter` constructor takes a `schema`, `mappings` and `options` parameter.
A _mapping_ defines how to convert a BlockNote schema element (a Block, Inline Content, or Style) to a [docxjs](https://docx.js.org/) element.
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`:

For example, use the following code in case your schema has an `extraBlock` type:

```typescript
import {
DOCXExporter,
docxDefaultSchemaMappings,
} from "@blocknote/xl-docx-exporter";
import { Paragraph, TextRun } from "docx";

new DOCXExporter(schema, {
blockMapping: {
...docxDefaultSchemaMappings.blockMapping,
myCustomBlock: (block, exporter) => {
return new Paragraph({
children: [
new TextRun({
text: "My custom block",
}),
],
});
},
},
inlineContentMapping: docxDefaultSchemaMappings.inlineContentMapping,
styleMapping: docxDefaultSchemaMappings.styleMapping,
});
```

### Exporter options

The `DOCXExporter` constructor takes an optional `options` parameter.
While conversion happens on the client-side, the default setup uses a server hosted proxy to resolve files:

```typescript
const defaultOptions = {
// a function to resolve external resources in order to avoid CORS issues
// by default, this calls a BlockNote hosted server-side proxy to resolve files
resolveFileUrl: corsProxyResolveFileUrl,
// the colors to use in the Docx for things like highlighting, background colors and font colors.
colors: COLORS_DEFAULT, // defaults from @blocknote/core
};
```
108 changes: 108 additions & 0 deletions docs/pages/docs/editor-api/export-to-pdf.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
title: Export to PDF
description: Export BlockNote documents to a PDF.
imageTitle: Export to PDF
path: /docs/export-to-pdf
---

import { Example } from "@/components/example";
import { Callout } from "nextra/components";

# Exporting blocks to PDF

It's possible to export BlockNote documents to PDF, completely client-side.

<Callout type={"info"}>
This feature is provided by the `@blocknote/xl-pdf-exporter`. `xl-` packages
are fully open source, but released under a copyleft license. A commercial
license for usage in closed source, proprietary products comes as part of the
[Business subscription](/pricing).
</Callout>

First, install the `@blocknote/xl-pdf-exporter` and `@react-pdf/renderer` packages:

```bash
npm install @blocknote/xl-pdf-exporter @react-pdf/renderer
```

Then, create an instance of the `PDFExporter` class. This exposes the following methods:

```typescript
import {
PDFExporter,
pdfDefaultSchemaMappings,
} from "@blocknote/xl-pdf-exporter";
import * as ReactPDF from "@react-pdf/renderer";

// Create the exporter
const exporter = new PDFExporter(editor.schema, pdfDefaultSchemaMappings);

// Convert the blocks to a react-pdf document
const pdfDocument = await exporter.toReactPDFDocument(editor.document);

// Use react-pdf to write to file:
await ReactPDF.render(pdfDocument, `filename.pdf`);
```

See the [full example](/examples/interoperability/converting-blocks-to-pdf) with live PDF preview below:

<Example name="interoperability/converting-blocks-to-pdf" />

### Customizing the PDF

`toReactPDFDocument` takes an optional `options` parameter, which allows you to customize the header and footer of the PDF:

Example usage:

```typescript
import { Text } from "@react-pdf/renderer";
const pdfDocument = await exporter.toReactPDFDocument(editor.document, {
header: <Text>Header</Text>,
footer: <Text>Footer</Text>,
});
```

### Custom mappings / custom schemas

The `PDFExporter` constructor takes a `schema` and `mappings` parameter.
A _mapping_ defines how to convert a BlockNote schema element (a Block, Inline Content, or Style) to a React-PDF element.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Link to React-PDF needed to match the docx export docs

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`:

For example, use the following code in case your schema has an `extraBlock` type:

```typescript
import { PDFExporter, pdfDefaultSchemaMappings } from "@blocknote/xl-pdf-exporter";
import { Text } from "@react-pdf/renderer";

new PDFExporter(schema, {
blockMapping: {
...pdfDefaultSchemaMappings.blockMapping,
myCustomBlock: (block, exporter) => {
return <Text>My custom block</Text>;
},
},
inlineContentMapping: pdfDefaultSchemaMappings.inlineContentMapping,
styleMapping: pdfDefaultSchemaMappings.styleMapping,
});
```

### Exporter options

The `PDFExporter` constructor takes an optional `options` parameter.
While conversion happens on the client-side, the default setup uses two server based resources:

```typescript
const defaultOptions = {
// emoji source, this is passed to the react-pdf library (https://react-pdf.org/fonts#registeremojisource)
// these are loaded from cloudflare + twemoji by default
emojiSource: {
format: "png",
url: "https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/",
},
// a function to resolve external resources in order to avoid CORS issues
// by default, this calls a BlockNote hosted server-side proxy to resolve files
resolveFileUrl: corsProxyResolveFileUrl,
// the colors to use in the PDF for things like highlighting, background colors and font colors.
colors: COLORS_DEFAULT, // defaults from @blocknote/core
};
```
12 changes: 8 additions & 4 deletions docs/pages/pricing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export default function Pricing() {
price: { month: 90, year: 24 },
features: [
"Access to all Pro Examples",
"Prioritized Bug Reports and Feature Requests on GitHub",
"Prioritized Bug Reports on GitHub",
"Keep the open source library running and maintained",
"XL packages available for open source projects under AGPL-3.0"
],
githubTierId: "291733",
},
Expand All @@ -35,16 +36,19 @@ export default function Pricing() {
mostPopular: true,
description:
"Best for companies that want a direct line to the team.",
price: { month: 189, year: 48 },
price: { month: 390, year: 48 },
features: [
"Commercial license for XL packages:",
"XL: Multi-column layouts",
"XL: Export to PDF, Docx",
"Access to all Pro Examples",
"Prioritized Bug Reports and Feature Requests on GitHub",
"Prioritized Bug Reports on GitHub",
"Keep the open source library running and maintained",
"Logo on our website and repositories",
"Access to a private Discord channel with the maintainers",
"Up to 2 hours of individual support per month",
],
githubTierId: "413994",
githubTierId: "440968",
},
{
id: "tier-enterprise",
Expand Down
11 changes: 5 additions & 6 deletions examples/01-basic/05-removing-default-blocks/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import { useCreateBlockNote } from "@blocknote/react";

export default function App() {
// Disable the Audio and Image blocks from the built-in schema
// This is done by picking out the blocks you want to disable
const { audio, image, ...remainingBlockSpecs } = defaultBlockSpecs;

const schema = BlockNoteSchema.create({
blockSpecs: {
//first pass all the blockspecs from the built in, default block schema
...defaultBlockSpecs,

// disable blocks you don't want
audio: undefined as any,
image: undefined as any,
// remainingBlockSpecs contains all the other blocks
...remainingBlockSpecs,
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import "@blocknote/core/fonts/inter.css";
import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useState } from "react";
import { useCreateBlockNote } from "@blocknote/react";
import { useEffect, useState } from "react";

import "./styles.css";

Expand Down Expand Up @@ -35,6 +35,12 @@ export default function App() {
setHTML(html);
};

useEffect(() => {
// on mount, trigger initial conversion of the initial content to html
onChange();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// Renders the editor instance, and its contents as HTML below.
return (
<div className="wrapper">
Expand Down
10 changes: 8 additions & 2 deletions examples/05-interoperability/03-converting-blocks-to-md/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import "@blocknote/core/fonts/inter.css";
import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useState } from "react";
import { useCreateBlockNote } from "@blocknote/react";
import { useEffect, useState } from "react";

import "./styles.css";

Expand Down Expand Up @@ -35,6 +35,12 @@ export default function App() {
setMarkdown(markdown);
};

useEffect(() => {
// on mount, trigger initial conversion of the initial content to md
onChange();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// Renders the editor instance, and its contents as Markdown below.
return (
<div className={"wrapper"}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"playground": true,
"docs": true,
"author": "yousefed",
"tags": [""],
"dependencies": {
"@blocknote/xl-pdf-exporter": "latest",
"@react-pdf/renderer": "^4.0.0"
},
"pro": true
}
Loading
Loading