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

feat: server-side processing #451

Merged
merged 27 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
9 changes: 5 additions & 4 deletions docs/pages/docs/editor-api/_meta.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"manipulating-blocks":"",
"manipulating-inline-content":"",
"cursor-selections":"",
"converting-blocks":""
"manipulating-blocks": "",
"manipulating-inline-content": "",
"cursor-selections": "",
"converting-blocks": "",
"server-processing": ""
}
25 changes: 23 additions & 2 deletions docs/pages/docs/editor-api/converting-blocks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,30 @@ Tries to create `Block` and `InlineContent` objects based on Markdown syntax, th

<Example name="interoperability/converting-blocks-from-md" />

## HTML
## Export HTML (for static rendering)

The editor exposes functions to convert Blocks to and from HTML. Converting Blocks to HTML will lose some information such as the nesting of nodes in order to export a simple HTML structure.
Use `blocksToFullHTML` to export the entire document with all structure, styles and formatting.
The exported HTML is the same as BlockNote would use to render the editor, and includes all structure for nested blocks.

For example, you an use this for static rendering documents that have been created in the editor.
Make sure to include the same stylesheets when you want to render the output HTML ([see example](/examples/backend/rendering-static-documents)).

```typescript
blocksToFullHTML(blocks?: Block[]): string;

// Usage
const HTMLFromBlocks = editor.blocksToFullHTML(blocks);
```

`blocks:` The blocks to convert. If not provided, the entire document (all top-level blocks) is used.

`returns:` The blocks, exported to an HTML string.

## HTML (for interoperability)

The editor exposes functions to convert Blocks to and from HTML for interoperability with other applications.

Converting Blocks to HTML this way will lose some information such as the nesting of nodes in order to export a simple HTML structure.

### Converting Blocks to HTML

Expand Down
59 changes: 59 additions & 0 deletions docs/pages/docs/editor-api/server-processing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Server-side processing
description: Use `ServerBlockNoteEditor` to process Blocks on the server.
imageTitle: Server-side processing
path: /docs/server-side-processing
YousefED marked this conversation as resolved.
Show resolved Hide resolved
---

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

# Server-side processing

While you can use the `BlockNoteEditor` on the client side,
you can also use `ServerBlockNoteEditor` from `@blocknote/server-util` to process BlockNote documents on the server.

For example, use the following code to convert a BlockNote document to HTML on the server:

```tsx
import { ServerBlockNoteEditor } from "@blocknote/server-util";

const editor = ServerBlockNoteEditor.create();
const html = await editor.blocksToFullHTML(blocks);
```

`ServerBlockNoteEditor.create` takes the same BlockNoteEditorOptions as `useCreateBlockNote` and `BlockNoteEditor.create` ([see docs](/docs/editor-basics/setup)),
so you can pass the same configuration (for example, your custom schema) to your server-side BlockNote editor as on the client.

## Functions for converting blocks

`ServerBlockNoteEditor` exposes the same functions for [converting blocks as the client side editor](/docs/converting-blocks):

- `blocksToFullHTML`
- `blocksToHTMLLossy` and `tryParseHTMLToBlocks`
- `blocksToMarkdownLossy` and `tryParseMarkdownToBlocks`

## Yjs processing

Additionally, `ServerBlockNoteEditor` provides functions for processing Yjs documents in case you use Yjs collaboration:

- `yDocToBlocks` or `yXmlFragmentToBlocks`: use this to convert a Yjs document or XML Fragment to BlockNote blocks
- `blocksToYDoc` or `blocksToYXmlFragment`: use this to convert a BlockNote document (blocks) to a Yjs document or XML Fragment

## React compatibility

If you use [custom schemas in React](/docs/custom-schemas), you can use the same schema on the server side.
Functions like `blocksToFullHTML` will use your custom React rendering functions to export blocks to HTML, similar to how these functions work on the client.
However, it could be that your React components require access to a React context (e.g. a theme or localization context).

For these use-cases, we provide a function `withReactContext` that allows you to pass a React context to the server-side editor.
This example exports a BlockNote document to HTML within a React context `YourContext`, so that even Custom Blocks built in React that require `YourContext` will be exported correctly:

