-
-
Notifications
You must be signed in to change notification settings - Fork 503
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: Comments #1376
feat: Comments #1376
Changes from all commits
4d6e252
dcd7c72
c659253
a03d33f
85521df
e43741a
e0c7f0f
d747238
6ce69ea
a3028c1
15c7520
b8a8d49
1b44296
23275a0
721a4e9
e824c42
f2d4bb8
434eafa
9d35f72
58ed7c0
b761e1e
5f52147
060708d
5c6f45b
16c6a7a
a5e07c0
b42047f
43a1eb0
306c335
99d9d21
4501af4
eadb49d
b81bab0
9f40651
7ae2b7f
748a183
e93b53b
be9dd46
3e93d1e
40269a5
62a8944
22fdde6
c293fdf
c0d1bd5
311881c
a71fc69
4d498de
c5a6a0c
3f7828d
d2c9f34
6df6886
5b7e3d4
3ef17e5
db7d339
6067a2b
298ba46
c007f84
99c599c
c0c848a
9576cfe
44b2dca
6c6b017
97f1f0a
5decc46
4cb7af0
27a2523
b27db16
65dfc20
2fe1052
e3291d0
165a014
807d8d1
e3e5867
6bdbf15
d0b3f39
cfd94eb
ac6a17c
f200646
7c0746e
4d293ef
f35a66a
8d9d542
0f1e019
490291f
11d9ee6
7d4b64e
8ec3e50
fa1e5a7
f2f29e7
e97985e
a4a90b8
df1d6a0
7b128dc
9ae74bd
953aefa
46101a0
ab00b5d
38a670d
8d8ebec
6744b26
a7a5656
ebfb4da
9944ca7
30003d7
f1557b1
a647ec3
065c74d
f26edd3
ae534b4
9a12826
828d13e
46836e5
641a6b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,38 @@ | ||
:focus-visible { | ||
box-shadow: unset !important; | ||
box-shadow: unset !important; | ||
} | ||
|
||
.demo .nextra-code-block pre { | ||
background-color: transparent !important; | ||
margin: 0 !important; | ||
padding: 0 !important; | ||
background-color: transparent !important; | ||
margin: 0 !important; | ||
padding: 0 !important; | ||
} | ||
|
||
.demo .nextra-code-block code > span { | ||
padding: 0 !important; | ||
padding: 0 !important; | ||
} | ||
|
||
.demo .bn-container, | ||
.demo .bn-container:not(.bn-comment-editor), | ||
.demo .bn-editor { | ||
height: 100%; | ||
height: 100%; | ||
} | ||
|
||
.demo .bn-container:not(.bn-comment-editor) .bn-editor { | ||
height: 100%; | ||
} | ||
|
||
.demo .bn-editor { | ||
overflow: auto; | ||
padding-block: 1rem; | ||
overflow: auto; | ||
padding-block: 1rem; | ||
} | ||
|
||
.demo-contents a { | ||
color: revert; | ||
text-decoration: revert; | ||
color: revert; | ||
text-decoration: revert; | ||
} | ||
|
||
.demo code.bn-inline-content { | ||
font-size: 1em; | ||
line-height: 1.5; | ||
display: block; | ||
} | ||
font-size: 1em; | ||
line-height: 1.5; | ||
display: block; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
title: Collaboration | ||
description: Learn how to create multiplayer experiences with BlockNote | ||
--- | ||
|
||
# Collaboration (advanced) | ||
|
||
BlockNote supports multi-user collaborative document editing. | ||
|
||
- [Real-time collaboration](/docs/collaboration/real-time-collaboration) | ||
- [Comments](/docs/collaboration/comments) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"real-time-collaboration": "Real-time collaboration", | ||
"comments": "Comments" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
--- | ||
title: Comments | ||
description: Learn how to enable comments in your BlockNote editor | ||
imageTitle: Comments | ||
--- | ||
|
||
import { Example } from "@/components/example"; | ||
|
||
# Comments | ||
|
||
BlockNote supports Comments, Comment Threads (replies) and emoji reactions out of the box. | ||
|
||
To enable comments in your editor, you need to: | ||
|
||
- provide a `resolveUsers` so BlockNote can retrieve and display user information (names and avatars). | ||
- provide a `ThreadStore` so BlockNote can store and retrieve comment threads. | ||
- enable real-time collaboration (see [Real-time collaboration](/docs/collaboration/real-time-collaboration)) | ||
|
||
```tsx | ||
const editor = useCreateBlockNote({ | ||
resolveUsers: async (userIds: string[]) => { | ||
// return user information for the given userIds (see below) | ||
}, | ||
comments: { | ||
threadStore: yourThreadStore, // see below | ||
}, | ||
// ... | ||
collaboration: { | ||
// ... // see real-time collaboration docs | ||
}, | ||
}); | ||
``` | ||
|
||
**Demo** | ||
|
||
<Example name="collaboration/comments" /> | ||
|
||
## ThreadStores | ||
|
||
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. | ||
BlockNote comes with several built-in ThreadStore implementations: | ||
|
||
### `YjsThreadStore` | ||
|
||
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. | ||
|
||
```tsx | ||
import { YjsThreadStore } from "@blocknote/core"; | ||
|
||
const threadStore = new YjsThreadStore( | ||
userId, // The active user's ID | ||
yDoc.getMap("threads"), // Y.Map to store threads | ||
new DefaultThreadStoreAuth(userId, "editor"), // Authorization information, see below | ||
); | ||
``` | ||
|
||
_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._ | ||
|
||
### `RESTYjsThreadStore` | ||
|
||
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. | ||
|
||
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. | ||
|
||
```tsx | ||
import { RESTYjsThreadStore, DefaultThreadStoreAuth } from "@blocknote/core"; | ||
|
||
const threadStore = new RESTYjsThreadStore( | ||
"https://api.example.com/comments", // Base URL for the REST API | ||
{ | ||
Authorization: "Bearer your-token", // Optional headers to add to requests | ||
}, | ||
yDoc.getMap("threads"), // Y.Map to retrieve commend data from | ||
new DefaultThreadStoreAuth(userId, "editor"), // Authorization rules (see below) | ||
); | ||
``` | ||
|
||
An example implementation of the REST API can be found in the [example repository](https://github.com/TypeCellOS/BlockNote-demo-nextjs-hocuspocus). | ||
|
||
_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._ | ||
|
||
### `TiptapThreadStore` | ||
|
||
The `TiptapThreadStore` integrates with Tiptap's collaboration provider for comment management. This implementation is designed specifically for use with Tiptap's collaborative editing features. | ||
|
||
```tsx | ||
import { TiptapThreadStore, DefaultThreadStoreAuth } from "@blocknote/core"; | ||
import { TiptapCollabProvider } from "@hocuspocus/provider"; | ||
|
||
// Create a TiptapCollabProvider (you probably have this already) | ||
const provider = new TiptapCollabProvider({ | ||
name: "test", | ||
baseUrl: "https://collab.yourdomain.com", | ||
appId: "test", | ||
document: doc, | ||
}); | ||
|
||
// Create a TiptapThreadStore | ||
const threadStore = new TiptapThreadStore( | ||
userId, // The active user's ID | ||
provider, // Tiptap collaboration provider | ||
new DefaultThreadStoreAuth(userId, "editor"), // Authorization rules (see below) | ||
); | ||
``` | ||
|
||
### ThreadStoreAuth | ||
|
||
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.). | ||
|
||
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. | ||
|
||
_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)._ | ||
|
||
## `resolveUsers` function | ||
|
||
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). | ||
|
||
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. | ||
|
||
This function is called with an array of user IDs, and should return an array of `User` objects in the same order. | ||
|
||
```tsx | ||
type User = { | ||
id: string; | ||
username: string; | ||
avatarUrl: string; | ||
}; | ||
|
||
async function myResolveUsers(userIds: string[]): Promise<User[]> { | ||
// fetch user information from your database / backend | ||
// and return an array of User objects | ||
|
||
return await callYourBackend(userIds); // | ||
|
||
// Return a list of users | ||
return users; | ||
} | ||
``` | ||
Comment on lines
+114
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this should be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. Might be a good idea, agree it's a better API with a Map, but also a bit more complex to explain maybe. I'll decide when finalizing unless you feel very strong about it :) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"playground": true, | ||
"docs": true, | ||
"author": "yousefed", | ||
"tags": ["Advanced", "Comments", "Collaboration"], | ||
"dependencies": { | ||
"@y-sweet/react": "^0.6.3", | ||
"@mantine/core": "^7.10.1" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to determine