âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â ð shadcn/directory/udecode/plate/(plugins)/(collaboration)/suggestion.cn â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â
title: 建议åèœ docs:
æå¿«æ·çæ·»å 建议åèœæ¹åŒæ¯äœ¿çš SuggestionKitïŒå®å
å«é¢é
眮ç SuggestionPlugin åçžå
³ç»ä»¶ïŒä»¥åå®ä»¬ç Plate UI ç»ä»¶ã
SuggestionLeafïŒæž²æå»ºè®®ææ¬æ è®°BlockSuggestionïŒæž²æåºå级建议SuggestionLineBreakïŒå€ç建议äžçæ¢è¡ç¬Šimport { createPlateEditor } from 'platejs/react';
import { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';
const editor = createPlateEditor({
plugins: [
// ...å
¶ä»æä»¶,
...SuggestionKit,
],
});
</Steps>
npm install @platejs/suggestion
å建垊æç¶æç®¡çæ©å±é 眮ç建议æä»¶ïŒ
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', // 讟眮åœåçšæ·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ïŒåœå掻è·å»ºè®®IDïŒçšäºè§è§é«äº®options.currentUserIdïŒå建建议çåœåçšæ·IDoptions.hoverIdïŒåœåæ¬å建议IDïŒçšäºæ¬åææoptions.uniquePathMapïŒè¿œèžªå»ºè®®è§£æå¯äžè·¯åŸçæ å°è¡šrender.nodeïŒæå® SuggestionLeaf æž²æå»ºè®®ææ¬æ è®°render.belowRootNodesïŒäžºåºå级建议枲æ BlockSuggestionæ·»å ç¹å»å€çä»¥ç®¡çæŽ»è·å»ºè®®ç¶æïŒ
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
handlers: {
// åœç¹å»å»ºè®®å€éšæ¶åæ¶æŽ»è·å»ºè®®
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();
},
},
// ... ä¹åçéé¡¹åæž²æé
眮
})
);
ç¹å»å€çåšè¿œèžªåœå掻è·å»ºè®®ïŒ
activeIdactiveIdimport { 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: [
// ...å
¶ä»æä»¶,
suggestionPlugin,
suggestionLineBreakPlugin,
],
});
render.belowNodesïŒæž²æ SuggestionLineBreak å€ç建议äžçæ¢è¡ç¬Šäœ¿çšæä»¶APIæ§å¶å»ºè®®æš¡åŒïŒ
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 ? 'åæ¢å»ºè®®' : 'åŒå§å»ºè®®'}
</button>
);
}
æšå¯ä»¥åšå·¥å
·æ äžæ·»å SuggestionToolbarButton æ¥åæ¢çŒèŸåšç建议暡åŒã
建议æä»¶äžè®šè®ºæä»¶ååå·¥äœå®ç°å®æŽåäœïŒ
const editor = createPlateEditor({
plugins: [
// ...å
¶ä»æä»¶,
discussionPlugin,
suggestionPlugin.configure({
options: {
currentUserId: 'alice',
},
}),
suggestionLineBreakPlugin,
],
});
</Steps>
SuggestionPluginçšäºå建åç®¡çææ¬ååºå建议çæä»¶ïŒå ·æç¶æè¿œèžªå讚论éæåèœã
<API name="SuggestionPlugin"> <APIOptions> <APIItem name="currentUserId" type="string | null"> å建建议çåœåçšæ·IDãæ£ç¡®åœå±å»ºè®®æå¿ éã </APIItem> <APIItem name="isSuggesting" type="boolean"> çŒèŸåšåœåæ¯åŠå€äºå»ºè®®æš¡åŒãå éšçšäºè¿œèžªç¶æã </APIItem> </APIOptions> </API>api.suggestion.dataList仿æ¬èç¹è·åå»ºè®®æ°æ®ã
<API name="dataList"> <APIParameters> <APIItem name="node" type="TSuggestionText"> å»ºè®®ææ¬èç¹ã </APIItem> </APIParameters> <APIReturns type="TInlineSuggestionData[]"> å»ºè®®æ°æ®æ°ç»ã </APIReturns> </API>api.suggestion.isBlockSuggestionæ£æ¥èç¹æ¯åŠäžºåºå建议å çŽ ã
<API name="isBlockSuggestion"> <APIParameters> <APIItem name="node" type="TElement"> èŠæ£æ¥çèç¹ã </APIItem> </APIParameters> <APIReturns type="node is TSuggestionElement"> æ¯åŠäžºåºå建议ã </APIReturns> </API>api.suggestion.nodeè·å建议èç¹æ¡ç®ã
<API name="node"> <APIOptions type="EditorNodesOptions & { id?: string; isText?: boolean }" optional> æ¥æŸèç¹çé项ã </APIOptions> <APIReturns type="NodeEntry<TSuggestionElement | TSuggestionText> | undefined"> æŸå°ç建议èç¹æ¡ç®ã </APIReturns> </API>api.suggestion.nodeIdä»èç¹è·å建议IDã
<API name="nodeId"> <APIParameters> <APIItem name="node" type="TElement | TSuggestionText"> èŠè·åIDçèç¹ã </APIItem> </APIParameters> <APIReturns type="string | undefined"> æŸå°ç建议IDã </APIReturns> </API>api.suggestion.nodesè·åææå¹é é项ç建议èç¹æ¡ç®ã
<API name="nodes"> <APIOptions type="EditorNodesOptions" optional> æ¥æŸèç¹çé项ã </APIOptions> <APIReturns type="NodeEntry<TElement | TSuggestionText>[]"> 建议èç¹æ¡ç®æ°ç»ã </APIReturns> </API>api.suggestion.suggestionDataä»èç¹è·åå»ºè®®æ°æ®ã
<API name="suggestionData"> <APIParameters> <APIItem name="node" type="TElement | TSuggestionText"> èŠè·åå»ºè®®æ°æ®çèç¹ã </APIItem> </APIParameters> <APIReturns type="TInlineSuggestionData | TSuggestionElement['suggestion'] | undefined"> æŸå°çå»ºè®®æ°æ®ã </APIReturns> </API>api.suggestion.withoutSuggestionsåšæ§è¡åœæ°æ¶äžŽæ¶çŠçšå»ºè®®ã
<API name="withoutSuggestions"> <APIParameters> <APIItem name="fn" type="() => void"> èŠæ§è¡çåœæ°ã </APIItem> </APIParameters> </API>TSuggestionTextå¯å å«å»ºè®®çææ¬èç¹ã
<API name="TSuggestionText"> <APIAttributes> <APIItem name="suggestion" type="boolean" optional> æ¯åŠäžºå»ºè®®ã </APIItem> <APIItem name="suggestion_<id>" type="TInlineSuggestionData" optional> å»ºè®®æ°æ®ãåäžªææ¬èç¹å¯å å«å€äžªå»ºè®®ã </APIItem> </APIAttributes> </API>TSuggestionElementå å«å»ºè®®å æ°æ®çåºåå çŽ ã
<API name="TSuggestionElement"> <APIAttributes> <APIItem name="suggestion" type="TSuggestionData"> åºåçº§å»ºè®®æ°æ®ïŒå æ¬ç±»åãçšæ·åæ¶éŽä¿¡æ¯ã </APIItem> </APIAttributes> </API>TInlineSuggestionDataå èææ¬å»ºè®®çæ°æ®ç»æã
<API name="TInlineSuggestionData"> <APIAttributes> <APIItem name="id" type="string"> 建议çå¯äžæ è¯ç¬Šã </APIItem> <APIItem name="userId" type="string"> å建建议ççšæ·IDã </APIItem> <APIItem name="createdAt" type="number"> 建议åå»ºçæ¶éŽæ³ã </APIItem> <APIItem name="type" type="'insert' | 'remove' | 'update'"> 建议æäœç±»åã </APIItem> <APIItem name="newProperties" type="object" optional> å¯¹äºæŽæ°å»ºè®®ïŒå»ºè®®çæ°æ è®°å±æ§ã </APIItem> <APIItem name="properties" type="object" optional> å¯¹äºæŽæ°å»ºè®®ïŒå åçæ è®°å±æ§ã </APIItem> </APIAttributes> </API>TSuggestionDataåºåçº§å»ºè®®çæ°æ®ç»æã
<API name="TSuggestionData"> <APIAttributes> <APIItem name="id" type="string"> 建议çå¯äžæ è¯ç¬Šã </APIItem> <APIItem name="userId" type="string"> å建建议ççšæ·IDã </APIItem> <APIItem name="createdAt" type="number"> 建议åå»ºçæ¶éŽæ³ã </APIItem> <APIItem name="type" type="'insert' | 'remove'"> åºå建议æäœç±»åã </APIItem> <APIItem name="isLineBreak" type="boolean" optional> 该建议æ¯åŠä»£è¡šæ¢è¡ç¬Šæå ¥ã </APIItem> </APIAttributes> </API>â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