āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/udecode/plate/(plugins)/(collaboration)/suggestion ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
title: Suggestion docs:
The fastest way to add suggestion functionality is with the SuggestionKit, which includes pre-configured SuggestionPlugin and related components along with their Plate UI components.
SuggestionLeaf: Renders suggestion text marksBlockSuggestion: Renders block-level suggestionsSuggestionLineBreak: Handles line breaks in suggestionsimport { createPlateEditor } from 'platejs/react';
import { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
...SuggestionKit,
],
});
</Steps>
npm install @platejs/suggestion
Create the suggestion plugin with extended configuration for state management:
import {
type ExtendConfig,
type Path,
isSlateEditor,
isSlateElement,
isSlateString,
} from 'platejs';
import {
type BaseSuggestionConfig,
BaseSuggestionPlugin,
} from '@platejs/suggestion';
import { createPlatePlugin, toTPlatePlugin } from 'platejs/react';
import { BlockSuggestion } from '@/components/ui/block-suggestion';
import { SuggestionLeaf } from '@/components/ui/suggestion-node';
export type SuggestionConfig = ExtendConfig<
BaseSuggestionConfig,
{
activeId: string | null;
hoverId: string | null;
uniquePathMap: Map<string, Path>;
}
>;
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
options: {
activeId: null,
currentUserId: 'alice', // Set your current user ID
hoverId: null,
uniquePathMap: new Map(),
},
render: {
node: SuggestionLeaf,
belowRootNodes: ({ api, element }) => {
if (!api.suggestion!.isBlockSuggestion(element)) {
return null;
}
return <BlockSuggestion element={element} />;
},
},
})
);
options.activeId: Currently active suggestion ID for visual highlightingoptions.currentUserId: ID of the current user creating suggestionsoptions.hoverId: Currently hovered suggestion ID for hover effectsoptions.uniquePathMap: Map tracking unique paths for suggestion resolutionrender.node: Assigns SuggestionLeaf to render suggestion text marksrender.belowRootNodes: Renders BlockSuggestion for block-level suggestionsAdd click handling to manage active suggestion state:
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
handlers: {
// Unset active suggestion when clicking outside of suggestion
onClick: ({ api, event, setOption, type }) => {
let leaf = event.target as HTMLElement;
let isSet = false;
const unsetActiveSuggestion = () => {
setOption('activeId', null);
isSet = true;
};
if (!isSlateString(leaf)) unsetActiveSuggestion();
while (
leaf.parentElement &&
!isSlateElement(leaf.parentElement) &&
!isSlateEditor(leaf.parentElement)
) {
if (leaf.classList.contains(`slate-${type}`)) {
const suggestionEntry = api.suggestion!.node({ isText: true });
if (!suggestionEntry) {
unsetActiveSuggestion();
break;
}
const id = api.suggestion!.nodeId(suggestionEntry[0]);
setOption('activeId', id ?? null);
isSet = true;
break;
}
leaf = leaf.parentElement;
}
if (!isSet) unsetActiveSuggestion();
},
},
// ... previous options and render
})
);
The click handler tracks which suggestion is currently active:
activeId when clicking on suggestionsactiveId when clicking outside suggestionsimport { createPlateEditor, createPlatePlugin } from 'platejs/react';
import { SuggestionLineBreak } from '@/components/ui/suggestion-node';
const suggestionLineBreakPlugin = createPlatePlugin({
key: 'suggestionLineBreak',
render: { belowNodes: SuggestionLineBreak as any },
});
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
suggestionPlugin,
suggestionLineBreakPlugin,
],
});
render.belowNodes: Renders SuggestionLineBreak below nodes to handle line break suggestionsUse the plugin's API to control suggestion mode:
import { useEditorRef, usePluginOption } from 'platejs/react';
function SuggestionToolbar() {
const editor = useEditorRef();
const isSuggesting = usePluginOption(suggestionPlugin, 'isSuggesting');
const toggleSuggesting = () => {
editor.setOption(suggestionPlugin, 'isSuggesting', !isSuggesting);
};
return (
<button onClick={toggleSuggesting}>
{isSuggesting ? 'Stop Suggesting' : 'Start Suggesting'}
</button>
);
}
You can add SuggestionToolbarButton to your Toolbar to toggle suggestion mode in the editor.
The suggestion plugin works with the discussion plugin for complete collaboration:
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
discussionPlugin,
suggestionPlugin.configure({
options: {
currentUserId: 'alice',
},
}),
suggestionLineBreakPlugin,
],
});
</Steps>
SuggestionPluginPlugin for creating and managing text and block suggestions with state tracking and discussion integration.
<API name="SuggestionPlugin"> <APIOptions> <APIItem name="currentUserId" type="string | null"> ID of the current user creating suggestions. Required for proper suggestion attribution. </APIItem> <APIItem name="isSuggesting" type="boolean"> Whether the editor is currently in suggestion mode. Used internally to track state. </APIItem> </APIOptions> </API>api.suggestion.dataListGets suggestion data from a text node.
<API name="dataList"> <APIParameters> <APIItem name="node" type="TSuggestionText"> The suggestion text node. </APIItem> </APIParameters> <APIReturns type="TInlineSuggestionData[]"> Array of suggestion data. </APIReturns> </API>api.suggestion.isBlockSuggestionChecks if a node is a block suggestion element.
<API name="isBlockSuggestion"> <APIParameters> <APIItem name="node" type="TElement"> The node to check. </APIItem> </APIParameters> <APIReturns type="node is TSuggestionElement"> Whether the node is a block suggestion. </APIReturns> </API>api.suggestion.nodeGets a suggestion node entry.
<API name="node"> <APIOptions type="EditorNodesOptions & { id?: string; isText?: boolean }" optional> Options for finding the node. </APIOptions> <APIReturns type="NodeEntry<TSuggestionElement | TSuggestionText> | undefined"> The suggestion node entry if found. </APIReturns> </API>api.suggestion.nodeIdGets the ID of a suggestion from a node.
<API name="nodeId"> <APIParameters> <APIItem name="node" type="TElement | TSuggestionText"> The node to get ID from. </APIItem> </APIParameters> <APIReturns type="string | undefined"> The suggestion ID if found. </APIReturns> </API>api.suggestion.nodesGets all suggestion node entries matching the options.
<API name="nodes"> <APIOptions type="EditorNodesOptions" optional> Options for finding the nodes. </APIOptions> <APIReturns type="NodeEntry<TElement | TSuggestionText>[]"> Array of suggestion node entries. </APIReturns> </API>api.suggestion.suggestionDataGets suggestion data from a node.
<API name="suggestionData"> <APIParameters> <APIItem name="node" type="TElement | TSuggestionText"> The node to get suggestion data from. </APIItem> </APIParameters> <APIReturns type="TInlineSuggestionData | TSuggestionElement['suggestion'] | undefined"> The suggestion data if found. </APIReturns> </API>api.suggestion.withoutSuggestionsTemporarily disables suggestions while executing a function.
<API name="withoutSuggestions"> <APIParameters> <APIItem name="fn" type="() => void"> The function to execute. </APIItem> </APIParameters> </API>TSuggestionTextText nodes that can contain suggestions.
<API name="TSuggestionText"> <APIAttributes> <APIItem name="suggestion" type="boolean" optional> Whether this is a suggestion. </APIItem> <APIItem name="suggestion_<id>" type="TInlineSuggestionData" optional> Suggestion data. Multiple suggestions can exist in one text node. </APIItem> </APIAttributes> </API>TSuggestionElementBlock elements that contain suggestion metadata.
<API name="TSuggestionElement"> <APIAttributes> <APIItem name="suggestion" type="TSuggestionData"> Block-level suggestion data including type, user, and timing information. </APIItem> </APIAttributes> </API>TInlineSuggestionDataData structure for inline text suggestions.
<API name="TInlineSuggestionData"> <APIAttributes> <APIItem name="id" type="string"> Unique identifier for the suggestion. </APIItem> <APIItem name="userId" type="string"> ID of the user who created the suggestion. </APIItem> <APIItem name="createdAt" type="number"> Timestamp when the suggestion was created. </APIItem> <APIItem name="type" type="'insert' | 'remove' | 'update'"> Type of suggestion operation. </APIItem> <APIItem name="newProperties" type="object" optional> For update suggestions, the new mark properties being suggested. </APIItem> <APIItem name="properties" type="object" optional> For update suggestions, the previous mark properties. </APIItem> </APIAttributes> </API>TSuggestionDataData structure for block-level suggestions.
<API name="TSuggestionData"> <APIAttributes> <APIItem name="id" type="string"> Unique identifier for the suggestion. </APIItem> <APIItem name="userId" type="string"> ID of the user who created the suggestion. </APIItem> <APIItem name="createdAt" type="number"> Timestamp when the suggestion was created. </APIItem> <APIItem name="type" type="'insert' | 'remove'"> Type of block suggestion operation. </APIItem> <APIItem name="isLineBreak" type="boolean" optional> Whether this suggestion represents a line break insertion. </APIItem> </APIAttributes> </API>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā