|
| 1 | +--- |
| 2 | +title: YJS Utilities |
| 3 | +description: Utilities for converting between BlockNote blocks and YJS collaborative documents |
| 4 | +imageTitle: YJS Utilities |
| 5 | +--- |
| 6 | + |
| 7 | +# YJS Utilities |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +## Import |
| 12 | + |
| 13 | +```typescript |
| 14 | +import { |
| 15 | + blocksToYDoc, |
| 16 | + blocksToYXmlFragment, |
| 17 | + yDocToBlocks, |
| 18 | + yXmlFragmentToBlocks, |
| 19 | + _blocksToProsemirrorNode, |
| 20 | + _prosemirrorJSONToBlocks, |
| 21 | +} from "@blocknote/core/yjs"; |
| 22 | +``` |
| 23 | + |
| 24 | +## Overview |
| 25 | + |
| 26 | +YJS utilities enable bidirectional conversion between: |
| 27 | + |
| 28 | +- **BlockNote blocks** ↔ **Y.Doc** (YJS document) |
| 29 | +- **BlockNote blocks** ↔ **Y.XmlFragment** (YJS XML fragment) |
| 30 | +- **BlockNote blocks** ↔ **Prosemirror nodes** (intermediate format) |
| 31 | + |
| 32 | +These conversions are essential for: |
| 33 | + |
| 34 | +- Importing existing BlockNote content into a YJS document for collaboration |
| 35 | +- Exporting content from a YJS document back to BlockNote blocks |
| 36 | +- Working with YJS documents programmatically without an active editor instance |
| 37 | + |
| 38 | +## Converting Blocks to YJS Documents |
| 39 | + |
| 40 | +### `blocksToYDoc` |
| 41 | + |
| 42 | +Converts BlockNote blocks into a Y.Doc. This is useful when importing existing content to a Y.Doc for the first time. |
| 43 | + |
| 44 | +<Callout type="warning"> |
| 45 | + **Important:** This should not be used to rehydrate a Y.Doc from a database once collaboration has begun, as all history will be lost. |
| 46 | +</Callout> |
| 47 | + |
| 48 | +```typescript |
| 49 | +function blocksToYDoc< |
| 50 | + BSchema extends BlockSchema, |
| 51 | + ISchema extends InlineContentSchema, |
| 52 | + SSchema extends StyleSchema, |
| 53 | +>( |
| 54 | + editor: BlockNoteEditor<BSchema, ISchema, SSchema>, |
| 55 | + blocks: PartialBlock<BSchema, ISchema, SSchema>[], |
| 56 | + xmlFragment?: string |
| 57 | +): Y.Doc |
| 58 | +``` |
| 59 | + |
| 60 | +**Parameters:** |
| 61 | + |
| 62 | +- `editor` - The BlockNote editor instance |
| 63 | +- `blocks` - Array of blocks to convert |
| 64 | +- `xmlFragment` - Optional XML fragment name (defaults to `"prosemirror"`) |
| 65 | + |
| 66 | +**Returns:** A new Y.Doc containing the converted blocks |
| 67 | + |
| 68 | +**Example:** |
| 69 | + |
| 70 | +```typescript |
| 71 | +import { BlockNoteEditor } from "@blocknote/core"; |
| 72 | +import { blocksToYDoc } from "@blocknote/core/yjs"; |
| 73 | +import * as Y from "yjs"; |
| 74 | +
|
| 75 | +const editor = BlockNoteEditor.create(); |
| 76 | +
|
| 77 | +const blocks = [ |
| 78 | + { |
| 79 | + type: "paragraph", |
| 80 | + content: "Hello, world!", |
| 81 | + }, |
| 82 | + { |
| 83 | + type: "heading", |
| 84 | + props: { level: 1 }, |
| 85 | + content: "My Document", |
| 86 | + }, |
| 87 | +]; |
| 88 | +
|
| 89 | +// Convert blocks to Y.Doc |
| 90 | +const ydoc = blocksToYDoc(editor, blocks); |
| 91 | +
|
| 92 | +// Now you can use this Y.Doc with a YJS provider for collaboration |
| 93 | +const provider = new WebrtcProvider("my-room", ydoc); |
| 94 | +``` |
| 95 | + |
| 96 | +### `blocksToYXmlFragment` |
| 97 | + |
| 98 | +Converts BlockNote blocks into a Y.XmlFragment. This is useful when you want to work with a specific XML fragment within a Y.Doc. |
| 99 | + |
| 100 | +```typescript |
| 101 | +function blocksToYXmlFragment< |
| 102 | + BSchema extends BlockSchema, |
| 103 | + ISchema extends InlineContentSchema, |
| 104 | + SSchema extends StyleSchema, |
| 105 | +>( |
| 106 | + editor: BlockNoteEditor<BSchema, ISchema, SSchema>, |
| 107 | + blocks: Block<BSchema, ISchema, SSchema>[], |
| 108 | + xmlFragment?: Y.XmlFragment |
| 109 | +): Y.XmlFragment |
| 110 | +``` |
| 111 | + |
| 112 | +**Parameters:** |
| 113 | + |
| 114 | +- `editor` - The BlockNote editor instance |
| 115 | +- `blocks` - Array of blocks to convert |
| 116 | +- `xmlFragment` - Optional existing Y.XmlFragment to populate (creates new one if not provided) |
| 117 | + |
| 118 | +**Returns:** A Y.XmlFragment containing the converted blocks |
| 119 | + |
| 120 | +**Example:** |
| 121 | + |
| 122 | +```typescript |
| 123 | +import { BlockNoteEditor } from "@blocknote/core"; |
| 124 | +import { blocksToYXmlFragment } from "@blocknote/core/yjs"; |
| 125 | +import * as Y from "yjs"; |
| 126 | +
|
| 127 | +const editor = BlockNoteEditor.create(); |
| 128 | +const doc = new Y.Doc(); |
| 129 | +const fragment = doc.getXmlFragment("my-fragment"); |
| 130 | +
|
| 131 | +const blocks = [ |
| 132 | + { |
| 133 | + type: "paragraph", |
| 134 | + content: "Content for fragment", |
| 135 | + }, |
| 136 | +]; |
| 137 | +
|
| 138 | +// Convert blocks to the XML fragment |
| 139 | +blocksToYXmlFragment(editor, blocks, fragment); |
| 140 | +``` |
| 141 | + |
| 142 | +## Converting YJS Documents to Blocks |
| 143 | + |
| 144 | +### `yDocToBlocks` |
| 145 | + |
| 146 | +Converts a Y.Doc back into BlockNote blocks. This is useful for reading content from a YJS document. |
| 147 | + |
| 148 | +```typescript |
| 149 | +function yDocToBlocks< |
| 150 | + BSchema extends BlockSchema, |
| 151 | + ISchema extends InlineContentSchema, |
| 152 | + SSchema extends StyleSchema, |
| 153 | +>( |
| 154 | + editor: BlockNoteEditor<BSchema, ISchema, SSchema>, |
| 155 | + ydoc: Y.Doc, |
| 156 | + xmlFragment?: string |
| 157 | +): Block<BSchema, ISchema, SSchema>[] |
| 158 | +``` |
| 159 | + |
| 160 | +**Parameters:** |
| 161 | + |
| 162 | +- `editor` - The BlockNote editor instance |
| 163 | +- `ydoc` - The Y.Doc to convert |
| 164 | +- `xmlFragment` - Optional XML fragment name (defaults to `"prosemirror"`) |
| 165 | + |
| 166 | +**Returns:** Array of BlockNote blocks |
| 167 | + |
| 168 | +**Example:** |
| 169 | + |
| 170 | +```typescript |
| 171 | +import { BlockNoteEditor } from "@blocknote/core"; |
| 172 | +import { yDocToBlocks } from "@blocknote/core/yjs"; |
| 173 | +import * as Y from "yjs"; |
| 174 | +
|
| 175 | +const editor = BlockNoteEditor.create(); |
| 176 | +const ydoc = new Y.Doc(); |
| 177 | +
|
| 178 | +// ... Y.Doc is populated through collaboration or other means ... |
| 179 | +
|
| 180 | +// Convert Y.Doc back to blocks |
| 181 | +const blocks = yDocToBlocks(editor, ydoc); |
| 182 | +
|
| 183 | +console.log(blocks); // Array of BlockNote blocks |
| 184 | +``` |
| 185 | + |
| 186 | +### `yXmlFragmentToBlocks` |
| 187 | + |
| 188 | +Converts a Y.XmlFragment back into BlockNote blocks. |
| 189 | + |
| 190 | +```typescript |
| 191 | +function yXmlFragmentToBlocks< |
| 192 | + BSchema extends BlockSchema, |
| 193 | + ISchema extends InlineContentSchema, |
| 194 | + SSchema extends StyleSchema, |
| 195 | +>( |
| 196 | + editor: BlockNoteEditor<BSchema, ISchema, SSchema>, |
| 197 | + xmlFragment: Y.XmlFragment |
| 198 | +): Block<BSchema, ISchema, SSchema>[] |
| 199 | +``` |
| 200 | + |
| 201 | +**Parameters:** |
| 202 | + |
| 203 | +- `editor` - The BlockNote editor instance |
| 204 | +- `xmlFragment` - The Y.XmlFragment to convert |
| 205 | + |
| 206 | +**Returns:** Array of BlockNote blocks |
| 207 | + |
| 208 | +**Example:** |
| 209 | + |
| 210 | +```typescript |
| 211 | +import { BlockNoteEditor } from "@blocknote/core"; |
| 212 | +import { yXmlFragmentToBlocks } from "@blocknote/core/yjs"; |
| 213 | +import * as Y from "yjs"; |
| 214 | +
|
| 215 | +const editor = BlockNoteEditor.create(); |
| 216 | +const doc = new Y.Doc(); |
| 217 | +const fragment = doc.getXmlFragment("my-fragment"); |
| 218 | +
|
| 219 | +// ... Fragment is populated through collaboration or other means ... |
| 220 | +
|
| 221 | +// Convert fragment back to blocks |
| 222 | +const blocks = yXmlFragmentToBlocks(editor, fragment); |
| 223 | +``` |
| 224 | + |
| 225 | +## Prosemirror Conversions |
| 226 | + |
| 227 | +### `_blocksToProsemirrorNode` |
| 228 | + |
| 229 | +Converts BlockNote blocks to a Prosemirror node. This is an intermediate conversion step used internally. |
| 230 | + |
| 231 | +```typescript |
| 232 | +function _blocksToProsemirrorNode< |
| 233 | + BSchema extends BlockSchema, |
| 234 | + ISchema extends InlineContentSchema, |
| 235 | + SSchema extends StyleSchema, |
| 236 | +>( |
| 237 | + editor: BlockNoteEditor<BSchema, ISchema, SSchema>, |
| 238 | + blocks: PartialBlock<BSchema, ISchema, SSchema>[] |
| 239 | +): Node |
| 240 | +``` |
| 241 | + |
| 242 | +**Parameters:** |
| 243 | + |
| 244 | +- `editor` - The BlockNote editor instance |
| 245 | +- `blocks` - Array of blocks to convert |
| 246 | + |
| 247 | +**Returns:** Prosemirror root node |
| 248 | + |
| 249 | +### `_prosemirrorJSONToBlocks` |
| 250 | + |
| 251 | +Converts Prosemirror JSON to BlockNote blocks. |
| 252 | + |
| 253 | +```typescript |
| 254 | +function _prosemirrorJSONToBlocks< |
| 255 | + BSchema extends BlockSchema, |
| 256 | + ISchema extends InlineContentSchema, |
| 257 | + SSchema extends StyleSchema, |
| 258 | +>( |
| 259 | + editor: BlockNoteEditor<BSchema, ISchema, SSchema>, |
| 260 | + json: any |
| 261 | +): Block<BSchema, ISchema, SSchema>[] |
| 262 | +``` |
| 263 | + |
| 264 | +**Parameters:** |
| 265 | + |
| 266 | +- `editor` - The BlockNote editor instance |
| 267 | +- `json` - Prosemirror JSON object |
| 268 | + |
| 269 | +**Returns:** Array of BlockNote blocks |
| 270 | + |
| 271 | +## Complete Example: Importing Existing Content |
| 272 | + |
| 273 | +Here's a complete example showing how to import existing BlockNote content into a YJS document for collaboration: |
| 274 | + |
| 275 | +```typescript |
| 276 | +import { BlockNoteEditor } from "@blocknote/core"; |
| 277 | +import { blocksToYDoc, yDocToBlocks } from "@blocknote/core/yjs"; |
| 278 | +import { WebrtcProvider } from "y-webrtc"; |
| 279 | +import * as Y from "yjs"; |
| 280 | +
|
| 281 | +// Create editor |
| 282 | +const editor = BlockNoteEditor.create(); |
| 283 | +
|
| 284 | +// Existing content you want to import |
| 285 | +const existingBlocks = [ |
| 286 | + { |
| 287 | + type: "heading", |
| 288 | + props: { level: 1 }, |
| 289 | + content: "Welcome to Collaboration", |
| 290 | + }, |
| 291 | + { |
| 292 | + type: "paragraph", |
| 293 | + content: "This content will be synced across all users.", |
| 294 | + }, |
| 295 | +]; |
| 296 | +
|
| 297 | +// Convert blocks to Y.Doc |
| 298 | +const ydoc = blocksToYDoc(editor, existingBlocks); |
| 299 | +
|
| 300 | +// Set up collaboration provider |
| 301 | +const provider = new WebrtcProvider("my-room", ydoc); |
| 302 | +
|
| 303 | +// Create editor with collaboration |
| 304 | +const collaborativeEditor = BlockNoteEditor.create({ |
| 305 | + collaboration: { |
| 306 | + provider, |
| 307 | + fragment: ydoc.getXmlFragment("prosemirror"), |
| 308 | + user: { |
| 309 | + name: "User 1", |
| 310 | + color: "#ff0000", |
| 311 | + }, |
| 312 | + }, |
| 313 | +}); |
| 314 | +
|
| 315 | +// Later, you can read the content back |
| 316 | +const currentBlocks = yDocToBlocks(editor, ydoc); |
| 317 | +console.log("Current document:", currentBlocks); |
| 318 | +``` |
| 319 | + |
| 320 | +## Round-trip Conversion |
| 321 | + |
| 322 | +All conversion functions support round-trip conversion, meaning you can convert blocks → YJS → blocks and get back the same content: |
| 323 | + |
| 324 | +```typescript |
| 325 | +import { BlockNoteEditor } from "@blocknote/core"; |
| 326 | +import { blocksToYDoc, yDocToBlocks } from "@blocknote/core/yjs"; |
| 327 | +
|
| 328 | +const editor = BlockNoteEditor.create(); |
| 329 | +
|
| 330 | +const originalBlocks = [ |
| 331 | + { |
| 332 | + type: "paragraph", |
| 333 | + content: "Test content", |
| 334 | + }, |
| 335 | +]; |
| 336 | +
|
| 337 | +// Convert to Y.Doc and back |
| 338 | +const ydoc = blocksToYDoc(editor, originalBlocks); |
| 339 | +const convertedBlocks = yDocToBlocks(editor, ydoc); |
| 340 | +
|
| 341 | +// originalBlocks and convertedBlocks are equivalent |
| 342 | +console.log(originalBlocks); // Same structure as convertedBlocks |
| 343 | +``` |
| 344 | + |
| 345 | +## Related Documentation |
| 346 | + |
| 347 | +- [Real-time Collaboration](/docs/features/collaboration) - Learn how to set up collaboration in BlockNote |
| 348 | +- [Manipulating Content](/docs/reference/editor/manipulating-content) - Working with blocks and inline content |
| 349 | +- [Server Utilities](/docs/reference/server-util) - Server-side utilities for BlockNote (uses these YJS utilities internally) |
| 350 | + |
0 commit comments