Skip to content

Commit 79eea41

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/odt-export
2 parents 072ce3b + add526c commit 79eea41

File tree

239 files changed

+25176
-5227
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

239 files changed

+25176
-5227
lines changed

.github/workflows/build.yml

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ jobs:
3030
${{ runner.os }}-build-
3131
${{ runner.os }}-
3232
33-
- name: cache playwright
34-
id: playwright-cache
35-
uses: actions/cache@v4
36-
with:
37-
path: ~/.cache/ms-playwright
38-
key: pw-new-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
39-
4033
- name: Install Dependencies
4134
run: npm install
4235

@@ -45,40 +38,44 @@ jobs:
4538

4639
- name: Lint packages
4740
run: npm run lint
48-
env:
49-
CI: true
5041

5142
- name: Build packages
5243
run: npm run build
53-
env:
54-
CI: true
5544

5645
- name: Run unit tests
5746
run: npm run test
58-
env:
59-
CI: true
6047

61-
- name: Run server
62-
run: npm run start:built & npx wait-on http://localhost:3000
63-
env:
64-
CI: true
48+
- name: Upload webpack stats artifact (editor)
49+
uses: relative-ci/agent-upload-artifact-action@v2
50+
with:
51+
webpackStatsFile: ./playground/dist/webpack-stats.json
52+
artifactName: relative-ci-artifacts-editor
53+
playwright:
54+
name: "Playwright Tests"
55+
runs-on: ubuntu-latest
56+
container:
57+
image: mcr.microsoft.com/playwright:v1.49.1-noble
58+
steps:
59+
- uses: actions/checkout@v4
60+
- uses: actions/setup-node@v4
61+
with:
62+
node-version: lts/*
63+
- run: apt-get update && apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
64+
- name: Install dependencies
65+
run: npm ci
6566

66-
- name: Install Playwright
67-
run: npm run install-playwright
67+
- name: Build packages
68+
run: npm run build
6869

69-
- name: Run Playwright tests
70-
working-directory: ./tests
71-
run: npx playwright test
70+
- name: Run server and Playwright tests
71+
run: |
72+
npm run start:built > /dev/null &
73+
npx wait-on http://localhost:3000
74+
cd tests && HOME=/root npx playwright test
7275
7376
- uses: actions/upload-artifact@v4
7477
if: always()
7578
with:
7679
name: playwright-report
7780
path: tests/playwright-report/
7881
retention-days: 30
79-
80-
- name: Upload webpack stats artifact (editor)
81-
uses: relative-ci/agent-upload-artifact-action@v2
82-
with:
83-
webpackStatsFile: ./playground/dist/webpack-stats.json
84-
artifactName: relative-ci-artifacts-editor

docs/components/example/styles.css

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,38 @@
11
:focus-visible {
2-
box-shadow: unset !important;
2+
box-shadow: unset !important;
33
}
44

55
.demo .nextra-code-block pre {
6-
background-color: transparent !important;
7-
margin: 0 !important;
8-
padding: 0 !important;
6+
background-color: transparent !important;
7+
margin: 0 !important;
8+
padding: 0 !important;
99
}
1010

1111
.demo .nextra-code-block code > span {
12-
padding: 0 !important;
12+
padding: 0 !important;
1313
}
1414

15-
.demo .bn-container,
15+
.demo .bn-container:not(.bn-comment-editor),
1616
.demo .bn-editor {
17-
height: 100%;
17+
height: 100%;
18+
}
19+
20+
.demo .bn-container:not(.bn-comment-editor) .bn-editor {
21+
height: 100%;
1822
}
1923

2024
.demo .bn-editor {
21-
overflow: auto;
22-
padding-block: 1rem;
25+
overflow: auto;
26+
padding-block: 1rem;
2327
}
2428

2529
.demo-contents a {
26-
color: revert;
27-
text-decoration: revert;
30+
color: revert;
31+
text-decoration: revert;
2832
}
2933

3034
.demo code.bn-inline-content {
31-
font-size: 1em;
32-
line-height: 1.5;
33-
display: block;
34-
}
35+
font-size: 1em;
36+
line-height: 1.5;
37+
display: block;
38+
}

docs/next.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ const nextConfig = withAnalyzer(
123123
destination: "/examples/basic/default-blocks",
124124
permanent: true,
125125
},
126+
{
127+
source: "/docs/advanced/real-time-collaboration",
128+
destination: "/docs/collaboration",
129+
permanent: true,
130+
},
126131
],
127132
experimental: {
128133
externalDir: true,

docs/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "docs",
3-
"version": "0.24.1",
3+
"version": "0.24.2",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
@@ -9,12 +9,12 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12-
"@blocknote/ariakit": "^0.24.1",
13-
"@blocknote/core": "^0.24.1",
14-
"@blocknote/mantine": "^0.24.1",
15-
"@blocknote/react": "^0.24.1",
16-
"@blocknote/shadcn": "^0.24.1",
17-
"@blocknote/xl-multi-column": "^0.24.1",
12+
"@blocknote/ariakit": "^0.24.2",
13+
"@blocknote/core": "^0.24.2",
14+
"@blocknote/mantine": "^0.24.2",
15+
"@blocknote/react": "^0.24.2",
16+
"@blocknote/shadcn": "^0.24.2",
17+
"@blocknote/xl-multi-column": "^0.24.2",
1818
"@headlessui/react": "^1.7.18",
1919
"@heroicons/react": "^2.1.4",
2020
"@mantine/core": "^7.10.1",

docs/pages/docs/_meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"styling-theming": "Styling & Theming",
77
"ui-components": "UI Components",
88
"custom-schemas": "Custom Schemas",
9+
"collaboration": "Collaboration",
910
"advanced": "Advanced",
1011
"discord-link": {
1112
"title": "Community ↗",

docs/pages/docs/collaboration.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: Collaboration
3+
description: Learn how to create multiplayer experiences with BlockNote
4+
---
5+
6+
# Collaboration (advanced)
7+
8+
BlockNote supports multi-user collaborative document editing.
9+
10+
- [Real-time collaboration](/docs/collaboration/real-time-collaboration)
11+
- [Comments](/docs/collaboration/comments)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"real-time-collaboration": "Real-time collaboration",
3+
"comments": "Comments"
4+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
title: Comments
3+
description: Learn how to enable comments in your BlockNote editor
4+
imageTitle: Comments
5+
---
6+
7+
import { Example } from "@/components/example";
8+
9+
# Comments
10+
11+
BlockNote supports Comments, Comment Threads (replies) and emoji reactions out of the box.
12+
13+
To enable comments in your editor, you need to:
14+
15+
- provide a `resolveUsers` so BlockNote can retrieve and display user information (names and avatars).
16+
- provide a `ThreadStore` so BlockNote can store and retrieve comment threads.
17+
- enable real-time collaboration (see [Real-time collaboration](/docs/collaboration/real-time-collaboration))
18+
19+
```tsx
20+
const editor = useCreateBlockNote({
21+
resolveUsers: async (userIds: string[]) => {
22+
// return user information for the given userIds (see below)
23+
},
24+
comments: {
25+
threadStore: yourThreadStore, // see below
26+
},
27+
// ...
28+
collaboration: {
29+
// ... // see real-time collaboration docs
30+
},
31+
});
32+
```
33+
34+
**Demo**
35+
36+
<Example name="collaboration/comments" />
37+
38+
## ThreadStores
39+
40+
A ThreadStore is used to store and retrieve comment threads. BlockNote is backend agnostic, so you can use any database or backend to store the threads.
41+
BlockNote comes with several built-in ThreadStore implementations:
42+
43+
### `YjsThreadStore`
44+
45+
The `YjsThreadStore` provides direct Yjs-based storage for comments, storing thread data directly in the Yjs document. This implementation is ideal for simple collaborative setups where all users have write access to the document.
46+
47+
```tsx
48+
import { YjsThreadStore } from "@blocknote/core";
49+
50+
const threadStore = new YjsThreadStore(
51+
userId, // The active user's ID
52+
yDoc.getMap("threads"), // Y.Map to store threads
53+
new DefaultThreadStoreAuth(userId, "editor"), // Authorization information, see below
54+
);
55+
```
56+
57+
_Note: While this is the easiest to implement, it requires users to have write access to the Yjs document to leave comments. Also, without proper server-side validation, any user could technically modify other users' comments._
58+
59+
### `RESTYjsThreadStore`
60+
61+
The `RESTYjsThreadStore` combines Yjs storage with a REST API backend, providing secure comment management while maintaining real-time collaboration. This implementation is ideal when you have strong authentication requirements, but is a little more work to set up.
62+
63+
In this implementation, data is written to the Yjs document via a REST API which can handle access control. Data is still retrieved from the Yjs document directly (after it's been updated by the REST API), this way all comment information automatically syncs between clients using the existing collaboration provider.
64+
65+
```tsx
66+
import { RESTYjsThreadStore, DefaultThreadStoreAuth } from "@blocknote/core";
67+
68+
const threadStore = new RESTYjsThreadStore(
69+
"https://api.example.com/comments", // Base URL for the REST API
70+
{
71+
Authorization: "Bearer your-token", // Optional headers to add to requests
72+
},
73+
yDoc.getMap("threads"), // Y.Map to retrieve commend data from
74+
new DefaultThreadStoreAuth(userId, "editor"), // Authorization rules (see below)
75+
);
76+
```
77+
78+
An example implementation of the REST API can be found in the [example repository](https://github.com/TypeCellOS/BlockNote-demo-nextjs-hocuspocus).
79+
80+
_Note: Because writes are executed via a REST API, the `RESTYjsThreadStore` is not suitable for local-first applications that should be able to add and edit comments offline._
81+
82+
### `TiptapThreadStore`
83+
84+
The `TiptapThreadStore` integrates with Tiptap's collaboration provider for comment management. This implementation is designed specifically for use with Tiptap's collaborative editing features.
85+
86+
```tsx
87+
import { TiptapThreadStore, DefaultThreadStoreAuth } from "@blocknote/core";
88+
import { TiptapCollabProvider } from "@hocuspocus/provider";
89+
90+
// Create a TiptapCollabProvider (you probably have this already)
91+
const provider = new TiptapCollabProvider({
92+
name: "test",
93+
baseUrl: "https://collab.yourdomain.com",
94+
appId: "test",
95+
document: doc,
96+
});
97+
98+
// Create a TiptapThreadStore
99+
const threadStore = new TiptapThreadStore(
100+
userId, // The active user's ID
101+
provider, // Tiptap collaboration provider
102+
new DefaultThreadStoreAuth(userId, "editor"), // Authorization rules (see below)
103+
);
104+
```
105+
106+
### ThreadStoreAuth
107+
108+
The `ThreadStoreAuth` class defines the authorization rules for interacting with comments. Every ThreadStore implementation requires a `ThreadStoreAuth` instance. BlockNote uses the `ThreadStoreAuth` instance to deterine which interactions are allowed for the current user (for example, whether they can create a new comment, edit or delete a comment, etc.).
109+
110+
The `DefaultThreadStoreAuth` class provides a basic implementation of the `ThreadStoreAuth` class. It takes a user ID and a role ("comment" or "editor") and implements the rules. See the [source code](https://github.com/TypeCellOS/BlockNote/blob/main/packages/core/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.ts) for more details.
111+
112+
_Note: The `ThreadStoreAuth` only used to show / hide options in the UI. To secure comment related data, you still need to implement your own server-side validation (e.g. using `RESTYjsThreadStore` and a secure REST API)._
113+
114+
## `resolveUsers` function
115+
116+
When a user interacts with a comment, the data is stored in the ThreadStore, along with the active user ID (as specified when initiating the ThreadStore).
117+
118+
To display comments, BlockNote needs to retrieve user information (such as the username and avatar) based on the user ID. To do this, you need to provide a `resolveUsers` function in the editor options.
119+
120+
This function is called with an array of user IDs, and should return an array of `User` objects in the same order.
121+
122+
```tsx
123+
type User = {
124+
id: string;
125+
username: string;
126+
avatarUrl: string;
127+
};
128+
129+
async function myResolveUsers(userIds: string[]): Promise<User[]> {
130+
// fetch user information from your database / backend
131+
// and return an array of User objects
132+
133+
return await callYourBackend(userIds); //
134+
135+
// Return a list of users
136+
return users;
137+
}
138+
```

0 commit comments

Comments
 (0)