Discussions
Review and refine content seamlessly. Use suggestions like this added text or to mark text for removal. Discuss changes using comments on many text segments. You can even have overlapping annotations!
1
100%
Files
'use client';
import * as React from 'react';
import { Plate, usePlateEditor } from 'platejs/react';
import { EditorKit } from '@/components/editor/editor-kit';
import { Editor, EditorContainer } from '@/components/ui/editor';
import { DEMO_VALUES } from './values/demo-values';
export default function Demo({ id }: { id: string }) {
const editor = usePlateEditor({
plugins: EditorKit,
value: DEMO_VALUES[id],
});
return (
<Plate editor={editor}>
<EditorContainer variant="demo">
<Editor />
</EditorContainer>
</Plate>
);
}
Adding and displaying comments within content.
discussion-demo


功能特点
- 用户管理: 存储和管理带有头像和名称的用户数据
- 讨论线程: 管理带有评论的讨论数据结构
- 当前用户跟踪: 跟踪当前活跃用户以进行协作
- 数据存储: 用于存储协作状态的纯 UI 插件
- 选择器 API: 通过插件选择器轻松访问用户数据
Kit 使用
安装
添加讨论功能最快的方法是使用 DiscussionKit,它包含预配置的 discussionPlugin 及其 Plate UI 组件。
'use client';
import type { TComment } from '@/components/ui/comment';
import { createPlatePlugin } from 'platejs/react';
import { BlockDiscussion } from '@/components/ui/block-discussion';
export interface TDiscussion {
id: string;
comments: TComment[];
createdAt: Date;
isResolved: boolean;
userId: string;
documentContent?: string;
}
const discussionsData: TDiscussion[] = [
{
id: 'discussion1',
comments: [
{
id: 'comment1',
contentRich: [
{
children: [
{
text: 'Comments are a great way to provide feedback and discuss changes.',
},
],
type: 'p',
},
],
createdAt: new Date(Date.now() - 600_000),
discussionId: 'discussion1',
isEdited: false,
userId: 'charlie',
},
{
id: 'comment2',
contentRich: [
{
children: [
{
text: 'Agreed! The link to the docs makes it easy to learn more.',
},
],
type: 'p',
},
],
createdAt: new Date(Date.now() - 500_000),
discussionId: 'discussion1',
isEdited: false,
userId: 'bob',
},
],
createdAt: new Date(),
documentContent: 'comments',
isResolved: false,
userId: 'charlie',
},
{
id: 'discussion2',
comments: [
{
id: 'comment1',
contentRich: [
{
children: [
{
text: 'Nice demonstration of overlapping annotations with both comments and suggestions!',
},
],
type: 'p',
},
],
createdAt: new Date(Date.now() - 300_000),
discussionId: 'discussion2',
isEdited: false,
userId: 'bob',
},
{
id: 'comment2',
contentRich: [
{
children: [
{
text: 'This helps users understand how powerful the editor can be.',
},
],
type: 'p',
},
],
createdAt: new Date(Date.now() - 200_000),
discussionId: 'discussion2',
isEdited: false,
userId: 'charlie',
},
],
createdAt: new Date(),
documentContent: 'overlapping',
isResolved: false,
userId: 'bob',
},
];
const avatarUrl = (seed: string) =>
`https://api.dicebear.com/9.x/glass/svg?seed=${seed}`;
const usersData: Record<
string,
{ id: string; avatarUrl: string; name: string; hue?: number }
> = {
alice: {
id: 'alice',
avatarUrl: avatarUrl('alice6'),
name: 'Alice',
},
bob: {
id: 'bob',
avatarUrl: avatarUrl('bob4'),
name: 'Bob',
},
charlie: {
id: 'charlie',
avatarUrl: avatarUrl('charlie2'),
name: 'Charlie',
},
};
// This plugin is purely UI. It's only used to store the discussions and users data
export const discussionPlugin = createPlatePlugin({
key: 'discussion',
options: {
currentUserId: 'alice',
discussions: discussionsData,
users: usersData,
},
})
.configure({
render: { aboveNodes: BlockDiscussion },
})
.extendSelectors(({ getOption }) => ({
currentUser: () => getOption('users')[getOption('currentUserId')],
user: (id: string) => getOption('users')[id],
}));
export const DiscussionKit = [discussionPlugin];
BlockDiscussion: 在节点上方渲染讨论 UI
添加 Kit
import { createPlateEditor } from 'platejs/react';
import { DiscussionKit } from '@/components/editor/plugins/discussion-kit';
const editor = createPlateEditor({
plugins: [
// ...其他插件,
...DiscussionKit,
],
});手动使用
安装
pnpm add @platejs/comment @platejs/suggestion
创建插件
import { createPlatePlugin } from 'platejs/react';
import { BlockDiscussion } from '@/components/ui/block-discussion';
export interface TDiscussion {
id: string;
comments: TComment[];
createdAt: Date;
isResolved: boolean;
userId: string;
documentContent?: string;
}
const usersData = {
alice: {
id: 'alice',
avatarUrl: 'https://api.dicebear.com/9.x/glass/svg?seed=alice6',
name: 'Alice',
},
bob: {
id: 'bob',
avatarUrl: 'https://api.dicebear.com/9.x/glass/svg?seed=bob4',
name: 'Bob',
},
};
export const discussionPlugin = createPlatePlugin({
key: 'discussion',
options: {
currentUserId: 'alice',
discussions: [],
users: usersData,
},
})
.configure({
render: { aboveNodes: BlockDiscussion },
})
.extendSelectors(({ getOption }) => ({
currentUser: () => getOption('users')[getOption('currentUserId')],
user: (id: string) => getOption('users')[id],
}));options.currentUserId: 当前活跃用户的 IDoptions.discussions: 讨论数据结构数组options.users: 将用户 ID 映射到用户数据的对象render.aboveNodes: 在节点上方渲染BlockDiscussion用于讨论 UIselectors.currentUser: 获取当前用户数据selectors.user: 通过 ID 获取用户数据
添加插件
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件,
discussionPlugin,
],
});插件
discussionPlugin
用于管理包括用户和讨论数据在内的协作状态的纯 UI 插件。
选择器
currentUser
获取当前用户数据。
user
通过 ID 获取用户数据。
类型
TDiscussion
包含评论和元数据的讨论数据结构。
UserData
用于协作的用户信息结构。