```tsx
const html = await editor.withReactContext(
({ children }) => (
<YourContext.Provider value={true}>{children}</YourContext.Provider>
),
async () => editor.blocksToFullHTML(blocks),
);
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"playground": true,
"docs": true,
"author": "yousefed",
"tags": ["server"],
"dependencies": {
"@blocknote/server-util": "latest"
}
}
60 changes: 60 additions & 0 deletions examples/02-backend/04-rendering-static-documents/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import "@blocknote/core/fonts/inter.css";
import "@blocknote/core/style.css";

/**
On Server Side, you can use the ServerBlockNoteEditor to render BlockNote documents to HTML. e.g.:

import { ServerBlockNoteEditor } from "@blocknote/server-util";

const editor = ServerBlockNoteEditor.create();
const html = await editor.blocksToFullHTML(document);

You can then use render this HTML as a static page or send it to the client. Make sure to include the editor stylesheets:

import "@blocknote/core/fonts/inter.css";
import "@blocknote/core/style.css";

This example has the HTML hard-coded, but shows at least how the document will be rendered when the appropriate style sheets are loaded.
*/

export default function App() {
// This HTML is generated by the ServerBlockNoteEditor.blocksToFullHTML method
const html = `<div class="bn-block-group" data-node-type="blockGroup">
<div class="bn-block-outer" data-node-type="blockOuter" data-id="1" data-text-color="yellow" data-background-color="blue">
<div class="bn-block" data-node-type="blockContainer" data-id="1" data-text-color="yellow" data-background-color="blue">
<div class="bn-block-content" data-content-type="heading" data-text-alignment="right" data-level="2">
<h2 class="bn-inline-content">
<strong><u>Heading </u></strong><em><s>2</s></em>
</h2>
</div>
<div class="bn-block-group" data-node-type="blockGroup">
<div class="bn-block-outer" data-node-type="blockOuter" data-id="2" data-background-color="red">
<div class="bn-block" data-node-type="blockContainer" data-id="2" data-background-color="red">
<div class="bn-block-content" data-content-type="paragraph">
<p class="bn-inline-content">Paragraph</p>
</div>
</div>
</div>
<div class="bn-block-outer" data-node-type="blockOuter" data-id="3">
<div class="bn-block" data-node-type="blockContainer" data-id="3">
<div class="bn-block-content" data-content-type="bulletListItem">
<p class="bn-inline-content">list item</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;

// Renders the editor instance using a React component.
return (
<div className="bn-container">
<div
className=" bn-default-styles"
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
);
}
7 changes: 7 additions & 0 deletions examples/02-backend/04-rendering-static-documents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Rendering static documents

This example shows how you can use HTML exported using the `blocksToFullHTML` and render it as a static document (a view-only document, without the editor). You can use this for example if you use BlockNote to edit blog posts in a CMS, but want to display non-editable static, published pages to end-users.

**Relevant Docs:**

- [Server-side processing](/docs/editor-api/server-processing)
14 changes: 14 additions & 0 deletions examples/02-backend/04-rendering-static-documents/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html lang="en">
<head>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
</script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rendering static documents</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions examples/02-backend/04-rendering-static-documents/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 "./App";

const root = createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
38 changes: 38 additions & 0 deletions examples/02-backend/04-rendering-static-documents/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@blocknote/example-rendering-static-documents",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"private": true,
"version": "0.12.4",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --max-warnings 0"
},
"dependencies": {
"@blocknote/core": "latest",
"@blocknote/react": "latest",
"@blocknote/ariakit": "latest",
"@blocknote/mantine": "latest",
"@blocknote/shadcn": "latest",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@blocknote/server-util": "latest"
},
"devDependencies": {
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^4.0.4",
"eslint": "^8.10.0",
"vite": "^4.4.8"
},
"eslintConfig": {
"extends": [
"../../../.eslintrc.js"
]
},
"eslintIgnore": [
"dist"
]
}
36 changes: 36 additions & 0 deletions examples/02-backend/04-rendering-static-documents/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true
},
"include": [
"."
],
"__ADD_FOR_LOCAL_DEV_references": [
{
"path": "../../../packages/core/"
},
{
"path": "../../../packages/react/"
}
]
}
32 changes: 32 additions & 0 deletions examples/02-backend/04-rendering-static-documents/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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";
// import eslintPlugin from "vite-plugin-eslint";
// https://vitejs.dev/config/
export default defineConfig((conf) => ({
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/"
),
} as any),
},
}));
Loading
Loading