Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion docs/content/docs/reference/editor/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Editor",
"pages": ["overview", "manipulating-content", "cursor-selections", "..."]
"pages": ["overview", "manipulating-content", "cursor-selections", "yjs-utilities", "..."]
}
6 changes: 6 additions & 0 deletions docs/content/docs/reference/editor/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ The editor can be configured with the following options when using `BlockNoteEdi
name="BlockNoteEditorOptions"
/>

## YJS Utilities

BlockNote provides utilities for working with YJS collaborative documents. These utilities allow you to convert between BlockNote blocks and YJS documents programmatically.

To read more about YJS utilities, see the [YJS Utilities](/docs/reference/editor/yjs-utilities) reference.

## Related Documentation

For more detailed information about specific areas:
Expand Down
259 changes: 259 additions & 0 deletions docs/content/docs/reference/editor/yjs-utilities.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
---
title: YJS Utilities
description: Utilities for converting between BlockNote blocks and YJS collaborative documents
imageTitle: YJS Utilities
---

# YJS Utilities

The `@blocknote/core/yjs` export provides utilities for converting between BlockNote blocks and YJS collaborative documents. These utilities are useful when you need to work with YJS documents outside of the standard collaboration setup, such as importing existing content or working with YJS documents programmatically.

<Callout type="warning">
**Important:** This package is for advanced use cases where you need to
convert between BlockNote blocks and YJS documents programmatically. For most
use cases, you should use the [collaboration
features](/docs/features/collaboration) directly instead.
</Callout>

## Import

```typescript
import {
blocksToYDoc,
blocksToYXmlFragment,
yDocToBlocks,
yXmlFragmentToBlocks,
} from "@blocknote/core/yjs";
```

## Overview

YJS utilities enable bidirectional conversion between:

- **BlockNote blocks** ↔ **Y.Doc** (YJS document)
- **BlockNote blocks** ↔ **Y.XmlFragment** (YJS XML fragment)

These conversions are essential for:

- Importing existing BlockNote content into a YJS document for collaboration
- Exporting content from a YJS document back to BlockNote blocks
- Working with YJS documents programmatically without an active editor instance

## Converting Blocks to YJS Documents

### `blocksToYDoc`

Converts BlockNote blocks into a Y.Doc. This is useful when importing existing content to a Y.Doc for the first time.

<Callout type="warning">
**Important:** This should not be used to rehydrate a Y.Doc from a database
once collaboration has begun, as all history will be lost.
</Callout>

```typescript
function blocksToYDoc<
BSchema extends BlockSchema,
ISchema extends InlineContentSchema,
SSchema extends StyleSchema,
>(
editor: BlockNoteEditor<BSchema, ISchema, SSchema>,
blocks: PartialBlock<BSchema, ISchema, SSchema>[],
xmlFragment?: string,
): Y.Doc;
```

**Parameters:**

- `editor` - The BlockNote editor instance
- `blocks` - Array of blocks to convert
- `xmlFragment` - Optional XML fragment name (defaults to `"prosemirror"`)

**Returns:** A new Y.Doc containing the converted blocks

**Example:**

```typescript
import { BlockNoteEditor } from "@blocknote/core";
import { blocksToYDoc } from "@blocknote/core/yjs";
import * as Y from "yjs";

const editor = BlockNoteEditor.create();

const blocks = [
{
type: "paragraph",
content: "Hello, world!",
},
{
type: "heading",
props: { level: 1 },
content: "My Document",
},
];

// Convert blocks to Y.Doc
const ydoc = blocksToYDoc(editor, blocks);

// Now you can use this Y.Doc with a YJS provider for collaboration
const provider = new WebrtcProvider("my-room", ydoc);
```

### `blocksToYXmlFragment`

Converts BlockNote blocks into a Y.XmlFragment. This is useful when you want to work with a specific XML fragment within a Y.Doc.

```typescript
function blocksToYXmlFragment<
BSchema extends BlockSchema,
ISchema extends InlineContentSchema,
SSchema extends StyleSchema,
>(
editor: BlockNoteEditor<BSchema, ISchema, SSchema>,
blocks: Block<BSchema, ISchema, SSchema>[],
xmlFragment?: Y.XmlFragment,
): Y.XmlFragment;
```

**Parameters:**

- `editor` - The BlockNote editor instance
- `blocks` - Array of blocks to convert
- `xmlFragment` - Optional existing Y.XmlFragment to populate (creates new one if not provided)

**Returns:** A Y.XmlFragment containing the converted blocks

**Example:**

```typescript
import { BlockNoteEditor } from "@blocknote/core";
import { blocksToYXmlFragment } from "@blocknote/core/yjs";
import * as Y from "yjs";

const editor = BlockNoteEditor.create();
const doc = new Y.Doc();
const fragment = doc.getXmlFragment("my-fragment");

const blocks = [
{
type: "paragraph",
content: "Content for fragment",
},
];

