Skip to content

Commit b17bb09

Browse files
committed
Merge branch 'feature/ai' of github.com:TypeCellOS/BlockNote into feature/ai
2 parents 8e0fb6c + 61b3b7e commit b17bb09

31 files changed

+1119
-415
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262

6363
- name: Soft release
6464
id: soft-release
65-
run: pnpx pkg-pr-new publish './packages/*' --compact
65+
run: pnpx pkg-pr-new publish './packages/*' # TODO disabled only for AI branch--compact
6666

6767
playwright:
6868
name: "Playwright Tests - ${{ matrix.browser }}"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"playground": true,
3+
"docs": false,
4+
"author": "nperez0111",
5+
"tags": ["Advanced", "Development", "Collaboration"],
6+
"dependencies": {
7+
"y-partykit": "^0.0.25",
8+
"yjs": "^13.6.15"
9+
}
10+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import "@blocknote/core/fonts/inter.css";
2+
import { useCreateBlockNote } from "@blocknote/react";
3+
import { BlockNoteView } from "@blocknote/mantine";
4+
import "@blocknote/mantine/style.css";
5+
import YPartyKitProvider from "y-partykit/provider";
6+
import * as Y from "yjs";
7+
import { useEffect } from "react";
8+
import { useState } from "react";
9+
10+
// Sets up Yjs document and PartyKit Yjs provider.
11+
const doc = new Y.Doc();
12+
const provider = new YPartyKitProvider(
13+
"blocknote-dev.yousefed.partykit.dev",
14+
// Use a unique name as a "room" for your application.
15+
"your-project-name-room",
16+
doc,
17+
);
18+
19+
export default function App() {
20+
const editor = useCreateBlockNote({
21+
collaboration: {
22+
// The Yjs Provider responsible for transporting updates:
23+
provider,
24+
// Where to store BlockNote data in the Y.Doc:
25+
fragment: doc.getXmlFragment("document-store"),
26+
// Information (name and color) for this user:
27+
user: {
28+
name: "My Username",
29+
color: "#ff0000",
30+
},
31+
},
32+
});
33+
const [isForked, setIsForked] = useState(false);
34+
35+
useEffect(() => {
36+
editor.forkYDocPlugin!.on("forked", setIsForked);
37+
}, [editor]);
38+
39+
// Renders the editor instance.
40+
return (
41+
<>
42+
<button
43+
onClick={() => {
44+
editor.forkYDocPlugin!.fork();
45+
}}
46+
disabled={isForked}
47+
>
48+
Pause syncing
49+
</button>
50+
<button
51+
onClick={() => {
52+
editor.forkYDocPlugin!.merge({ keepChanges: true });
53+
}}
54+
disabled={!isForked}
55+
>
56+
Play (accept changes)
57+
</button>
58+
<button
59+
onClick={() => {
60+
editor.forkYDocPlugin!.merge({ keepChanges: false });
61+
}}
62+
disabled={!isForked}
63+
>
64+
Play (reject changes)
65+
</button>
66+
<div>
67+
<p>Forked: {isForked ? "Yes" : "No"}</p>
68+
</div>
69+
<BlockNoteView editor={editor} />
70+
</>
71+
);
72+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Collaborative Editing with Forking
2+
3+
In this example, we can fork a document and edit it independently of other collaborators. Then, we can choose to merge the changes back into the original document, or discard the changes.
4+
5+
**Try it out:** Open this page in a new browser tab or window to see it in action!
6+
7+
**Relevant Docs:**
8+
9+
- [Editor Setup](/docs/editor-basics/setup)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<html lang="en">
2+
<head>
3+
<script>
4+
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
5+
</script>
6+
<meta charset="UTF-8" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>Collaborative Editing with Forking</title>
9+
</head>
10+
<body>
11+
<div id="root"></div>
12+
<script type="module" src="./main.tsx"></script>
13+
</body>
14+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
2+
import React from "react";
3+
import { createRoot } from "react-dom/client";
4+
import App from "./App.jsx";
5+
6+
const root = createRoot(document.getElementById("root")!);
7+
root.render(
8+
<React.StrictMode>
9+
<App />
10+
</React.StrictMode>
11+
);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "@blocknote/example-collaboration-forking",
3+
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
4+
"private": true,
5+
"version": "0.12.4",
6+
"scripts": {
7+
"start": "vite",
8+
"dev": "vite",
9+
"build:prod": "tsc && vite build",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"@blocknote/core": "latest",
14+
"@blocknote/react": "latest",
15+
"@blocknote/ariakit": "latest",
16+
"@blocknote/mantine": "latest",
17+
"@blocknote/shadcn": "latest",
18+
"react": "^18.3.1",
19+
"react-dom": "^18.3.1",
20+
"y-partykit": "^0.0.25",
21+
"yjs": "^13.6.15"
22+
},
23+
"devDependencies": {
24+
"@types/react": "^18.0.25",
25+
"@types/react-dom": "^18.0.9",
26+
"@vitejs/plugin-react": "^4.3.1",
27+
"vite": "^5.3.4"
28+
}
29+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
3+
"compilerOptions": {
4+
"target": "ESNext",
5+
"useDefineForClassFields": true,
6+
"lib": [
7+
"DOM",
8+
"DOM.Iterable",
9+
"ESNext"
10+
],
11+
"allowJs": false,
12+
"skipLibCheck": true,
13+
"esModuleInterop": false,
14+
"allowSyntheticDefaultImports": true,
15+
"strict": true,
16+
"forceConsistentCasingInFileNames": true,
17+
"module": "ESNext",
18+
"moduleResolution": "bundler",
19+
"resolveJsonModule": true,
20+
"isolatedModules": true,
21+
"noEmit": true,
22+
"jsx": "react-jsx",
23+
"composite": true
24+
},
25+
"include": [
26+
"."
27+
],
28+
"__ADD_FOR_LOCAL_DEV_references": [
29+
{
30+
"path": "../../../packages/core/"
31+
},
32+
{
33+
"path": "../../../packages/react/"
34+
}
35+
]
36+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
2+
import react from "@vitejs/plugin-react";
3+
import * as fs from "fs";
4+
import * as path from "path";
5+
import { defineConfig } from "vite";
6+
// import eslintPlugin from "vite-plugin-eslint";
7+
// https://vitejs.dev/config/
8+
export default defineConfig((conf) => ({
9+
plugins: [react()],
10+
optimizeDeps: {},
11+
build: {
12+
sourcemap: true,
13+
},
14+
resolve: {
15+
alias:
16+
conf.command === "build" ||
17+
!fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
18+
? {}
19+
: ({
20+
// Comment out the lines below to load a built version of blocknote
21+
// or, keep as is to load live from sources with live reload working
22+
"@blocknote/core": path.resolve(
23+
__dirname,
24+
"../../packages/core/src/"
25+
),
26+
"@blocknote/react": path.resolve(
27+
__dirname,
28+
"../../packages/react/src/"
29+
),
30+
} as any),
31+
},
32+
}));

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"better-sqlite3",
2929
"canvas",
3030
"esbuild",
31+
"msw",
3132
"nx"
3233
]
3334
},

packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"dependencies": {
7777
"@emoji-mart/data": "^1.2.1",
7878
"@shikijs/types": "3.2.1",
79-
"@tiptap/core": "^2.11.5",
79+
"@tiptap/core": "^2.12.0",
8080
"@tiptap/extension-bold": "^2.11.5",
8181
"@tiptap/extension-code": "^2.11.5",
8282
"@tiptap/extension-gapcursor": "^2.11.5",
@@ -90,7 +90,7 @@
9090
"@tiptap/extension-table-header": "^2.11.5",
9191
"@tiptap/extension-text": "^2.11.5",
9292
"@tiptap/extension-underline": "^2.11.5",
93-
"@tiptap/pm": "^2.11.5",
93+
"@tiptap/pm": "^2.12.0",
9494
"emoji-mart": "^5.6.0",
9595
"hast-util-from-dom": "^5.0.1",
9696
"prosemirror-dropcursor": "^1.8.1",

packages/core/src/editor/BlockNoteEditor.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,13 @@ import {
114114
import { nestedListsToBlockNoteStructure } from "../api/parsers/html/util/nestedLists.js";
115115
import { CodeBlockOptions } from "../blocks/CodeBlockContent/CodeBlockContent.js";
116116
import type { ThreadStore, User } from "../comments/index.js";
117-
import { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
118-
import "../style.css";
117+
import type { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
118+
import type { ForkYDocPlugin } from "../extensions/Collaboration/ForkYDocPlugin.js";
119119
import { EventEmitter } from "../util/EventEmitter.js";
120120
import { BlockNoteExtension } from "./BlockNoteExtension.js";
121121

122+
import "../style.css";
123+
122124
/**
123125
* A factory function that returns a BlockNoteExtension
124126
* This is useful so we can create extensions that require an editor instance
@@ -416,7 +418,7 @@ export class BlockNoteEditor<
416418
/**
417419
* extensions that are added to the editor, can be tiptap extensions or prosemirror plugins
418420
*/
419-
public readonly extensions: Record<string, SupportedExtension> = {};
421+
public extensions: Record<string, SupportedExtension> = {};
420422

421423
/**
422424
* Boolean indicating whether the editor is in headless mode.
@@ -485,8 +487,10 @@ export class BlockNoteEditor<
485487

486488
private readonly showSelectionPlugin: ShowSelectionPlugin;
487489

488-
private readonly cursorPlugin: CursorPlugin;
489-
490+
/**
491+
* The plugin for forking a document, only defined if in collaboration mode
492+
*/
493+
public readonly forkYDocPlugin?: ForkYDocPlugin;
490494
/**
491495
* The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
492496
* This method should set when creating the editor as this is application-specific.
@@ -647,7 +651,7 @@ export class BlockNoteEditor<
647651
this.tableHandles = this.extensions["tableHandles"] as any;
648652
this.comments = this.extensions["comments"] as any;
649653
this.showSelectionPlugin = this.extensions["showSelection"] as any;
650-
this.cursorPlugin = this.extensions["yCursorPlugin"] as any;
654+
this.forkYDocPlugin = this.extensions["forkYDocPlugin"] as any;
651655

652656
if (newOptions.uploadFile) {
653657
const uploadFile = newOptions.uploadFile;
@@ -1547,7 +1551,7 @@ export class BlockNoteEditor<
15471551
);
15481552
}
15491553

1550-
this.cursorPlugin.updateUser(user);
1554+
(this.extensions["yCursorPlugin"] as CursorPlugin).updateUser(user);
15511555
}
15521556

15531557
/**

packages/core/src/editor/BlockNoteExtension.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Plugin } from "prosemirror-state";
22
import { EventEmitter } from "../util/EventEmitter.js";
33

4-
export abstract class BlockNoteExtension extends EventEmitter<any> {
4+
export abstract class BlockNoteExtension<
5+
TEvent extends Record<string, any> = any,
6+
> extends EventEmitter<TEvent> {
57
public static name(): string {
68
throw new Error("You must implement the name method in your extension");
79
}

packages/core/src/editor/BlockNoteExtensions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import type {
5656
BlockNoteEditorOptions,
5757
SupportedExtension,
5858
} from "./BlockNoteEditor.js";
59+
import { ForkYDocPlugin } from "../extensions/Collaboration/ForkYDocPlugin.js";
5960

6061
type ExtensionOptions<
6162
BSchema extends BlockSchema,
@@ -120,6 +121,10 @@ export const getBlockNoteExtensions = <
120121
if (opts.collaboration.provider?.awareness) {
121122
ret["yCursorPlugin"] = new CursorPlugin(opts.collaboration);
122123
}
124+
ret["forkYDocPlugin"] = new ForkYDocPlugin({
125+
editor: opts.editor,
126+
collaboration: opts.collaboration,
127+
});
123128
}
124129

125130
// Note: this is pretty hardcoded and will break when user provides plugins with same keys.

packages/core/src/extensions/Collaboration/CursorPlugin.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export type CollaborationUser = {
1010
};
1111

1212
export class CursorPlugin extends BlockNoteExtension {
13+
public static name() {
14+
return "yCursorPlugin";
15+
}
16+
1317
private provider: { awareness: Awareness };
1418
private recentlyUpdatedCursors: Map<
1519
number,

0 commit comments

Comments
 (0)