Loading...
Files
components/demo.tsx
'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>
  );
}

Features

  • User Management: Store and manage user data with avatars and names
  • Discussion Threads: Manage discussion data structures with comments
  • Current User Tracking: Track the current active user for collaboration
  • Data Storage: Pure UI plugin for storing collaboration state
  • Selector API: Easy access to user data through plugin selectors

Kit Usage

Installation

The fastest way to add discussion functionality is with the DiscussionKit, which includes the pre-configured discussionPlugin along with its Plate UI components.

'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: 'This is a comment',
              },
            ],
            type: 'p',
          },
        ],
        createdAt: new Date(Date.now() - 900_000),
        discussionId: 'discussion1',
        isEdited: false,
        userId: 'alice',
      },
    ],
    createdAt: new Date(),
    documentContent: 'comments to your content',
    isResolved: false,
    userId: 'alice',
  },
  {
    id: 'discussion2',
    comments: [
      {
        id: 'comment1',
        contentRich: [
          {
            children: [
              {
                text: 'Hey, what do you think about this approach?',
              },
            ],
            type: 'p',
          },
        ],
        createdAt: new Date(Date.now() - 900_000),
        discussionId: 'discussion1',
        isEdited: false,
        userId: 'alice',
      },
      {
        id: 'comment2',
        contentRich: [
          {
            children: [
              {
                text: 'Looks good!',
              },
            ],
            type: 'p',
          },
        ],
        createdAt: new Date(Date.now() - 800_000),
        discussionId: 'discussion1',
        isEdited: false,
        userId: 'bob',
      },
      {
        id: 'comment3',
        contentRich: [
          {
            children: [
              {
                text: 'Thanks for the feedback!',
              },
            ],
            type: 'p',
          },
        ],
        createdAt: new Date(Date.now() - 700_000),
        discussionId: 'discussion1',
        isEdited: false,
        userId: 'alice',
      },
    ],
    createdAt: new Date(),
    documentContent: 'collaborate',
    isResolved: false,
    userId: 'bob',
  },
  {
    id: 'discussion4',
    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: 'discussion4',
        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: 'discussion4',
        isEdited: false,
        userId: 'bob',
      },
    ],
    createdAt: new Date(),
    documentContent: 'comments',
    isResolved: false,
    userId: 'charlie',
  },
  {
    id: 'discussion5',
    comments: [
      {
        id: 'comment1',
        contentRich: [
          {
            children: [
              {
                text: 'This is a good example of how to use comments.',
              },
            ],
            type: 'p',
          },
        ],
        createdAt: new Date(Date.now() - 400_000),
        discussionId: 'discussion5',
        isEdited: false,
        userId: 'alice',
      },
    ],
    createdAt: new Date(),
    documentContent: 'comments on many text segments',
    isResolved: false,
    userId: 'alice',
  },
  {
    id: 'discussion6',
    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: 'discussion6',
        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: 'discussion6',
        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];

Add Kit

import { createPlateEditor } from 'platejs/react';
import { DiscussionKit } from '@/components/editor/plugins/discussion-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ...DiscussionKit,
  ],
});

Manual Usage

Installation

pnpm add @platejs/comment @platejs/suggestion

Create Plugin

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: ID of the current active user
  • options.discussions: Array of discussion data structures
  • options.users: Object mapping user IDs to user data
  • render.aboveNodes: Renders BlockDiscussion above nodes for discussion UI
  • selectors.currentUser: Gets the current user data
  • selectors.user: Gets user data by ID

Add Plugin

import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    discussionPlugin,
  ],
});

Plugins

discussionPlugin

Pure UI plugin for managing collaboration state including users and discussion data.

Options

Collapse all

    ID of the current active user in the collaboration session.

    Array of discussion objects containing comments and metadata.

    Object mapping user IDs to user information including name and avatar.

Selectors

currentUser

Gets the current user data.

ReturnsUserData

    The current user's data including id, name, and avatarUrl.

user

Gets user data by ID.

Parameters

Collapse all

    The user ID to look up.

ReturnsUserData | undefined

    The user data if found, undefined otherwise.

Types

TDiscussion

Discussion data structure containing comments and metadata.

Attributes

Collapse all

    Unique identifier for the discussion.

    Array of comments in the discussion thread.

    When the discussion was created.

    Whether the discussion has been resolved.

    ID of the user who created the discussion.

    Content from the document related to this discussion.

UserData

User information structure for collaboration.

Attributes

Collapse all

    Unique identifier for the user.

    Display name of the user.

    URL for the user's avatar image.

    Optional color hue for user identification.