// Convert blocks to the XML fragment
blocksToYXmlFragment(editor, blocks, fragment);
```

## Converting YJS Documents to Blocks

### `yDocToBlocks`

Converts a Y.Doc back into BlockNote blocks. This is useful for reading content from a YJS document.

```typescript
function yDocToBlocks<
BSchema extends BlockSchema,
ISchema extends InlineContentSchema,
SSchema extends StyleSchema,
>(
editor: BlockNoteEditor<BSchema, ISchema, SSchema>,
ydoc: Y.Doc,
xmlFragment?: string,
): Block<BSchema, ISchema, SSchema>[];
```

**Parameters:**

- `editor` - The BlockNote editor instance
- `ydoc` - The Y.Doc to convert
- `xmlFragment` - Optional XML fragment name (defaults to `"prosemirror"`)

**Returns:** Array of BlockNote blocks

**Example:**

```typescript
import { BlockNoteEditor } from "@blocknote/core";
import { yDocToBlocks } from "@blocknote/core/yjs";
import * as Y from "yjs";

const editor = BlockNoteEditor.create();
const ydoc = new Y.Doc();

// ... Y.Doc is populated through collaboration or other means ...

// Convert Y.Doc back to blocks
const blocks = yDocToBlocks(editor, ydoc);

console.log(blocks); // Array of BlockNote blocks
```

### `yXmlFragmentToBlocks`

Converts a Y.XmlFragment back into BlockNote blocks.

```typescript
function yXmlFragmentToBlocks<
BSchema extends BlockSchema,
ISchema extends InlineContentSchema,
SSchema extends StyleSchema,
>(
editor: BlockNoteEditor<BSchema, ISchema, SSchema>,
xmlFragment: Y.XmlFragment,
): Block<BSchema, ISchema, SSchema>[];
```

**Parameters:**

- `editor` - The BlockNote editor instance
- `xmlFragment` - The Y.XmlFragment to convert

**Returns:** Array of BlockNote blocks

**Example:**

```typescript
import { BlockNoteEditor } from "@blocknote/core";
import { yXmlFragmentToBlocks } from "@blocknote/core/yjs";
import * as Y from "yjs";

const editor = BlockNoteEditor.create();
const doc = new Y.Doc();
const fragment = doc.getXmlFragment("my-fragment");

// ... Fragment is populated through collaboration or other means ...

// Convert fragment back to blocks
const blocks = yXmlFragmentToBlocks(editor, fragment);
```

## Round-trip Conversion

All conversion functions support round-trip conversion, meaning you can convert blocks → YJS → blocks and get back the same content:

```typescript
import { BlockNoteEditor } from "@blocknote/core";
import { blocksToYDoc, yDocToBlocks } from "@blocknote/core/yjs";

const editor = BlockNoteEditor.create();

const originalBlocks = [
{
type: "paragraph",
content: "Test content",
},
];

// Convert to Y.Doc and back
const ydoc = blocksToYDoc(editor, originalBlocks);
const convertedBlocks = yDocToBlocks(editor, ydoc);

// originalBlocks and convertedBlocks are equivalent
console.log(originalBlocks); // Same structure as convertedBlocks
```

## Related Documentation

- [Real-time Collaboration](/docs/features/collaboration) - Learn how to set up collaboration in BlockNote
- [Manipulating Content](/docs/reference/editor/manipulating-content) - Working with blocks and inline content
- [Server Processing](/docs/features/server-processing) - Server-side processing for BlockNote (uses these YJS utilities internally)
5 changes: 5 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
"types": "./types/src/i18n/index.d.ts",
"import": "./dist/locales.js",
"require": "./dist/locales.cjs"
},
"./yjs": {
"types": "./types/src/yjs/index.d.ts",
"import": "./dist/yjs.js",
"require": "./dist/yjs.cjs"
}
},
"scripts": {
Expand Down
31 changes: 17 additions & 14 deletions packages/core/src/api/nodeConversions/nodeToBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
getBlockCache,
getBlockSchema,
getInlineContentSchema,
getPmSchema,
getStyleSchema,
} from "../pmUtil.js";

Expand Down Expand Up @@ -503,26 +504,28 @@ export function docToBlocks<
S extends StyleSchema,
>(
doc: Node,
schema: Schema,
schema: Schema = getPmSchema(doc),
blockSchema: BSchema = getBlockSchema(schema) as BSchema,
inlineContentSchema: I = getInlineContentSchema(schema) as I,
styleSchema: S = getStyleSchema(schema) as S,
blockCache = getBlockCache(schema),
) {
const blocks: Block<BSchema, I, S>[] = [];
doc.firstChild!.descendants((node) => {
blocks.push(
nodeToBlock(
node,
schema,
blockSchema,
inlineContentSchema,
styleSchema,
blockCache,
),
);
return false;
});
if (doc.firstChild) {
doc.firstChild.descendants((node) => {
blocks.push(
nodeToBlock(
node,
schema,
blockSchema,
inlineContentSchema,
styleSchema,
blockCache,
),
);
return false;
});
}
return blocks;
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/yjs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./utils.js";
Loading
Loading