āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/udecode/plate/(plugins)/(collaboration)/comment ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
title: Comment docs:
The fastest way to add comment functionality is with the CommentKit, which includes pre-configured commentPlugin and related components along with their Plate UI components.
CommentLeaf: Renders comment text marksBlockDiscussion: Renders discussion UI with comments integrationimport { createPlateEditor } from 'platejs/react';
import { CommentKit } from '@/components/editor/plugins/comment-kit';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
...CommentKit,
],
});
</Steps>
npm install @platejs/comment
Create the comment plugin with extended configuration for state management:
import { type ExtendConfig, type Path, isSlateString } from 'platejs';
import {
type BaseCommentConfig,
BaseCommentPlugin,
getDraftCommentKey,
} from '@platejs/comment';
import { toTPlatePlugin } from 'platejs/react';
import { CommentLeaf } from '@/components/ui/comment-node';
type CommentConfig = ExtendConfig<
BaseCommentConfig,
{
activeId: string | null;
commentingBlock: Path | null;
hoverId: string | null;
uniquePathMap: Map<string, Path>;
}
>;
export const commentPlugin = toTPlatePlugin<CommentConfig>(
BaseCommentPlugin,
({ editor }) => ({
options: {
activeId: null,
commentingBlock: null,
hoverId: null,
uniquePathMap: new Map(),
},
render: {
node: CommentLeaf,
},
})
);
options.activeId: Currently active comment ID for visual highlightingoptions.commentingBlock: Path of the block currently being commentedoptions.hoverId: Currently hovered comment ID for hover effectsoptions.uniquePathMap: Map tracking unique paths for comment resolutionrender.node: Assigns CommentLeaf to render comment text marksAdd click handling to manage active comment state:
export const commentPlugin = toTPlatePlugin<CommentConfig>(
BaseCommentPlugin,
({ editor }) => ({
handlers: {
// Set active comment when clicking on comment marks
onClick: ({ api, event, setOption, type }) => {
let leaf = event.target as HTMLElement;
let isSet = false;
const unsetActiveComment = () => {
setOption('activeId', null);
isSet = true;
};
if (!isSlateString(leaf)) unsetActiveComment();
while (leaf.parentElement) {
if (leaf.classList.contains(`slate-${type}`)) {
const commentsEntry = api.comment.node();
if (!commentsEntry) {
unsetActiveComment();
break;
}
const id = api.comment.nodeId(commentsEntry[0]);
setOption('activeId', id ?? null);
isSet = true;
break;
}
leaf = leaf.parentElement;
}
if (!isSet) unsetActiveComment();
},
},
// ... previous options and render
})
);
The click handler tracks which comment is currently active:
activeId when clicking on commentsactiveId when clicking outside commentsExtend the setDraft transform for enhanced functionality:
export const commentPlugin = toTPlatePlugin<CommentConfig>(
BaseCommentPlugin,
({ editor }) => ({
// ... previous configuration
})
)
.extendTransforms(
({
editor,
setOption,
tf: {
comment: { setDraft },
},
}) => ({
setDraft: () => {
if (editor.api.isCollapsed()) {
editor.tf.select(editor.api.block()![1]);
}
setDraft();
editor.tf.collapse();
setOption('activeId', getDraftCommentKey());
setOption('commentingBlock', editor.selection!.focus.path.slice(0, 1));
},
})
)
.configure({
node: { component: CommentLeaf },
shortcuts: {
setDraft: { keys: 'mod+shift+m' },
},
});
You can add CommentToolbarButton to your Toolbar to add comments on selected text.
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
commentPlugin,
],
});
The comment plugin works with the discussion plugin for complete collaboration:
import { discussionPlugin } from '@/components/editor/plugins/discussion-kit';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
discussionPlugin,
commentPlugin,
],
});
</Steps>
CommentPluginPlugin for creating and managing text comments with state tracking and discussion integration.
<API name="CommentPlugin"> <APIOptions> <APIItem name="activeId" type="string | null"> Currently active comment ID for visual highlighting. Used internally to track state. </APIItem> <APIItem name="commentingBlock" type="Path | null"> Path of the block currently being commented on. </APIItem> <APIItem name="hoverId" type="string | null"> Currently hovered comment ID for hover effects. </APIItem> <APIItem name="uniquePathMap" type="Map<string, Path>"> Map tracking unique paths for comment resolution. </APIItem> </APIOptions> </API>api.comment.hasChecks if a comment with the given ID exists in the editor.
<API name="has"> <APIParameters> <APIItem name="options" type="{ id: string }"> Options containing the comment ID to check. </APIItem> </APIParameters> <APIReturns type="boolean">Whether the comment exists.</APIReturns> </API>api.comment.nodeGets a comment node entry.
<API name="node"> <APIOptions type="EditorNodesOptions & { id?: string; isDraft?: boolean }" optional > Options for finding the node. </APIOptions> <APIReturns type="NodeEntry<TCommentText> | undefined"> The comment node entry if found. </APIReturns> </API>api.comment.nodeIdGets the ID of a comment from a leaf node.
<API name="nodeId"> <APIParameters> <APIItem name="leaf" type="TCommentText"> The comment leaf node. </APIItem> </APIParameters> <APIReturns type="string | undefined">The comment ID if found.</APIReturns> </API>api.comment.nodesGets all comment node entries matching the options.
<API name="nodes"> <APIOptions type="EditorNodesOptions & { id?: string; isDraft?: boolean }" optional > Options for finding the nodes. </APIOptions> <APIReturns type="NodeEntry<TCommentText>[]"> Array of comment node entries. </APIReturns> </API>tf.comment.removeMarkRemoves the comment mark from the current selection or a specified location.
<API name="removeMark" />tf.comment.setDraftSets a draft comment mark at the current selection.
<API name="setDraft"> <APIOptions type="SetNodesOptions" optional> Options for setting the draft comment. </APIOptions> </API>tf.comment.unsetMarkUnsets comment nodes with the specified ID from the editor.
<API name="unsetMark"> <APIParameters> <APIItem name="options" type="{ id: string; transient?: boolean }"> Options for unsetting comment marks. </APIItem> </APIParameters> <APIOptions type="object"> <APIItem name="id" type="string"> The comment ID to unset. </APIItem> <APIItem name="transient" type="boolean" optional> When true, removes all AI comments at once. - **Default:** `false` </APIItem> </APIOptions> </API>getCommentCountGets the count of non-draft comments in a comment node.
<API name="getCommentCount"> <APIParameters> <APIItem name="node" type="TCommentText"> The comment node. </APIItem> </APIParameters> <APIReturns type="number">The count of comments.</APIReturns> </API>getCommentKeyGenerates a comment key based on the provided ID.
<API name="getCommentKey"> <APIParameters> <APIItem name="id" type="string"> The ID of the comment. </APIItem> </APIParameters> <APIReturns type="string">The generated comment key.</APIReturns> </API>getCommentKeyIdExtracts the comment ID from a comment key.
<API name="getCommentKeyId"> <APIParameters> <APIItem name="key" type="string"> The comment key. </APIItem> </APIParameters> <APIReturns type="string">The extracted comment ID.</APIReturns> </API>getCommentKeysReturns an array of comment keys present in the given node.
<API name="getCommentKeys"> <APIParameters> <APIItem name="node" type="TCommentText"> The node to check for comment keys. </APIItem> </APIParameters> <APIReturns type="string[]">Array of comment keys.</APIReturns> </API>getDraftCommentKeyGets the key used for draft comments.
<API name="getDraftCommentKey"> <APIReturns type="string">The draft comment key.</APIReturns> </API>isCommentKeyChecks if a given key is a comment key.
<API name="isCommentKey"> <APIParameters> <APIItem name="key" type="string"> The key to check. </APIItem> </APIParameters> <APIReturns type="boolean">Whether the key is a comment key.</APIReturns> </API>isCommentNodeByIdChecks if a given node is a comment with the specified ID.
<API name="isCommentNodeById"> <APIParameters> <APIItem name="node" type="TNode"> The node to check. </APIItem> <APIItem name="id" type="string"> The ID of the comment. </APIItem> </APIParameters> <APIReturns type="boolean"> Whether the node is a comment with the specified ID. </APIReturns> </API>TCommentTextText nodes that can contain comments.
<API name="TCommentText"> <APIAttributes> <APIItem name="comment" type="boolean" optional> Whether this text node contains comments. </APIItem> <APIItem name="comment_<id>" type="boolean" optional> Comment data keyed by comment ID. Multiple comments can exist in one text node. </APIItem> </APIAttributes> </API>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā