Loading…
套件使用
安装
最快捷的添加建议功能方式是使用 SuggestionKit,它包含预配置的 SuggestionPlugin 及相关组件,以及它们的 Plate UI 组件。
'use client';
import type {
ExtendConfig,
TElement,
TInlineSuggestionData,
TSuggestionData,
TSuggestionText,
} from 'platejs';
import { KEYS, TextApi, TrailingBlockPlugin } from 'platejs';
import {
type BaseSuggestionConfig,
BaseSuggestionPlugin,
} from '@platejs/suggestion';
import { toTPlatePlugin } from 'platejs/react';
import {
SuggestionLeaf,
SuggestionLineBreak,
VoidRemoveSuggestionOverlay,
} from '@/components/ui/suggestion-node';
import {
discussionPlugin,
getDiscussionBlockClickTarget,
getDiscussionClickTarget,
} from './discussion-kit';
export type SuggestionConfig = ExtendConfig<
BaseSuggestionConfig,
{
activeId: string | null;
hoverId: string | null;
}
>;
const INLINE_SUGGESTION_TARGET_PLUGINS = [
KEYS.date,
KEYS.inlineEquation,
KEYS.link,
KEYS.mention,
];
function getInlineSuggestionData(editor: any, element: TElement) {
const suggestionApi = editor.getApi(BaseSuggestionPlugin).suggestion;
const data = suggestionApi.suggestionData(element) as
| TSuggestionData
| TInlineSuggestionData
| undefined;
if (data) return data;
if (typeof suggestionApi.dataList !== 'function') return;
for (const child of element.children) {
if (!TextApi.isText(child)) continue;
const childData = suggestionApi.dataList(child as TSuggestionText).at(-1);
if (childData) return childData;
}
}
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
options: {
activeId: null,
currentUserId: editor.getOption(discussionPlugin, 'currentUserId'),
hoverId: null,
},
})
).configure({
handlers: {
// unset active suggestion when clicking outside of suggestion
onClick: ({ api, event, setOption, type }) => {
const markTarget = getDiscussionClickTarget({
selector: `.slate-${type}`,
target: event.target,
});
const blockTarget = markTarget
? null
: getDiscussionBlockClickTarget({
target: event.target,
});
if (!markTarget && !blockTarget) {
setOption('activeId', null);
return;
}
const suggestionEntry = api.suggestion?.node({
isText: !blockTarget,
});
setOption(
'activeId',
suggestionEntry
? (api.suggestion?.nodeId(suggestionEntry[0]) ?? null)
: null
);
},
},
inject: {
isElement: true,
nodeProps: {
nodeKey: '',
styleKey: 'cssText',
transformProps: ({ editor, element, props }) => {
if (!element) return props;
const suggestionData = getInlineSuggestionData(editor, element);
if (!suggestionData) return props;
return {
...props,
'data-inline-suggestion': suggestionData.type,
};
},
transformStyle: () => ({}) as CSSStyleDeclaration,
},
targetPlugins: INLINE_SUGGESTION_TARGET_PLUGINS,
},
render: {
belowNodes: SuggestionLineBreak as any,
belowRootNodes: VoidRemoveSuggestionOverlay as any,
node: SuggestionLeaf,
},
});
const trailingBlockPlugin = TrailingBlockPlugin.configure({
options: {
insert: (editor, { insert }) => {
editor.getApi(suggestionPlugin).suggestion.withoutSuggestions(insert);
},
},
});
export const SuggestionKit = [suggestionPlugin, trailingBlockPlugin];'use client';
import type {
ExtendConfig,
TElement,
TInlineSuggestionData,
TSuggestionData,
TSuggestionText,
} from 'platejs';
import { KEYS, TextApi, TrailingBlockPlugin } from 'platejs';
import {
type BaseSuggestionConfig,
BaseSuggestionPlugin,
} from '@platejs/suggestion';
import { toTPlatePlugin } from 'platejs/react';
import {
SuggestionLeaf,
SuggestionLineBreak,
VoidRemoveSuggestionOverlay,
} from '@/components/ui/suggestion-node';
import {
discussionPlugin,
getDiscussionBlockClickTarget,
getDiscussionClickTarget,
} from './discussion-kit';
export type SuggestionConfig = ExtendConfig<
BaseSuggestionConfig,
{
activeId: string | null;
hoverId: string | null;
}
>;
const INLINE_SUGGESTION_TARGET_PLUGINS = [
KEYS.date,
KEYS.inlineEquation,
KEYS.link,
KEYS.mention,
];
function getInlineSuggestionData(editor: any, element: TElement) {
const suggestionApi = editor.getApi(BaseSuggestionPlugin).suggestion;
const data = suggestionApi.suggestionData(element) as
| TSuggestionData
| TInlineSuggestionData
| undefined;
if (data) return data;
if (typeof suggestionApi.dataList !== 'function') return;
for (const child of element.children) {
if (!TextApi.isText(child)) continue;
const childData = suggestionApi.dataList(child as TSuggestionText).at(-1);
if (childData) return childData;
}
}
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
options: {
activeId: null,
currentUserId: editor.getOption(discussionPlugin, 'currentUserId'),
hoverId: null,
},
})
).configure({
handlers: {
// unset active suggestion when clicking outside of suggestion
onClick: ({ api, event, setOption, type }) => {
const markTarget = getDiscussionClickTarget({
selector: `.slate-${type}`,
target: event.target,
});
const blockTarget = markTarget
? null
: getDiscussionBlockClickTarget({
target: event.target,
});
if (!markTarget && !blockTarget) {
setOption('activeId', null);
return;
}
const suggestionEntry = api.suggestion?.node({
isText: !blockTarget,
});
setOption(
'activeId',
suggestionEntry
? (api.suggestion?.nodeId(suggestionEntry[0]) ?? null)
: null
);
},
},
inject: {
isElement: true,
nodeProps: {
nodeKey: '',
styleKey: 'cssText',
transformProps: ({ editor, element, props }) => {
if (!element) return props;
const suggestionData = getInlineSuggestionData(editor, element);
if (!suggestionData) return props;
return {
...props,
'data-inline-suggestion': suggestionData.type,
};
},
transformStyle: () => ({}) as CSSStyleDeclaration,
},
targetPlugins: INLINE_SUGGESTION_TARGET_PLUGINS,
},
render: {
belowNodes: SuggestionLineBreak as any,
belowRootNodes: VoidRemoveSuggestionOverlay as any,
node: SuggestionLeaf,
},
});
const trailingBlockPlugin = TrailingBlockPlugin.configure({
options: {
insert: (editor, { insert }) => {
editor.getApi(suggestionPlugin).suggestion.withoutSuggestions(insert);
},
},
});
export const SuggestionKit = [suggestionPlugin, trailingBlockPlugin];SuggestionLeaf:渲染建议文本标记BlockSuggestion:渲染区块级建议SuggestionLineBreak:处理建议中的换行符
添加套件
import { createPlateEditor } from 'platejs/react';
import { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';
const editor = createPlateEditor({
plugins: [
// ...其他插件,
...SuggestionKit,
],
});import { createPlateEditor } from 'platejs/react';
import { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';
const editor = createPlateEditor({
plugins: [
// ...其他插件,
...SuggestionKit,
],
});手动配置
安装
pnpm add @platejs/suggestionpnpm add @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;
}
>;
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
options: {
activeId: null,
currentUserId: 'alice', // 设置当前用户ID
hoverId: null,
},
render: {
node: SuggestionLeaf,
belowRootNodes: ({ api, element }) => {
if (!api.suggestion!.isBlockSuggestion(element)) {
return null;
}
return <BlockSuggestion element={element} />;
},
},
})
);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;
}
>;
export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
BaseSuggestionPlugin,
({ editor }) => ({
options: {
activeId: null,
currentUserId: 'alice', // 设置当前用户ID
hoverId: null,
},
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,用于悬停效果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();
},
},
// ... 之前的选项和渲染配置
})
);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();
},
},
// ... 之前的选项和渲染配置
})
);点击处理器追踪当前活跃建议:
- 检测建议点击:遍历DOM查找建议元素
- 设置活跃状态:点击建议时更新
activeId - 清除状态:点击建议外部时取消
activeId - 视觉反馈:在建议组件中启用悬停/活跃样式
添加插件
import { 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,
],
});import { 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>
);
}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,
],
});const editor = createPlateEditor({
plugins: [
// ...其他插件,
discussionPlugin,
suggestionPlugin.configure({
options: {
currentUserId: 'alice',
},
}),
suggestionLineBreakPlugin,
],
});键盘快捷键
| Key | Description |
|---|---|
| Cmd + Shift + S | 在选中文本上添加建议 |
Plate Plus
插件
SuggestionPlugin
用于创建和管理文本及区块建议的插件,具有状态追踪和讨论集成功能。
API
api.suggestion.dataList
从文本节点获取建议数据。
api.suggestion.isBlockSuggestion
检查节点是否为区块建议元素。
api.suggestion.node
获取建议节点条目。
api.suggestion.nodeId
从节点获取建议ID。
api.suggestion.nodes
获取所有匹配选项的建议节点条目。
api.suggestion.suggestionData
从节点获取建议数据。
api.suggestion.withoutSuggestions
在执行函数时临时禁用建议。
类型
TSuggestionText
可包含建议的文本节点。
TSuggestionElement
包含建议元数据的区块元素。
TInlineSuggestionData
内联文本建议的数据结构。
TSuggestionData
区块级建议的数据结构。
On This Page
功能特性套件使用安装添加套件手动配置安装扩展建议插件添加点击处理器添加插件启用建议模式添加工具栏按钮讨论集成键盘快捷键Plate Plus插件SuggestionPluginAPIapi.suggestion.dataListapi.suggestion.isBlockSuggestionapi.suggestion.nodeapi.suggestion.nodeIdapi.suggestion.nodesapi.suggestion.suggestionDataapi.suggestion.withoutSuggestions类型TSuggestionTextTSuggestionElementTInlineSuggestionDataTSuggestionData