diff --git a/.prettierignore b/.prettierignore
index bba0877552..dacd7e9d0f 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -5,7 +5,6 @@
**/index.ts
**/*.mdx
**/__tests__/**/*.html
-**/.contentlayer
**/__registry__
**/og
apps/www/src/app/globals.css
\ No newline at end of file
diff --git a/apps/www/content/blogs/affinity.cn.mdx b/apps/www/content/blogs/affinity.cn.mdx
new file mode 100644
index 0000000000..5b025c2c10
--- /dev/null
+++ b/apps/www/content/blogs/affinity.cn.mdx
@@ -0,0 +1,98 @@
+---
+title: 精准掌控光标行为:用 Plate.js 解决样式边界跳跃问题
+description: 传统富文本编辑器在样式边界的光标移动上体验不佳,Plate.js 仅用一行配置即可带来更自然、可控的输入体验。本文详解其原理与用法。
+date: 2025-06-12
+author: Ziad Beyens
+published: true
+badgeLink:
+ href: "/docs/plugin-rules#rulesselection"
+ text: "查看文档"
+---
+
+## 前言
+
+在下面字母x的右侧按下 ←,光标会直接跳进下方的代码块,因为两者之间 只有一个可停留的位置。
+
+这带来了一个很实际的问题:
+
+> 我只是想在 code 的后面补充一些普通文本,结果光标跳进了 code,输入的内容却自动套上了代码样式。
+
+用户并没有明确意图进入代码块,但编辑器却强行切换了上下文,导致写作体验割裂。
+
+更令人意外的是:即便是像 Notion 或 Google Docs 这样的顶级编辑器,也没有很好地解决这个问题
+
+
+
+类似的问题也存在于 加粗(bold)文本中。
+
+当我的光标落在加粗和普通文本之间的边界时,编辑器该如何判断:
+
+我接下来要输入的是加粗的内容,还是普通样式的内容?
+
+大多数编辑器的处理是:继承左侧的样式,即便用户希望输入的是普通文本。
+
+
+
+## 解决方案
+
+不过在 plate v49 中,只需要一行配置,你就可以彻底解决光标在块级元素边界"跳进跳出不可控"的问题:
+
+```tsx
+createSlatePlugin({
+ rules: { selection: { affinity: 'hard' } },
+})
+```
+
+这样设置之后,当你使用方向键在 code 标记(如 const a = 1;)周围移动光标时,系统会明确区分:
+
+从外部移入 → 第一次停在边缘;
+再按一次 → 才进入 code 内部。
+
+这就像是给光标加了一道"缓冲层",避免误触样式,让输入变得更精准、更符合预期。
+
+如下图所示,code 的左右两侧各有一个独立的光标位置,不再是传统编辑器中的"边界即跳转"。
+
+
+
+### Affinity: 'directional'
+
+不过回到 加粗文本(bold) 的情况,事情又有点不同。
+
+由于 bold 的左右两边 没有任何 padding,当你光标靠近边界时,第一下箭头其实已经起作用了,但用户看不到任何视觉反馈,这就造成了一种错觉:
+
+"我按下了左箭头,但光标好像没动?"
+
+这也意味着,如果我们在 bold 上使用 affinity: 'hard',反而会让用户觉得键盘"失灵"了。
+
+为了解决这个问题,Plate.js 提供了另一种策略 还是一行代码:
+
+```ts
+rules: { selection: { affinity: 'directional' } },
+```
+
+使用 affinity: 'directional' 后,光标行为将根据移动方向智能判断:
+
+* 从右往左离开 text → 新输入会继承 普通样式;
+* 从左往右离开 bold → 输入将是bold样式。
+
+
+
+这个策略利用了用户的操作意图,让输入行为更加自然和可预测,同时避免了视觉"卡顿"。
+
+不过默认我们只为 Link 开启了 directional 策略,详情见 [Rules](https://platejs.org/docs/plugin-rules#rulesselection)
+
+## 最后
+
+最重要的是:
+这一切的控制权,完全掌握在你手里。
+
+无论是 bold、italic、code,还是 link——
+你都可以为每一个样式(Mark),甚至每一个内联元素,指定最适合的光标行为策略。
+
+是选择 hard,让光标拥有明确的边界感?
+还是选择 directional,根据方向智能判断输入样式?
+还是干脆保持默认行为,沿用编辑器的标准策略?
+
+选择权在你手上。每一种策略,都只需一行配置即可启用。
+
+Plate.js 给你的不只是功能,而是掌控感。
diff --git a/apps/www/content/blogs/affinity.mdx b/apps/www/content/blogs/affinity.mdx
new file mode 100644
index 0000000000..64b0617b4f
--- /dev/null
+++ b/apps/www/content/blogs/affinity.mdx
@@ -0,0 +1,98 @@
+---
+title: Precise Cursor Control - Solving Style Boundary Jumping with Plate.js
+description: Traditional rich text editors often provide a poor experience when moving the cursor across style boundaries. With just one line of configuration, Plate.js delivers a more natural and controllable input experience. This article explains the principles and usage in detail.
+date: 2025-06-12
+author: Ziad Beyens
+published: true
+badgeLink:
+ href: "/docs/plugin-rules#rulesselection"
+ text: "View Docs"
+---
+
+## Introduction
+
+When you press ← to the right of the letter x below, the cursor jumps directly into the code block below, because there is only one position where the cursor can stop between them.
+
+This brings up a very practical problem:
+
+> I just want to add some plain text after the code, but the cursor jumps into the code, and what I type is automatically styled as code.
+
+The user did not intend to enter the code block, but the editor forcibly switched the context, resulting in a fragmented writing experience.
+
+Surprisingly, even top editors like Notion or Google Docs haven't solved this problem well.
+
+
+
+A similar issue exists with bold text.
+
+When my cursor is at the boundary between bold and normal text, how should the editor determine:
+
+Will I type bold content next, or plain styled content?
+
+Most editors handle this by inheriting the style from the left, even if the user wants to type plain text.
+
+
+
+## Solution
+
+But in Plate v49, you can completely solve the uncontrollable "jumping in and out" of the cursor at block element boundaries with just one line of configuration:
+
+```tsx
+createSlatePlugin({
+ rules: { selection: { affinity: 'hard' } },
+})
+```
+
+With this setting, when you use the arrow keys around a code mark (like `const a = 1;`), the system clearly distinguishes:
+
+Entering from outside → the first time, the cursor stops at the edge;
+Press again → only then does it enter the code.
+
+It's like adding a "buffer layer" to the cursor, preventing accidental style switches and making input more precise and predictable.
+
+As shown below, there is a separate cursor position on each side of the code, no longer the "boundary means jump" behavior of traditional editors.
+
+
+
+### Affinity: 'directional'
+
+But when it comes to bold text, things are a bit different.
+
+Since there is no padding on either side of bold, when your cursor approaches the boundary, the first arrow key press actually takes effect, but the user sees no visual feedback, creating the illusion:
+
+"I pressed the left arrow, but the cursor didn't move?"
+
+This means that if we use affinity: 'hard' on bold, it may make users feel like the keyboard is "unresponsive".
+
+To solve this, Plate.js provides another strategy—still just one line of code:
+
+```ts
+rules: { selection: { affinity: 'directional' } },
+```
+
+With affinity: 'directional', cursor behavior is intelligently determined by movement direction:
+
+* Move left out of text → new input will inherit plain style;
+* Move right out of bold → input will be bold style.
+
+
+
+This strategy leverages user intent, making input behavior more natural and predictable, while avoiding visual "stuttering".
+
+By default, we only enable the directional strategy for Link. See [Rules](https://platejs.org/docs/plugin-rules#rulesselection) for details.
+
+## Conclusion
+
+Most importantly:
+You have full control over all of this.
+
+Whether it's bold, italic, code, or link—
+You can specify the most suitable cursor behavior strategy for each style (Mark), or even for each inline element.
+
+Choose 'hard' for clear boundary feeling?
+Or 'directional' for smart style selection based on direction?
+Or just keep the default, using the editor's standard strategy?
+
+The choice is yours. Each strategy can be enabled with just one line of configuration.
+
+Plate.js gives you not just features, but a sense of control.
diff --git a/apps/www/content/blogs/mutiple-nodes-dnd.cn.mdx b/apps/www/content/blogs/mutiple-nodes-dnd.cn.mdx
new file mode 100644
index 0000000000..6444b65179
--- /dev/null
+++ b/apps/www/content/blogs/mutiple-nodes-dnd.cn.mdx
@@ -0,0 +1,50 @@
+---
+title: 拖拽体验全面升级:@platejs/dnd@49.1.7 新特性详解
+description: "@platejs/dnd@49.1.7 版本带来了多块拖拽和拖拽体验的显著提升。本文将详细解析本次更新的核心改动,并探讨后续可优化的方向。"
+date: 2025-07-14
+author: Felix Feng
+published: true
+badgeLink:
+ href: "/docs/dnd"
+ text: "View Docs"
+---
+
+## 拖拽体验全面升级
+
+> 本次更新新增多块同时拖拽,并修复了 margin-top 区域无法放置的问题,显著提升了拖拽体验。
+
+### 1. 支持多块同时拖拽
+
+你可以通过以下**两种方式**选中多个块:
+
+- **方式一:使用 block-selection**
+ 通过 block-selection 功能,选中多个块后一起拖动:
+
+ 
+
+- **方式二:利用编辑器原生 selection**
+ 直接用编辑器的原生 selection 选中多块内容,再一次性拖拽:
+
+ 
+
+---
+
+### 2. 修复 margin-top 区域无法 drop 的问题
+
+- 此前,当目标块上方存在 `margin-top` 时,用户无法在该区域完成 drop 操作,影响了拖拽的流畅性:
+
+ 
+
+- 在新版本中,这一问题已**被修复**。现在,drop 区域能够正确识别 margin,用户可以在 margin-top 区域顺利完成拖拽和插入,体验更加自然:
+
+ 
+
+
+
+## 展望
+
+本次更新,让整体拖拽体验更加顺滑自然。但要实现“随心所欲”的拖拽,还需进一步完善:
+
+- 支持在编辑器外 drop
+- 优化 hover 表格边框时 drop line 的位置
+
diff --git a/apps/www/content/blogs/mutiple-nodes-dnd.mdx b/apps/www/content/blogs/mutiple-nodes-dnd.mdx
new file mode 100644
index 0000000000..00cd74252a
--- /dev/null
+++ b/apps/www/content/blogs/mutiple-nodes-dnd.mdx
@@ -0,0 +1,49 @@
+---
+title: Comprehensive Drag-and-Drop Upgrade - @platejs/dnd@49.1.7 New Features Explained
+description: "@platejs/dnd@49.1.7 introduces significant improvements to multi-block drag-and-drop and overall drag-and-drop experience. This article details the core changes in this update and discusses potential future enhancements."
+date: 2025-07-14
+author: Felix Feng
+published: true
+badgeLink:
+ href: "/docs/dnd"
+ text: "View Docs"
+---
+
+## Comprehensive Drag-and-Drop Upgrade
+
+> This update adds multi-block drag-and-drop and fixes the issue where dropping was not possible in the margin-top area, greatly enhancing the drag-and-drop experience.
+
+### 1. Multi-block Drag-and-Drop Support
+
+You can select multiple blocks using **two methods**:
+
+- **Method 1: Using block-selection**
+ Use the block-selection feature to select multiple blocks and drag them together:
+
+ 
+
+- **Method 2: Using the editor's native selection**
+ Directly use the editor's native selection to select multiple blocks and drag them all at once:
+
+ 
+
+---
+
+### 2. Fixed the Issue with Dropping in margin-top Areas
+
+- Previously, when there was a `margin-top` above the target block, users could not drop in that area, which affected the smoothness of the drag-and-drop experience:
+
+ 
+
+- In the new version, this issue has **been fixed**. Now, the drop area correctly recognizes margins, allowing users to drag and insert blocks smoothly in the margin-top area for a more natural experience:
+
+ 
+
+
+
+## Looking Ahead
+
+This update makes the overall drag-and-drop experience smoother and more natural. However, to achieve truly "freeform" drag-and-drop, further improvements are needed:
+
+- Support dropping outside the editor
+- Optimize the position of the drop line when hovering over table borders
diff --git a/apps/www/content/docs/(guides)/controlled.cn.mdx b/apps/www/content/docs/(guides)/controlled.cn.mdx
new file mode 100644
index 0000000000..0e0473e89e
--- /dev/null
+++ b/apps/www/content/docs/(guides)/controlled.cn.mdx
@@ -0,0 +1,66 @@
+---
+title: 受控编辑器值
+description: 如何控制编辑器的值。
+---
+
+在 Plate 中实现完全受控的编辑器值较为复杂,原因如下:
+
+1. 编辑器状态不仅包含内容 (`editor.children`),还包含 `editor.selection` 和 `editor.history`。
+
+2. 直接替换 `editor.children` 可能会破坏选区(selection)和历史记录(history),导致意外行为或崩溃。
+
+3. 所有对编辑器值的更改都应通过 [Transforms](https://docs.slatejs.org/api/transforms) 进行,以保持与选区(selection)和历史记录(history)的一致性。
+
+鉴于这些挑战,通常建议将 Plate 作为非受控输入使用。但如果需要从外部更改编辑器内容,可以使用 `editor.tf.setValue(value)` 函数。
+
+
+ 使用 `editor.tf.setValue` 会在每次调用时重新渲染所有节点,因此应谨慎使用。
+ 如果频繁调用或处理大型文档,可能会影响性能。
+
+
+或者,您可以使用 `editor.tf.reset()` 来重置编辑器状态,这将同时重置选区(selection)和历史记录(history)。
+
+### 异步初始值
+
+您可以使用 `skipInitialization` 延迟编辑器初始化,直到异步数据准备就绪。然后调用 `editor.tf.init` 并传入您的值:
+
+```tsx
+function AsyncControlledEditor() {
+ const [initialValue, setInitialValue] = React.useState();
+ const [loading, setLoading] = React.useState(true);
+ const editor = usePlateEditor({
+ skipInitialization: true,
+ });
+
+ React.useEffect(() => {
+ // 模拟异步获取
+ setTimeout(() => {
+ setInitialValue([
+ {
+ type: 'p',
+ children: [{ text: '已加载异步值!' }],
+ },
+ ]);
+ setLoading(false);
+ }, 1000);
+ }, []);
+
+ React.useEffect(() => {
+ if (!loading && initialValue) {
+ editor.tf.init({ value: initialValue, autoSelect: 'end' });
+ }
+ }, [loading, initialValue, editor]);
+
+ if (loading) return
加载中…
;
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/controlled.mdx b/apps/www/content/docs/(guides)/controlled.mdx
new file mode 100644
index 0000000000..ffefbb8ad1
--- /dev/null
+++ b/apps/www/content/docs/(guides)/controlled.mdx
@@ -0,0 +1,90 @@
+---
+title: Controlled Editor Value
+description: How to control the editor value.
+---
+
+Implementing a fully controlled editor value in Plate is complex due to several factors:
+
+1. The editor state includes more than just the content (`editor.children`). It also includes `editor.selection` and `editor.history`.
+
+2. Directly replacing `editor.children` can break the selection and history, leading to unexpected behavior or crashes.
+
+3. All changes to the editor's value should ideally happen through [Transforms](https://docs.slatejs.org/api/transforms) to maintain consistency with selection and history.
+
+Given these challenges, it's generally recommended to use Plate as an uncontrolled input. However, if you need to make external changes to the editor's content, you can use `editor.tf.setValue(value)` function.
+
+
+ Using `editor.tf.setValue` will re-render all nodes on each call, so it
+ should be used carefully and sparingly. It may impact performance if used
+ frequently or with large documents.
+
+
+Alternatively, you can use `editor.tf.reset()` to reset the editor state, which will reset the selection and history.
+
+### Async Initial Value
+
+You can pass an async function directly to the `value` option. The editor will handle the async loading automatically:
+
+```tsx
+function AsyncControlledEditor() {
+ const editor = usePlateEditor({
+ value: async () => {
+ // Simulate fetching data from an API
+ const response = await fetch('/api/document');
+ const data = await response.json();
+ return data.content;
+ },
+ autoSelect: 'end',
+ });
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+For more control over initialization timing, you can use `shouldInitialize: false` and manually call `editor.tf.init`:
+
+```tsx
+function AsyncControlledEditor() {
+ const [data, setData] = React.useState(null);
+ const editor = usePlateEditor({
+ shouldInitialize: false,
+ });
+
+ React.useEffect(() => {
+ const loadData = async () => {
+ const response = await fetch('/api/document');
+ const data = await response.json();
+ setData(data);
+
+ editor.tf.init({
+ value: data.content,
+ autoSelect: 'end',
+ onReady: ({ editor, value }) => {
+ console.info('Editor ready with value:', value);
+ },
+ });
+ };
+
+ loadData();
+ }, [editor]);
+
+ if (!data) return Loading…
;
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+
+
diff --git a/apps/www/content/docs/(guides)/debugging.cn.mdx b/apps/www/content/docs/(guides)/debugging.cn.mdx
new file mode 100644
index 0000000000..2f8720e2ed
--- /dev/null
+++ b/apps/www/content/docs/(guides)/debugging.cn.mdx
@@ -0,0 +1,212 @@
+---
+title: 调试
+description: Plate 中的调试。
+---
+
+## 使用 DebugPlugin
+
+当你创建 Plate 编辑器时,`DebugPlugin` 会自动包含在内。你可以通过编辑器的 API 访问其方法:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [/* your plugins */],
+});
+
+editor.api.debug.log('This is a log message');
+editor.api.debug.info('This is an info message');
+editor.api.debug.warn('This is a warning');
+editor.api.debug.error('This is an error');
+```
+
+### 日志级别
+
+`DebugPlugin` 支持四种日志级别:
+
+1. `log`:用于一般日志记录
+2. `info`:用于信息性消息
+3. `warn`:用于警告
+4. `error`:用于错误
+
+你可以设置最小日志级别来控制显示哪些消息:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [
+ DebugPlugin.configure({
+ options: {
+ logLevel: 'warn', // 仅显示警告和错误
+ },
+ }),
+ ],
+});
+```
+
+### 配置选项
+
+`DebugPlugin` 可以使用以下选项进行配置:
+
+- `isProduction`:设置为 `true` 以在生产环境中禁用日志记录。
+- `logLevel`:设置最小日志级别(`'error'`、`'warn'`、`'info'` 或 `'log'`)。
+- `logger`:为每个日志级别提供自定义日志记录函数。
+- `throwErrors`:设置为 `true` 以抛出错误而不是记录它们(默认:`true`)。
+
+配置示例:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [
+ DebugPlugin.configure({
+ options: {
+ isProduction: process.env.NODE_ENV === 'production',
+ logLevel: 'info',
+ logger: {
+ error: (message, type, details) => {
+ // 自定义错误日志记录
+ console.error(`Custom Error: ${message}`, type, details);
+ },
+ // ... 其他级别的自定义日志记录器
+ },
+ throwErrors: false,
+ },
+ }),
+ ],
+});
+```
+
+### 错误处理
+
+默认情况下,当调用 `error` 时,`DebugPlugin` 会抛出错误。你可以捕获这些错误并按需处理:
+
+```ts
+try {
+ editor.api.debug.error('An error occurred', 'CUSTOM_ERROR', { details: 'Additional information' });
+} catch (error) {
+ if (error instanceof PlateError) {
+ console.debug(error.type); // 'CUSTOM_ERROR'
+ console.debug(error.message); // '[CUSTOM_ERROR] An error occurred'
+ }
+}
+```
+
+要记录错误而不是抛出它们,请在配置中将 `throwErrors` 设置为 `false`。
+
+### 最佳实践
+
+1. 为不同类型的消息使用适当的日志级别。
+2. 在生产环境中,将 `isProduction` 设置为 `true` 以禁用非必要的日志记录。
+3. 使用自定义日志记录器与您首选的日志记录服务集成。
+4. 在记录时包含相关详细信息,以便于调试。
+5. 使用错误类型来分类和处理不同的错误场景。
+
+## 其他调试策略
+
+除了使用 DebugPlugin,还有其他有效的方法来调试你的 Plate 编辑器:
+
+### 1. 使用日志覆盖编辑器方法
+
+你可以使用 `extendEditor` 选项来覆盖编辑器方法并添加日志记录:
+
+```ts
+const LoggingPlugin = createPlatePlugin({
+ key: 'logging',
+}).overrideEditor(({ editor, tf: { apply } }) => ({
+ transforms: {
+ apply(operation) {
+ console.debug('Operation:', operation);
+ apply(operation);
+ },
+ },
+}));
+
+const editor = createPlateEditor({
+ plugins: [LoggingPlugin],
+});
+```
+
+这种方法允许你记录操作、选择或任何你想要检查的编辑器行为。
+
+### 2. 移除可疑插件
+
+如果你遇到问题,尝试逐个移除插件以隔离问题:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [
+ // 注释或移除可疑插件
+ // HeadingPlugin,
+ // BoldPlugin,
+ // ...其他插件
+ ],
+});
+```
+
+逐步添加插件,直到找出导致问题的那个。
+
+### 3. 使用 React DevTools
+
+React DevTools 对于调试 Plate 组件非常有用:
+
+1. 安装 React DevTools 浏览器扩展。
+2. 打开你的应用和 DevTools。
+3. 导航到 Components 标签页。
+4. 检查 Plate 组件、它们的 props 和状态。
+
+### 4. 使用浏览器 DevTools 断点
+
+使用浏览器 DevTools 在代码中设置断点:
+
+1. 在浏览器中打开你的应用并打开 DevTools。
+2. 导航到 Sources 标签页。
+3. 找到你的源文件并点击你想要设置断点的行号。
+4. 与编辑器交互以触发断点。
+5. 检查变量并逐步执行代码。
+
+### 5. 创建最小可重现示例
+
+如果你遇到复杂问题:
+
+1. 选择一个[模板](/docs/installation)。
+2. 仅添加必要的插件和组件来重现问题。
+3. 如果问题仍然存在,在 [GitHub 上提交问题](https://github.com/udecode/plate/issues/new?assignees=&labels=bug&projects=&template=bug.yml)或在 [Discord](https://discord.gg/mAZRuBzGM3) 上分享你的示例。
+
+### 6. 使用 Redux DevTools 进行 zustand 存储
+
+Zustand 以及 zustand-x 可以与 Redux DevTools 浏览器扩展一起使用。它对于跟踪 zustand 存储中的状态变化非常有用。
+
+按照 [zustand 文档](https://zustand.docs.pmnd.rs/middlewares/devtools) 开始使用 Redux DevTools 和 zustand。
+
+## 调试错误类型
+
+Plate 使用几个预定义的错误类型来帮助识别开发过程中的特定问题。以下是这些错误类型及其描述的列表:
+
+### DEFAULT
+
+一个不适合其他特定类别的一般错误。当没有其他错误类型适用于该情况时使用。
+
+### OPTION_UNDEFINED
+
+当尝试访问未定义的插件选项时抛出。这发生在尝试使用尚未设置或未定义的插件选项时。
+
+### OVERRIDE_MISSING
+
+表示插件配置中缺少预期的覆盖。当插件期望提供某些覆盖但它们不在配置中时,会发生这种情况。
+
+### PLUGIN_DEPENDENCY_MISSING
+
+当找不到必需的插件依赖项时发生。当插件依赖于尚未注册或包含在编辑器配置中的另一个插件时,会抛出此错误。
+
+### PLUGIN_MISSING
+
+表示尝试使用尚未注册的插件。这发生在尝试访问或使用不属于当前编辑器配置的插件时。
+
+### USE_CREATE_PLUGIN
+
+当插件不是使用 `createSlatePlugin` 或 `createPlatePlugin` 函数创建时抛出。当插件被添加到编辑器中但未使用指定函数正确创建时,会发生此错误。
+
+### USE_ELEMENT_CONTEXT
+
+表示 `useElement` hook 在适当的 element 上下文之外使用。这发生在尝试在正确的组件上下文之外访问 element 特定的数据或功能时。
+
+### PLUGIN_NODE_TYPE
+
+当插件被错误地配置为既是 element 又是 leaf 时抛出。当插件的配置通过同时将 `isElement` 和 `isLeaf` 设置为 true 而自相矛盾时,会发生此错误。
diff --git a/apps/www/content/docs/(guides)/debugging.mdx b/apps/www/content/docs/(guides)/debugging.mdx
new file mode 100644
index 0000000000..6689e76313
--- /dev/null
+++ b/apps/www/content/docs/(guides)/debugging.mdx
@@ -0,0 +1,213 @@
+---
+title: Debugging
+description: Debugging in Plate.
+---
+
+## Using the DebugPlugin
+
+The `DebugPlugin` is automatically included when you create a Plate editor. You can access its methods through the editor's API:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [/* your plugins */],
+});
+
+editor.api.debug.log('This is a log message');
+editor.api.debug.info('This is an info message');
+editor.api.debug.warn('This is a warning');
+editor.api.debug.error('This is an error');
+```
+
+### Log Levels
+
+The `DebugPlugin` supports four log levels:
+
+1. `log`: For general logging
+2. `info`: For informational messages
+3. `warn`: For warnings
+4. `error`: For errors
+
+You can set the minimum log level to control which messages are displayed:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [
+ DebugPlugin.configure({
+ options: {
+ logLevel: 'warn', // Only show warnings and errors
+ },
+ }),
+ ],
+});
+```
+
+### Configuration Options
+
+The `DebugPlugin` can be configured with the following options:
+
+- `isProduction`: Set to `true` to disable logging in production environments.
+- `logLevel`: Set the minimum log level (`'error'`, `'warn'`, `'info'`, or `'log'`).
+- `logger`: Provide custom logging functions for each log level.
+- `throwErrors`: Set to `true` to throw errors instead of logging them (default: `true`).
+
+Example configuration:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [
+ DebugPlugin.configure({
+ options: {
+ isProduction: process.env.NODE_ENV === 'production',
+ logLevel: 'info',
+ logger: {
+ error: (message, type, details) => {
+ // Custom error logging
+ console.error(`Custom Error: ${message}`, type, details);
+ },
+ // ... custom loggers for other levels
+ },
+ throwErrors: false,
+ },
+ }),
+ ],
+});
+```
+
+### Error Handling
+
+By default, the `DebugPlugin` throws errors when `error` is called. You can catch these errors and handle them as needed:
+
+```ts
+try {
+ editor.api.debug.error('An error occurred', 'CUSTOM_ERROR', { details: 'Additional information' });
+} catch (error) {
+ if (error instanceof PlateError) {
+ console.debug(error.type); // 'CUSTOM_ERROR'
+ console.debug(error.message); // '[CUSTOM_ERROR] An error occurred'
+ }
+}
+```
+
+To log errors instead of throwing them, set `throwErrors` to `false` in the configuration.
+
+### Best Practices
+
+1. Use appropriate log levels for different types of messages.
+2. In production, set `isProduction` to `true` to disable non-essential logging.
+3. Use custom loggers to integrate with your preferred logging service.
+4. Include relevant details when logging to make debugging easier.
+5. Use error types to categorize and handle different error scenarios.
+
+## Additional Debugging Strategies
+
+Besides using the DebugPlugin, there are other effective ways to debug your Plate editor:
+
+### 1. Override Editor Methods with Logging
+
+You can use the `extendEditor` option to override editor methods and add logging:
+
+```ts
+const LoggingPlugin = createPlatePlugin({
+ key: 'logging',
+}).overrideEditor(({ editor, tf: { apply } }) => ({
+ transforms: {
+ apply(operation) {
+ console.debug('Operation:', operation);
+ apply(operation);
+ },
+ },
+}));
+
+const editor = createPlateEditor({
+ plugins: [LoggingPlugin],
+});
+```
+
+This approach allows you to log operations, selections, or any other editor behavior you want to inspect.
+
+### 2. Remove Suspected Plugins
+
+If you're experiencing issues, try removing plugins one by one to isolate the problem:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [
+ // Comment out or remove suspected plugins
+ // HeadingPlugin,
+ // BoldPlugin,
+ // ...other plugins
+ ],
+});
+```
+
+Gradually add plugins back until you identify the one causing the issue.
+
+### 3. Use React DevTools
+
+React DevTools can be invaluable for debugging Plate components:
+
+1. Install the React DevTools browser extension.
+2. Open your app and the DevTools.
+3. Navigate to the Components tab.
+4. Inspect Plate components, their props, and state.
+
+### 4. Use Browser DevTools Breakpoints
+
+Set breakpoints in your code using browser DevTools:
+
+1. Open your app in the browser and open DevTools.
+2. Navigate to the Sources tab.
+3. Find your source file and click on the line number where you want to set a breakpoint.
+4. Interact with your editor to trigger the breakpoint.
+5. Inspect variables and step through the code.
+
+### 5. Create Minimal Reproducible Examples
+
+If you're facing a complex issue:
+
+1. Pick a [template](/docs/installation).
+2. Add only the essential plugins and components to reproduce the issue.
+3. If the issue persists, [open an issue on GitHub](https://github.com/udecode/plate/issues/new?assignees=&labels=bug&projects=&template=bug.yml) or share your example on [Discord](https://discord.gg/mAZRuBzGM3).
+
+### 6. Use Redux DevTools for zustand stores
+
+Zustand and thus zustand-x works with the Redux DevTools browser extension. It can be very useful to help track state changes in zustand stores.
+
+Follow the [zustand documentation](https://zustand.docs.pmnd.rs/middlewares/devtools) to get going with Redux DevTools and zustand.
+
+
+## Debug Error Types
+
+Plate uses several predefined error types to help identify specific issues during development. Here's a list of these error types and their descriptions:
+
+### DEFAULT
+
+A general error that doesn't fit into other specific categories. Used when no other error type is applicable to the situation.
+
+### OPTION_UNDEFINED
+
+Thrown when an attempt is made to access an undefined plugin option. This occurs when trying to use a plugin option that hasn't been set or is undefined.
+
+### OVERRIDE_MISSING
+
+Indicates that an expected override is missing in a plugin configuration. This happens when a plugin expects certain overrides to be provided, but they are not present in the configuration.
+
+### PLUGIN_DEPENDENCY_MISSING
+
+Occurs when a required plugin dependency is not found. This error is thrown when a plugin depends on another plugin that hasn't been registered or included in the editor configuration.
+
+### PLUGIN_MISSING
+
+Indicates an attempt to use a plugin that hasn't been registered. This happens when trying to access or use a plugin that is not part of the current editor configuration.
+
+### USE_CREATE_PLUGIN
+
+Thrown when a plugin wasn't created using `createSlatePlugin` or `createPlatePlugin` function. This error occurs when a plugin is added to the editor without being properly created using the designated function.
+
+### USE_ELEMENT_CONTEXT
+
+Indicates that the `useElement` hook is being used outside of the appropriate element context. This occurs when trying to access element-specific data or functionality outside of the correct component context.
+
+### PLUGIN_NODE_TYPE
+
+Thrown when a plugin is incorrectly configured as both an element and a leaf. This error occurs when a plugin's configuration contradicts itself by setting both `isElement` and `isLeaf` to true.
diff --git a/apps/www/content/docs/(guides)/editor-methods.cn.mdx b/apps/www/content/docs/(guides)/editor-methods.cn.mdx
new file mode 100644
index 0000000000..aefab1bab1
--- /dev/null
+++ b/apps/www/content/docs/(guides)/editor-methods.cn.mdx
@@ -0,0 +1,208 @@
+---
+title: 编辑器方法
+description: 探索可用于与Plate编辑器交互及自定义的各种方法。
+---
+
+本指南涵盖了Plate编辑器实例上可用的各种方法。
+
+## 访问编辑器
+
+访问编辑器实例的方式取决于您需要的上下文。
+
+### Plate组件内部
+
+使用以下钩子之一:
+
+- `useEditorRef`: 永不重新渲染。
+- `useEditorSelector`: 仅当特定编辑器属性变化时重新渲染。
+- `useEditorState`: 每次变化都重新渲染。
+
+```ts
+import { useEditorRef, useEditorSelector, useEditorState } from 'platejs/react';
+
+const MyComponent = () => {
+ const editor = useEditorRef();
+ const hasSelection = useEditorSelector((editor) => !!editor.selection, []);
+ const editorState = useEditorState();
+
+ // ...
+};
+```
+
+#### useEditorRef
+
+- 使用永不替换的编辑器引用。这应该是默认选择。
+- 由于编辑器是一个通过引用更新的可变对象,`useEditorRef`总是足以在回调中访问编辑器。
+- `useEditorRef`不会导致组件重新渲染,因此是性能最佳的选择。
+
+#### useEditorSelector
+
+- 订阅基于编辑器的特定选择器。这是订阅状态变化最高效的方式。
+- 示例用法:`const hasSelection = useEditorSelector((editor) => !!editor.selection, []);`
+- 当您希望组件响应特定变化重新渲染时,可以使用`useEditorSelector`访问相关属性。
+- 选择器函数在每次编辑器变化时被调用(如每次按键或选择变化),但组件仅在返回值变化时重新渲染。
+- 为确保正常工作,返回值应能使用`===`进行比较。通常这意味着返回原始值(数字、字符串或布尔值)。
+- 可以为`useEditorSelector`提供自定义的`equalityFn`选项来处理`===`不适用的情况。
+- 如果选择器函数依赖局部作用域变量,应将它们包含在依赖项列表中。
+
+#### useEditorState
+
+- 每次编辑器变化时都重新渲染。
+- 使用`useEditorState`会导致组件在用户每次按键或改变选择时重新渲染。
+- 对于大型文档或重渲染成本较高的情况,可能会引发性能问题。
+
+### Plate组件外部
+
+要在`Plate`组件外部访问编辑器或处理多个编辑器,使用`PlateController`组件:
+
+```ts
+import { PlateController } from 'platejs/react';
+
+const App = () => (
+
+
+
+
+);
+
+const Toolbar = () => {
+ const editor = useEditorState();
+ const isMounted = useEditorMounted();
+ // 在此使用编辑器方法
+};
+```
+
+`PlateController`管理活动编辑器:
+- 默认第一个挂载的编辑器为活动状态(可通过`Plate`上的`primary={false}`覆盖)。
+- 焦点改变会切换活动编辑器。
+- 编辑器保持活动状态直到另一个编辑器获得焦点或自身卸载。
+
+`PlateController`内部的钩子如`useEditorRef`和`useEditorSelector`会与活动编辑器交互。若无活动编辑器,它们会返回一个回退编辑器,该编辑器:
+- 为查询提供默认值。
+- 不可被修改。
+- 在状态变更操作时抛出错误。
+
+回退编辑器场景:
+- 没有挂载的`Plate`组件。
+- 所有`Plate`组件都标记为非主要。
+- 在`PlateContent`挂载过程中。
+
+可使用`useEditorMounted`检查是否有编辑器已挂载:
+
+```ts
+const Toolbar = () => {
+ const editor = useEditorState();
+ const isMounted = useEditorMounted();
+
+ if (!isMounted) {
+ return 编辑器未就绪
;
+ }
+
+ return {/* 工具栏内容 */}
;
+};
+```
+
+也可通过`editor.meta.isFallback`检查是否为回退实例。
+
+## API方法
+
+### findPath
+
+查找节点的路径。默认为`findNodePath`(遍历)。被`withPlate`覆盖为使用`ReactEditor.findPath`(记忆化)。
+
+```ts
+const path = editor.findPath(node);
+```
+
+### getApi
+
+获取编辑器的类型化API:
+
+```ts
+const api = editor.getApi(TablePlugin);
+api.api.create.tableCell(); // 类型安全的API方法
+```
+
+### getTransforms
+
+获取编辑器的类型化转换方法:
+
+```ts
+const tf = editor.getTransforms(TablePlugin);
+tf.insert.tableRow(); // 类型安全的转换方法
+```
+
+## 插件方法
+
+### getPlugin
+
+通过键或基础插件获取编辑器插件实例:
+
+```ts
+const codeBlockPlugin = editor.getPlugin(CodeBlockPlugin);
+const headingPlugin = editor.getPlugin({ key: KEYS.heading });
+```
+
+### getType
+
+获取与插件关联的节点类型:
+
+```ts
+const paragraphType = editor.getType(KEYS.p);
+```
+
+## 选项方法
+
+### getOption
+
+获取插件的特定选项值:
+
+```ts
+const search = editor.getOption(FindReplacePlugin, 'search');
+```
+
+要订阅选项变化,可使用`usePluginOption`或`usePluginOptions`钩子。
+
+### getOptions
+
+获取插件的所有选项:
+
+```ts
+const linkOptions = editor.getOptions(LinkPlugin);
+```
+
+### setOption
+
+设置插件的特定选项值:
+
+```ts
+editor.setOption(FindReplacePlugin, 'search', 'hello');
+```
+
+### setOptions
+
+设置插件的多个选项:
+
+```ts
+editor.setOptions(FindReplacePlugin, {
+ search: 'hello',
+ caseSensitive: true,
+});
+```
+
+也可使用Immer函数更新选项:
+
+```ts
+editor.setOptions(FindReplacePlugin, (draft) => {
+ draft.search = 'hello';
+ draft.caseSensitive = true;
+});
+```
+
+### getOptionsStore
+
+获取插件的[zustand-x](https://github.com/udecode/zustand-x)选项存储:
+
+```ts
+const store = editor.getOptionsStore(FindReplacePlugin);
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/editor-methods.mdx b/apps/www/content/docs/(guides)/editor-methods.mdx
new file mode 100644
index 0000000000..034c5eaded
--- /dev/null
+++ b/apps/www/content/docs/(guides)/editor-methods.mdx
@@ -0,0 +1,208 @@
+---
+title: Editor Methods
+description: Explore the various methods available for interacting with and customizing the Plate editor.
+---
+
+This guide covers the various methods available on the Plate editor instance.
+
+## Accessing the Editor
+
+How you access the editor instance depends on the context in which you need it.
+
+### Below Plate
+
+Use one of these hooks:
+
+- `useEditorRef`: Never re-render.
+- `useEditorSelector`: Re-render only when a specific editor property changes.
+- `useEditorState`: Re-render on every change.
+
+```ts
+import { useEditorRef, useEditorSelector, useEditorState } from 'platejs/react';
+
+const MyComponent = () => {
+ const editor = useEditorRef();
+ const hasSelection = useEditorSelector((editor) => !!editor.selection, []);
+ const editorState = useEditorState();
+
+ // ...
+};
+```
+
+#### useEditorRef
+
+- Use a reference to editor that never gets replaced. This should be the default choice.
+- Since editor is a mutable object that gets updated by reference, `useEditorRef` is always sufficient for accessing the editor inside callbacks.
+- `useEditorRef` will never cause your component to re-render, so it's the best choice for performance.
+
+#### useEditorSelector
+
+- Subscribe to a specific selector based on editor. This is the most performant option for subscribing to state changes.
+- Example usage: `const hasSelection = useEditorSelector((editor) => !!editor.selection, []);`
+- When you want your component to re-render in response to a specific change that you're interested in, you can use `useEditorSelector` to access the relevant property.
+- The selector function is called every time the editor changes (i.e. on every keystroke or selection change), but the component only re-renders when the return value changes.
+- For this to work properly, you should make sure that the return value can be compared using `===`. In most cases, this means returning a primitive value, like a number, string or boolean.
+- You can provide a custom `equalityFn` in the options to `useEditorSelector` for cases where `===` isn't sufficient.
+- If the selector function depends on any locally scoped variables, you should include these in the dependency list.
+
+#### useEditorState
+
+- Re-render every time the editor changes.
+- Using `useEditorState` will cause your component to re-render every time the user presses a key or changes the selection.
+- This may cause performance issues for large documents, or when re-rendering is particularly expensive.
+
+### Outside Plate
+
+To access the editor outside the `Plate` component or work with multiple editors, use the `PlateController` component:
+
+```ts
+import { PlateController } from 'platejs/react';
+
+const App = () => (
+
+
+
+
+);
+
+const Toolbar = () => {
+ const editor = useEditorState();
+ const isMounted = useEditorMounted();
+ // Use editor methods here
+};
+```
+
+`PlateController` manages active editors:
+- The first mounted editor is active by default (override with `primary={false}` on `Plate`).
+- Focus changes the active editor.
+- An editor remains active until another is focused or it unmounts.
+
+Hooks like `useEditorRef` and `useEditorSelector` work with the active editor inside `PlateController`. If no editor is active, they return a fallback editor, which:
+- Provides default values for queries.
+- Cannot be mutated.
+- Throws errors on state-changing operations.
+
+Fallback editor scenarios:
+- No mounted `Plate` components.
+- All `Plate` components are non-primary.
+- During `PlateContent` mounting.
+
+You can check if any editor is mounted using `useEditorMounted`:
+
+```ts
+const Toolbar = () => {
+ const editor = useEditorState();
+ const isMounted = useEditorMounted();
+
+ if (!isMounted) {
+ return Editor not ready
;
+ }
+
+ return {/* Toolbar content */}
;
+};
+```
+
+You can also use `editor.meta.isFallback` to check if the editor is a fallback instance.
+
+## API Methods
+
+### findPath
+
+Find the path of a node. Default is `findNodePath` (traversal). Overridden by `withPlate` to use `ReactEditor.findPath` (memo).
+
+```ts
+const path = editor.findPath(node);
+```
+
+### getApi
+
+Retrieve the typed API for the editor:
+
+```ts
+const api = editor.getApi(TablePlugin);
+api.api.create.tableCell(); // Type-safe API method
+```
+
+### getTransforms
+
+Get the typed transforms for the editor:
+
+```ts
+const tf = editor.getTransforms(TablePlugin);
+tf.insert.tableRow(); // Type-safe transform method
+```
+
+## Plugin Methods
+
+### getPlugin
+
+Retrieve the editor plugin instance by its key or base plugin:
+
+```ts
+const codeBlockPlugin = editor.getPlugin(CodeBlockPlugin);
+const headingPlugin = editor.getPlugin({ key: KEYS.heading });
+```
+
+### getType
+
+Get the node type associated with a plugin:
+
+```ts
+const paragraphType = editor.getType(KEYS.p);
+```
+
+## Option Methods
+
+### getOption
+
+Get a specific option value for a plugin:
+
+```ts
+const search = editor.getOption(FindReplacePlugin, 'search');
+```
+
+To subscribe to options changes, use `usePluginOption` or `usePluginOptions` hooks.
+
+### getOptions
+
+Get all options for a plugin:
+
+```ts
+const linkOptions = editor.getOptions(LinkPlugin);
+```
+
+### setOption
+
+Set a specific option value for a plugin:
+
+```ts
+editor.setOption(FindReplacePlugin, 'search', 'hello');
+```
+
+### setOptions
+
+Set multiple options for a plugin:
+
+```ts
+editor.setOptions(FindReplacePlugin, {
+ search: 'hello',
+ caseSensitive: true,
+});
+```
+
+You can also use a function to update options using Immer:
+
+```ts
+editor.setOptions(FindReplacePlugin, (draft) => {
+ draft.search = 'hello';
+ draft.caseSensitive = true;
+});
+```
+
+### getOptionsStore
+
+Get the [zustand-x](https://github.com/udecode/zustand-x) options store for a plugin:
+
+```ts
+const store = editor.getOptionsStore(FindReplacePlugin);
+```
diff --git a/apps/www/content/docs/(guides)/editor.cn.mdx b/apps/www/content/docs/(guides)/editor.cn.mdx
new file mode 100644
index 0000000000..e0258bae81
--- /dev/null
+++ b/apps/www/content/docs/(guides)/editor.cn.mdx
@@ -0,0 +1,369 @@
+---
+title: 编辑器配置
+description: 学习如何配置和自定义Plate编辑器。
+---
+
+本指南涵盖Plate编辑器的配置选项,包括基础设置、插件管理和高级配置技巧。
+
+## 基础编辑器配置
+
+创建基础Plate编辑器可以使用`createPlateEditor`函数,或在React组件中使用`usePlateEditor`:
+
+```ts
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin],
+});
+```
+
+### 初始值
+
+设置编辑器的初始内容:
+
+```ts
+const editor = createPlateEditor({
+ value: [
+ {
+ type: 'p',
+ children: [{ text: '你好,Plate!' }],
+ },
+ ],
+});
+```
+
+也可以使用HTML字符串和关联插件初始化编辑器:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [BoldPlugin, ItalicPlugin],
+ value: '这是加粗 和斜体 文本!
',
+});
+```
+
+支持HTML字符串反序列化的完整插件列表,请参阅[插件反序列化规则](/docs/html#plugin-deserialization-rules)部分。
+
+### 异步初始值
+
+如果需要异步获取初始值(例如从API),可以使用`skipInitialization`选项并在数据就绪后调用`editor.tf.init`:
+
+```tsx
+function AsyncEditor() {
+ const [initialValue, setInitialValue] = React.useState();
+ const [loading, setLoading] = React.useState(true);
+ const editor = usePlateEditor({
+ skipInitialization: true,
+ });
+
+ React.useEffect(() => {
+ // 模拟异步获取
+ setTimeout(() => {
+ setInitialValue([
+ {
+ type: 'p',
+ children: [{ text: '异步加载的值!' }],
+ },
+ ]);
+ setLoading(false);
+ }, 1000);
+ }, []);
+
+ React.useEffect(() => {
+ if (!loading && initialValue) {
+ editor.tf.init({ value: initialValue });
+ }
+ }, [loading, initialValue, editor]);
+
+ if (loading) return 加载中…
;
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+这种模式适用于需要等待数据才能初始化编辑器的情况,例如从服务器或数据库加载内容时。
+
+### 添加插件
+
+通过`plugins`数组添加插件:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin, ListPlugin],
+});
+```
+
+### 最大长度
+
+设置编辑器的最大长度:
+
+```ts
+const editor = createPlateEditor({
+ maxLength: 100,
+});
+```
+
+## 高级配置
+
+### 编辑器ID
+
+为编辑器设置自定义ID:
+
+```ts
+const editor = createPlateEditor({
+ id: 'my-custom-editor-id',
+});
+```
+
+如果定义了ID,在任何编辑器检索方法中都应该将其作为第一个参数传递。
+
+### 节点ID
+
+Plate内置了为节点自动分配唯一ID的系统,这对于某些插件和依赖稳定标识符的数据持久化策略至关重要。
+
+此功能默认启用。可以通过`nodeId`选项自定义其行为或完全禁用。
+
+#### 配置
+
+创建编辑器时,向`nodeId`属性传递对象来配置节点ID行为:
+
+```ts
+const editor = usePlateEditor({
+ // ... 其他插件和选项
+ nodeId: {
+ // 生成ID的函数(默认:nanoid(10))
+ idCreator: () => uuidv4(),
+
+ // 排除内联元素获取ID(默认:true)
+ filterInline: true,
+
+ // 排除文本节点获取ID(默认:true)
+ filterText: true,
+
+ // 在撤销/重做和复制/粘贴时重用ID(默认:false)
+ // 设置为true可使ID在这些操作中保持稳定
+ reuseId: false,
+
+ // 规范化初始值中的所有节点(默认:false - 仅检查首尾)
+ // 设置为true确保所有初始节点在缺失时获取ID
+ normalizeInitialValue: false,
+
+ // 禁止覆盖插入节点的现有ID(默认:false)
+ disableInsertOverrides: false,
+
+ // 仅允许特定节点类型获取ID(默认:全部)
+ allow: ['p', 'h1'],
+
+ // 排除特定节点类型获取ID(默认:[])
+ exclude: ['code_block'],
+
+ // 自定义过滤函数决定节点是否获取ID
+ filter: ([node, path]) => {
+ // 示例:仅对顶层块分配ID
+ return path.length === 1;
+ },
+ },
+});
+```
+
+
+ `NodeIdPlugin`(处理此功能)是核心插件的一部分,已自动包含。只有在需要自定义其默认行为时才需要指定`nodeId`选项。
+
+
+#### 禁用节点ID
+
+如果不需要自动节点ID,可以禁用此功能:
+
+```ts
+const editor = usePlateEditor({
+ // ... 其他插件和选项
+ nodeId: false, // 这将禁用NodeIdPlugin
+});
+```
+
+禁用后,依赖节点ID的某些插件将无法正常工作。以下插件需要块ID才能工作:
+
+- **[块选择](/docs/block-selection)** - 需要ID来跟踪选中的块
+- **[块菜单](/docs/block-menu)** - 需要ID显示特定块的上下文菜单
+- **[拖放](/docs/dnd)** - 使用ID在拖拽操作中识别块
+- **[表格](/docs/table)** - 依赖ID进行单元格选择
+- **[目录](/docs/toc)** - 需要标题ID进行导航和滚动
+- **[折叠](/docs/toggle)** - 使用ID跟踪哪些折叠项是打开/关闭的
+
+### 规范化
+
+控制编辑器是否在初始化时规范化其内容:
+
+```ts
+const editor = createPlateEditor({
+ shouldNormalizeEditor: true,
+});
+```
+
+注意,对于大型文档(如playground值),规范化可能需要几十毫秒。
+
+### 自动选择
+
+配置编辑器自动选择范围:
+
+```ts
+const editor = createPlateEditor({
+ autoSelect: 'end', // 或 'start',或 true
+});
+```
+
+这与自动聚焦不同:可以在不聚焦编辑器的情况下选择文本。
+
+### 组件覆盖
+
+覆盖插件的默认组件:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin],
+ components: {
+ [ParagraphPlugin.key]: CustomParagraphComponent,
+ [HeadingPlugin.key]: CustomHeadingComponent,
+ },
+});
+```
+
+### 插件覆盖
+
+覆盖特定插件配置:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin],
+ override: {
+ plugins: {
+ [ParagraphPlugin.key]: {
+ options: {
+ customOption: true,
+ },
+ },
+ },
+ },
+});
+```
+
+### 禁用插件
+
+禁用特定插件:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin, ListPlugin],
+ override: {
+ enabled: {
+ [HistoryPlugin.key]: false,
+ },
+ },
+});
+```
+
+### 覆盖插件
+
+可以通过添加具有相同键的插件来覆盖核心插件或先前定义的插件。最后一个具有给定键的插件将生效:
+
+```ts
+const CustomParagraphPlugin = createPlatePlugin({
+ key: 'p',
+ // 自定义实现
+});
+
+const editor = createPlateEditor({
+ plugins: [CustomParagraphPlugin],
+});
+```
+
+### 根插件
+
+从根插件可以配置任何插件:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin],
+ rootPlugin: (plugin) =>
+ plugin.configurePlugin(LengthPlugin, {
+ options: {
+ maxLength: 100,
+ },
+ }),
+});
+```
+
+## 类型化编辑器
+
+`createPlateEditor`会根据传递的值和插件自动推断编辑器的类型。要显式创建类型,可以使用泛型:
+
+### 插件类型
+
+```ts
+const editor = createPlateEditor({
+ plugins: [TablePlugin, LinkPlugin],
+});
+
+// 使用
+editor.tf.insert.tableRow()
+```
+
+### 值类型
+
+对于更复杂的编辑器,可以在单独的文件中定义类型(例如`plate-types.ts`):
+
+```ts
+import type { TElement, TText } from 'platejs';
+import type { TPlateEditor } from 'platejs/react';
+
+// 定义自定义元素类型
+interface ParagraphElement extends TElement {
+ align?: 'left' | 'center' | 'right' | 'justify';
+ children: RichText[];
+ type: typeof ParagraphPlugin.key;
+}
+
+interface ImageElement extends TElement {
+ children: [{ text: '' }]
+ type: typeof ImagePlugin.key;
+ url: string;
+}
+
+// 定义自定义文本类型
+interface FormattedText extends TText {
+ bold?: boolean;
+ italic?: boolean;
+}
+
+export type MyRootBlock = ParagraphElement | ImageElement;
+
+// 定义编辑器的值类型
+export type MyValue = MyRootBlock[];
+
+// 定义自定义编辑器类型
+export type MyEditor = TPlateEditor;
+
+export const useMyEditorRef = () => useEditorRef();
+
+// 使用
+const value: MyValue = [{
+ type: 'p',
+ children: [{ text: '你好,Plate!' }],
+}]
+
+const editorInferred = createPlateEditor({
+ plugins: [TablePlugin, LinkPlugin],
+ value,
+});
+
+// 或
+const editorExplicit = createPlateEditor({
+ plugins: [TablePlugin, LinkPlugin],
+ value,
+});
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/editor.mdx b/apps/www/content/docs/(guides)/editor.mdx
new file mode 100644
index 0000000000..e8559b040c
--- /dev/null
+++ b/apps/www/content/docs/(guides)/editor.mdx
@@ -0,0 +1,356 @@
+---
+title: Editor Configuration
+description: Learn how to configure and customize the Plate editor.
+---
+
+This guide covers the configuration options for the Plate editor, including basic setup, plugin management, and advanced configuration techniques.
+
+## Basic Editor Configuration
+
+To create a basic Plate editor, you can use the `createPlateEditor` function, or `usePlateEditor` in a React component:
+
+```ts
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin],
+});
+```
+
+### Initial Value
+
+Set the initial content of the editor:
+
+```ts
+const editor = createPlateEditor({
+ value: [
+ {
+ type: 'p',
+ children: [{ text: 'Hello, Plate!' }],
+ },
+ ],
+});
+```
+
+You can also initialize the editor with an HTML string and the associated plugins:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [BoldPlugin, ItalicPlugin],
+ value: 'This is bold and italic text!
',
+});
+```
+
+For a comprehensive list of plugins that support HTML string deserialization, refer to the [Plugin Deserialization Rules](/docs/html#plugin-deserialization-rules) section.
+
+### Async Initial Value
+
+If you need to fetch the initial value asynchronously (e.g., from an API), you can pass an async function directly to the `value` option:
+
+```tsx
+function AsyncEditor() {
+ const editor = usePlateEditor({
+ value: async () => {
+ // Simulate fetching data from an API
+ const response = await fetch('/api/document');
+ const data = await response.json();
+ return data.content;
+ },
+ autoSelect: 'end',
+ onReady: ({ editor, value }) => {
+ console.info('Editor ready with loaded value:', value);
+ },
+ });
+
+ if (!editor.children.length) return Loading…
;
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+### Adding Plugins
+
+You can add plugins to your editor by including them in the `plugins` array:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin, ListPlugin],
+});
+```
+
+### Max Length
+
+Set the maximum length of the editor:
+
+```ts
+const editor = createPlateEditor({
+ maxLength: 100,
+});
+```
+
+## Advanced Configuration
+
+### Editor ID
+
+Set a custom id for the editor:
+
+```ts
+const editor = createPlateEditor({
+ id: 'my-custom-editor-id',
+});
+```
+
+If defined, you should always pass the `id` as the first argument in any editor retrieval methods.
+
+### Node ID
+
+Plate includes a built-in system for automatically assigning unique IDs to nodes, which is crucial for certain plugins and for data persistence strategies that rely on stable identifiers.
+
+This feature is enabled by default. You can customize its behavior or disable it entirely through the `nodeId` option.
+
+#### Configuration
+
+To configure Node ID behavior, pass an object to the `nodeId` property when creating your editor:
+
+```ts
+const editor = usePlateEditor({
+ // ... other plugins and options
+ nodeId: {
+ // Function to generate IDs (default: nanoid(10))
+ idCreator: () => uuidv4(),
+
+ // Exclude inline elements from getting IDs (default: true)
+ filterInline: true,
+
+ // Exclude text nodes from getting IDs (default: true)
+ filterText: true,
+
+ // Reuse IDs on undo/redo and copy/paste if not in document (default: false)
+ // Set to true if IDs should be stable across such operations.
+ reuseId: false,
+
+ // Normalize all nodes in initial value (default: false - only checks first/last)
+ // Set to true to ensure all initial nodes get IDs if missing.
+ normalizeInitialValue: false,
+
+ // Prevent overriding IDs when inserting nodes with an existing id (default: false)
+ disableInsertOverrides: false,
+
+ // Only allow specific node types to receive IDs (default: all)
+ allow: ['p', 'h1'],
+
+ // Exclude specific node types from receiving IDs (default: [])
+ exclude: ['code_block'],
+
+ // Custom filter function to determine if a node should get an ID
+ filter: ([node, path]) => {
+ // Example: Only ID on top-level blocks
+ return path.length === 1;
+ },
+ },
+});
+```
+
+
+ The `NodeIdPlugin` (which handles this) is part of the core plugins and is automatically included. You only need to specify the `nodeId` option if you want to customize its default behavior.
+
+
+#### Disabling Node IDs
+
+If you don't need automatic node IDs, you can disable the feature:
+
+```ts
+const editor = usePlateEditor({
+ // ... other plugins and options
+ nodeId: false, // This will disable the NodeIdPlugin
+});
+```
+
+By disabling this, certain plugins that rely on node IDs will not function properly. The following plugins require block IDs to work:
+
+- **[Block Selection](/docs/block-selection)** - Needs IDs to track which blocks are selected
+- **[Block Menu](/docs/block-menu)** - Requires IDs to show context menus for specific blocks
+- **[Drag & Drop](/docs/dnd)** - Uses IDs to identify blocks during drag operations
+- **[Table](/docs/table)** - Relies on IDs for cell selection
+- **[Table of Contents](/docs/toc)** - Needs heading IDs for navigation and scrolling
+- **[Toggle](/docs/toggle)** - Uses IDs to track which toggles are open/closed
+
+### Normalization
+
+Control whether the editor should normalize its content on initialization:
+
+```ts
+const editor = createPlateEditor({
+ shouldNormalizeEditor: true,
+});
+```
+
+Note that normalization may take a few dozen milliseconds for large documents, such as the playground value.
+
+### Auto-selection
+
+Configure the editor to automatically select a range:
+
+```ts
+const editor = createPlateEditor({
+ autoSelect: 'end', // or 'start', or true
+});
+```
+
+This is not the same as auto-focus: you can select text without focusing the editor.
+
+### Component Overrides
+
+Override default components for plugins:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin],
+ components: {
+ [ParagraphPlugin.key]: CustomParagraphComponent,
+ [HeadingPlugin.key]: CustomHeadingComponent,
+ },
+});
+```
+
+### Plugin Overrides
+
+Override specific plugin configurations:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin],
+ override: {
+ plugins: {
+ [ParagraphPlugin.key]: {
+ options: {
+ customOption: true,
+ },
+ },
+ },
+ },
+});
+```
+
+### Disable Plugins
+
+Disable specific plugins:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin, ListPlugin],
+ override: {
+ enabled: {
+ [HistoryPlugin.key]: false,
+ },
+ },
+});
+```
+
+### Overriding Plugins
+
+You can override core plugins or previously defined plugins by adding a plugin with the same key. The last plugin with a given key wins:
+
+```ts
+const CustomParagraphPlugin = createPlatePlugin({
+ key: 'p',
+ // Custom implementation
+});
+
+const editor = createPlateEditor({
+ plugins: [CustomParagraphPlugin],
+});
+```
+
+### Root Plugin
+
+From the root plugin, you can configure any plugin:
+
+```ts
+const editor = createPlateEditor({
+ plugins: [HeadingPlugin],
+ rootPlugin: (plugin) =>
+ plugin.configurePlugin(LengthPlugin, {
+ options: {
+ maxLength: 100,
+ },
+ }),
+});
+```
+
+## Typed Editor
+
+`createPlateEditor` will automatically infer the types for your editor from the value and the plugins you pass in. For explicit type creation, use the generics:
+
+### Plugins Type
+
+```ts
+const editor = createPlateEditor({
+ plugins: [TablePlugin, LinkPlugin],
+});
+
+// Usage
+editor.tf.insert.tableRow()
+```
+
+### Value Type
+
+For more complex editors, you can define your types in a separate file (e.g., `plate-types.ts`):
+
+```ts
+import type { TElement, TText } from 'platejs';
+import type { TPlateEditor } from 'platejs/react';
+
+// Define custom element types
+interface ParagraphElement extends TElement {
+ align?: 'left' | 'center' | 'right' | 'justify';
+ children: RichText[];
+ type: typeof ParagraphPlugin.key;
+}
+
+interface ImageElement extends TElement {
+ children: [{ text: '' }]
+ type: typeof ImagePlugin.key;
+ url: string;
+}
+
+// Define custom text types
+interface FormattedText extends TText {
+ bold?: boolean;
+ italic?: boolean;
+}
+
+export type MyRootBlock = ParagraphElement | ImageElement;
+
+// Define the editor's value type
+export type MyValue = MyRootBlock[];
+
+// Define the custom editor type
+export type MyEditor = TPlateEditor;
+
+export const useMyEditorRef = () => useEditorRef();
+
+// Usage
+const value: MyValue = [{
+ type: 'p',
+ children: [{ text: 'Hello, Plate!' }],
+}]
+
+const editorInferred = createPlateEditor({
+ plugins: [TablePlugin, LinkPlugin],
+ value,
+});
+
+// or
+const editorExplicit = createPlateEditor({
+ plugins: [TablePlugin, LinkPlugin],
+ value,
+});
+```
+
diff --git a/apps/www/content/docs/(guides)/feature-kits.cn.mdx b/apps/www/content/docs/(guides)/feature-kits.cn.mdx
new file mode 100644
index 0000000000..11a1a0fb78
--- /dev/null
+++ b/apps/www/content/docs/(guides)/feature-kits.cn.mdx
@@ -0,0 +1,71 @@
+---
+title: 功能套件
+description: 了解如何使用预配置插件和UI组件的功能套件快速搭建编辑器。
+---
+
+功能套件将相关插件及其UI组件打包整合,提供最快捷的编辑器功能添加方式。
+
+## 什么是功能套件?
+
+功能套件是预先组装的工具包,包含针对特定编辑器功能的多个插件和组件。
+
+例如,`BasicBlocksKit`包含段落、标题、引用块和水平分割线的插件,以及对应的UI组件。
+
+## 使用套件的优势
+
+- **快速配置**:通过一次导入添加多个功能
+- **最佳实践**:套件采用合理的默认配置
+- **内置UI**:多数套件包含[Plate UI](/docs/installation/plate-ui)组件
+- **简化依赖**:套件自动处理插件依赖关系
+
+## 套件使用 vs 手动配置
+
+插件文档中展示两种使用方式:
+
+- **套件使用**:使用预配置套件,推荐大多数用户采用
+- **手动配置**:单独安装配置每个插件,适用于以下场景:
+ - 需要自定义插件配置(超出套件默认值)
+ - 需要使用不同UI组件或无UI(headless模式)
+ - 仅需套件中的特定插件
+
+套件本质上是预配置好的手动方案。
+
+## 查找可用套件
+
+在插件文档中寻找"Kit Usage"章节,这里会说明使用的套件并展示套件源代码。
+
+## 示例:BasicBlocksKit
+
+[`BasicBlocksKit`](/docs/basic-blocks#installation)包含以下插件和组件:
+
+- `ParagraphPlugin`及`ParagraphElement`
+- `H1Plugin`及`H1Element`
+- `H2Plugin`及`H2Element`
+- `H3Plugin`及`H3Element`
+- `BlockquotePlugin`及`BlockquoteElement`
+- `HorizontalRulePlugin`及`HrElement`
+
+无需单独添加每个插件,直接使用套件:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+即可一次性添加所有区块元素及其UI组件。
+
+## 何时选择手动配置
+
+以下情况建议手动配置:
+
+- **自定义配置**:套件默认值不满足需求时
+- **定制UI**:不使用[Plate UI](/docs/installation/plate-ui)组件时
+- **精简配置**:仅需套件中一两个插件时
+- **学习目的**:需要理解插件协作机制时
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/feature-kits.mdx b/apps/www/content/docs/(guides)/feature-kits.mdx
new file mode 100644
index 0000000000..db79cac441
--- /dev/null
+++ b/apps/www/content/docs/(guides)/feature-kits.mdx
@@ -0,0 +1,72 @@
+---
+title: Feature Kits
+description: Understand how to use Feature Kits for rapid editor setup with pre-configured plugins and UI components.
+---
+
+Feature Kits bundle related plugins and their UI components. They provide the fastest way to add editor features.
+
+## What are Feature Kits?
+
+Feature Kits are pre-assembled packages. They include multiple plugins and components for specific editor features.
+
+For example, `BasicBlocksKit` includes plugins for paragraphs, headings, blockquotes, and horizontal rules. It also includes their UI components.
+
+## Benefits of Using Kits
+
+- **Fast setup**: Add multiple features with one import
+- **Best practices**: Kits use sensible defaults and configurations
+- **UI included**: Most kits include [Plate UI](/docs/installation/plate-ui) components
+- **Simple dependencies**: Kits handle plugin dependencies automatically
+
+## Kit Usage vs Manual Usage
+
+Plugin documentation shows two approaches:
+
+- **Kit Usage**: Use a pre-configured kit. This is the recommended approach for most users.
+- **Manual Usage**: Install and configure each plugin individually. Use this when you need:
+ - Custom plugin options beyond kit defaults
+ - Different UI components or no UI (headless)
+ - Only specific plugins from a kit
+
+Kits are mostly the same as manual setup, but pre-configured.
+
+## Finding Available Kits
+
+Look for "Kit Usage" sections in plugin documentation. These sections show which kit to use and include the kit's source code.
+
+## Example: BasicBlocksKit
+
+The [`BasicBlocksKit`](/docs/basic-blocks#installation) includes these plugins and components:
+
+- `ParagraphPlugin` with `ParagraphElement`
+- `H1Plugin` with `H1Element`
+- `H2Plugin` with `H2Element`
+- `H3Plugin` with `H3Element`
+- `BlockquotePlugin` with `BlockquoteElement`
+- `HorizontalRulePlugin` with `HrElement`
+
+Instead of adding each plugin separately, use the kit:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+This adds all block elements with their UI components.
+
+## When to Use Manual Setup
+
+Use manual setup when you need:
+
+- **Custom options**: Kit defaults don't meet your needs
+- **Custom UI**: You're not using [Plate UI](/docs/installation/plate-ui) components
+- **Minimal setup**: You only need one or two plugins from a kit
+- **Learning**: You want to understand how plugins work together
+
diff --git a/apps/www/content/docs/(guides)/form.cn.mdx b/apps/www/content/docs/(guides)/form.cn.mdx
new file mode 100644
index 0000000000..0a6ca48f0d
--- /dev/null
+++ b/apps/www/content/docs/(guides)/form.cn.mdx
@@ -0,0 +1,428 @@
+---
+title: 表单
+description: 如何将 Plate 编辑器与 react-hook-form 集成。
+---
+
+虽然 Plate 通常用作**非受控**输入,但在某些场景下,您可能希望将编辑器集成到表单库中,比如 [**react-hook-form**](https://www.react-hook-form.com) 或 **shadcn/ui** 的 [**Form**](https://ui.shadcn.com/docs/components/form) 组件。本指南将介绍最佳实践和常见陷阱。
+
+## 何时将 Plate 与表单集成
+
+- **表单提交**:您希望在用户提交表单时,将编辑器内容与其他字段(如 ` `、``)一起包含。
+- **验证**:您希望同时验证编辑器内容(例如,检查是否为空)和其他表单字段。
+- **表单数据管理**:您希望将编辑器内容存储在与表单字段相同的存储中(如 `react-hook-form` 的状态)。
+
+但是,请注意关于**完全控制**编辑器值的警告。Plate 强烈推荐使用非受控模型。如果您过于频繁地替换编辑器的内部状态,可能会破坏**选择**、**历史记录**或导致性能问题。推荐的模式是将编辑器视为非受控的,但在特定事件上仍然**同步**表单数据。
+
+## 方法 1:在 `onChange` 时同步
+
+这是最直接的方法:每次编辑器更改时,更新表单字段的值。对于小型文档或不频繁的更改,这通常是可以接受的。
+
+### React Hook Form 示例
+
+```tsx
+import { useForm } from 'react-hook-form';
+import type { Value } from 'platejs';
+import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
+
+type FormData = {
+ content: Value;
+};
+
+export function RHFEditorForm() {
+ const initialValue = [
+ { type: 'p', children: [{ text: '来自 react-hook-form 的问候!' }] },
+ ]
+
+ // 设置 react-hook-form
+ const { register, handleSubmit, setValue } = useForm({
+ defaultValues: {
+ content: initialValue,
+ },
+ });
+
+ // 创建/配置 Plate 编辑器
+ const editor = usePlateEditor({ value: initialValue });
+
+ // 为 react-hook-form 注册字段
+ register('content', { /* 验证规则... */ });
+
+ const onSubmit = (data: FormData) => {
+ // data.content 将包含最终的编辑器内容
+ console.info('已提交:', data.content);
+ };
+
+ return (
+
+ );
+}
+```
+
+**注意事项**:
+1. **`defaultValues.content`**:您的初始编辑器内容。
+2. **`register('content')`**:向 RHF 表明该字段被跟踪。
+3. **`onChange({ value })`**:每次调用 `setValue('content', value)`。
+
+如果您处理大型文档或快速输入,请考虑使用防抖或切换到 `onBlur` 方法来减少表单更新。
+
+### shadcn/ui 表单示例
+
+[shadcn/ui](https://ui.shadcn.com/docs/components/form) 提供了一个与 react-hook-form 集成的 `
+ );
+}
+```
+
+这种方法使您的编辑器内容的行为与 shadcn/ui 表单中的其他字段一样。
+
+## 方法 2:在失焦时同步(或其他触发方式)
+
+您可能只需要在用户以下情况时获取最终值:
+- 离开编辑器(`onBlur`)
+- 点击"保存"按钮
+- 达到某些表单提交逻辑
+
+```tsx
+
+ {
+ // 仅在失焦时同步
+ setValue('content', editor.children);
+ }}
+ />
+
+```
+
+这减少了开销,但您的表单状态在用户输入时不会反映部分更新。
+
+## 方法 3:受控替换(高级)
+
+如果您希望表单成为单一数据源(完全[受控](/docs/controlled)):
+```ts
+editor.tf.setValue(formStateValue);
+```
+但这有已知的缺点:
+- 可能会破坏光标位置和撤销/重做功能
+- 对于大型文档可能导致频繁的完全重新渲染
+
+**建议**:如果可能,坚持使用部分非受控模型。
+
+## 示例:保存和重置
+
+这是一个更完整的表单示例,展示了如何保存和重置编辑器/表单:
+
+```tsx
+import { useForm } from 'react-hook-form';
+import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
+
+function MyForm() {
+ const form = useForm({
+ defaultValues: {
+ content: [
+ { type: 'p', children: [{ text: '初始内容...' }] },
+ ],
+ },
+ });
+
+ const editor = usePlateEditor();
+
+ const onSubmit = (data) => {
+ alert(JSON.stringify(data, null, 2));
+ };
+
+ return (
+
+ form.setValue('content', value)}
+ >
+
+
+
+ 保存
+
+ {
+ // 重置编辑器
+ editor.tf.reset();
+ // 重置表单
+ form.reset();
+ }}
+ >
+ 重置
+
+
+ );
+}
+```
+
+- **`onChange`** -> 更新表单状态
+- **重置** -> 同时调用 `editor.tf.reset()` 和 `form.reset()` 以保持一致性
+
+## 从 shadcn Textarea 迁移到 Plate
+
+如果您有一个标准的 [shadcn/ui 文档中的 TextareaForm](https://ui.shadcn.com/docs/components/textarea#form),并想用 Plate 编辑器替换 ``,可以按照以下步骤操作:
+
+```tsx
+// 1. 原始代码 (TextareaForm)
+ (
+
+ 个人简介
+
+
+
+
+ 您可以 @提及 其他用户和组织。
+
+
+
+ )}
+/>
+```
+
+创建一个新的 `EditorField` 组件:
+
+```tsx
+// EditorField.tsx
+"use client";
+
+import * as React from "react";
+import type { Value } from "platejs";
+import { Plate, PlateContent, usePlateEditor } from "platejs/react";
+
+/**
+ * 一个可重用的编辑器组件,工作方式类似于标准的 ,
+ * 接受 `value`、`onChange` 和可选的 placeholder。
+ *
+ * 用法:
+ *
+ * (
+ *
+ * 个人简介
+ *
+ *
+ *
+ * 一些有帮助的描述...
+ *
+ *
+ * )}
+ * />
+ */
+export interface EditorFieldProps
+ extends React.HTMLAttributes {
+ /**
+ * 当前的 Plate 值。应该是一个 Plate 节点数组。
+ */
+ value?: Value;
+
+ /**
+ * 当编辑器值改变时调用。
+ */
+ onChange?: (value: Value) => void;
+
+ /**
+ * 编辑器为空时显示的占位符文本。
+ */
+ placeholder?: string;
+}
+
+export function EditorField({
+ value,
+ onChange,
+ placeholder = "在此输入...",
+ ...props
+}: EditorFieldProps) {
+ // 使用提供的初始 `value` 创建编辑器实例。
+ const editor = usePlateEditor({
+ value: value ?? [
+ { type: "p", children: [{ text: "" }] }, // 默认空段落
+ ],
+ });
+
+ return (
+ {
+ // 通过 onChange prop 将更改同步回调用者
+ onChange?.(value);
+ }}
+ {...props}
+ >
+
+
+ );
+}
+```
+
+3. 用 `` 替换 `` 块:
+
+```tsx
+"use client";
+
+import { z } from "zod";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { EditorField } from "./EditorField"; // 导入上面的组件
+
+// 1. 使用 zod 定义验证模式
+const FormSchema = z.object({
+ bio: z
+ .string()
+ .min(10, { message: "个人简介至少需要 10 个字符。" })
+ .max(160, { message: "个人简介不能超过 160 个字符。" }),
+});
+
+// 2. 构建主表单组件
+export function EditorForm() {
+ // 3. 设置表单
+ const form = useForm>({
+ resolver: zodResolver(FormSchema),
+ defaultValues: {
+ // 这里 "bio" 只是一个字符串,但如果您将其解析为 JSON,
+ // 我们的编辑器会将其解释为初始内容
+ bio: "",
+ },
+ });
+
+ // 4. 提交处理函数
+ function onSubmit(data: z.infer) {
+ alert("已提交: " + JSON.stringify(data, null, 2));
+ }
+
+ return (
+
+
+ (
+
+ 个人简介
+
+
+
+
+ 您可以 @提及 其他用户和组织。
+
+
+
+ )}
+ />
+
+ 提交
+
+
+
+ );
+}
+```
+
+- 任何现有的表单验证或错误消息保持不变。
+- 对于默认值,确保 `form.defaultValues.bio` 是一个有效的 Plate 值(节点数组)而不是字符串。
+- 对于受控值,适度使用 `editor.tf.setValue(formStateValue)`。
+
+## 最佳实践
+
+1. **使用非受控编辑器**:让 Plate 管理自己的状态,仅在必要时更新表单。
+2. **最小化替换**:避免过于频繁地调用 `editor.tf.setValue`,这可能会破坏选择、历史记录或降低性能。
+3. **在适当的时机验证**:决定是需要即时验证(输入时)还是在失焦/提交时验证。
+4. **同时重置**:如果重置表单,调用 `editor.tf.reset()` 以保持同步。
diff --git a/apps/www/content/docs/(guides)/form.mdx b/apps/www/content/docs/(guides)/form.mdx
new file mode 100644
index 0000000000..72c9c4d65e
--- /dev/null
+++ b/apps/www/content/docs/(guides)/form.mdx
@@ -0,0 +1,428 @@
+---
+title: Form
+description: How to integrate Plate editor with react-hook-form.
+---
+
+While Plate is typically used as an **uncontrolled** input, there are valid scenarios where you want to integrate the editor within a form library like [**react-hook-form**](https://www.react-hook-form.com) or the [**Form**](https://ui.shadcn.com/docs/components/form) component from **shadcn/ui**. This guide walks through best practices and common pitfalls.
+
+## When to Integrate Plate with a Form
+
+- **Form Submission**: You want the editor's content to be included along with other fields (e.g., ` `, ``) when the user submits the form.
+- **Validation**: You want to validate the editor's content (e.g., checking if it's empty) at the same time as other form fields.
+- **Form Data Management**: You want to store the editor content in the same store (like `react-hook-form`'s state) as other fields.
+
+However, keep in mind the warning about **fully controlling** the editor value. Plate strongly prefer an uncontrolled model. If you attempt to replace the editor's internal state too frequently, you can break **selection**, **history**, or cause performance issues. The recommended pattern is to treat the editor as uncontrolled, but still **sync** form data on certain events.
+
+## Approach 1: Sync on `onChange`
+
+This is the most straightforward approach: each time the editor changes, update your form field's value. For small documents or infrequent changes, this is usually acceptable.
+
+### React Hook Form Example
+
+```tsx
+import { useForm } from 'react-hook-form';
+import type { Value } from 'platejs';
+import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
+
+type FormData = {
+ content: Value;
+};
+
+export function RHFEditorForm() {
+ const initialValue = [
+ { type: 'p', children: [{ text: 'Hello from react-hook-form!' }] },
+ ]
+
+ // Setup react-hook-form
+ const { register, handleSubmit, setValue } = useForm({
+ defaultValues: {
+ content: initialValue,
+ },
+ });
+
+ // Create/configure the Plate editor
+ const editor = usePlateEditor({ value: initialValue });
+
+ // Register the field for react-hook-form
+ register('content', { /* validation rules... */ });
+
+ const onSubmit = (data: FormData) => {
+ // data.content will have final editor content
+ console.info('Submitted:', data.content);
+ };
+
+ return (
+
+ {
+ // Sync editor changes to the form
+ setValue('content', value);
+ }}
+ >
+
+
+
+ Submit
+
+ );
+}
+```
+
+**Notes**:
+1. **`defaultValues.content`**: your initial editor content.
+2. **`register('content')`**: signals to RHF that the field is tracked.
+3. **`onChange({ value })`**: calls `setValue('content', value)` each time.
+
+If you expect large documents or fast typing, consider debouncing or switching to an `onBlur` approach to reduce form updates.
+
+### shadcn/ui Form Example
+
+[shadcn/ui](https://ui.shadcn.com/docs/components/form) provides a `` that integrates with react-hook-form. We'll use `` to handle the field logic:
+
+```tsx
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { useForm } from 'react-hook-form';
+import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
+
+type FormValues = {
+ content: any;
+};
+
+export function EditorForm() {
+ // 1. Create the form
+ const form = useForm({
+ defaultValues: {
+ content: [
+ { type: 'p', children: [{ text: 'Hello from shadcn/ui Form!' }] },
+ ],
+ },
+ });
+
+ // 2. Create the Plate editor
+ const editor = usePlateEditor();
+
+ const onSubmit = (data: FormValues) => {
+ console.info('Submitted data:', data.content);
+ };
+
+ return (
+
+
+ (
+
+ Content
+
+ {
+ // Sync to the form
+ field.onChange(value);
+ }}
+ >
+
+
+
+
+
+ )}
+ />
+
+ Submit
+
+
+ );
+}
+```
+
+This approach makes your editor content behave like any other field in the shadcn/ui form.
+
+## Approach 2: Sync on Blur (or Another Trigger)
+
+Instead of syncing on every keystroke, you might only need the final value when the user:
+- Leaves the editor (`onBlur`),
+- Clicks a “Save” button, or
+- Reaches certain form submission logic.
+
+```tsx
+
+ {
+ // Only sync on blur
+ setValue('content', editor.children);
+ }}
+ />
+
+```
+
+This reduces overhead but your form state won't reflect partial updates while the user is typing.
+
+## Approach 3: Controlled Replacement (Advanced)
+
+If you want the form to be the single source of truth (completely [controlled](/docs/controlled)):
+```ts
+editor.tf.setValue(formStateValue);
+```
+But this has known drawbacks:
+- Potentially breaks cursor position and undo/redo.
+- Can cause frequent full re-renders for large docs.
+
+**Recommendation**: Stick to a partially uncontrolled model if you can.
+
+## Example: Save & Reset
+
+Here's a more complete form demonstrating both saving and resetting the editor/form:
+
+```tsx
+import { useForm } from 'react-hook-form';
+import { Plate, PlateContent, usePlateEditor } from 'platejs/react';
+
+function MyForm() {
+ const form = useForm({
+ defaultValues: {
+ content: [
+ { type: 'p', children: [{ text: 'Initial content...' }] },
+ ],
+ },
+ });
+
+ const editor = usePlateEditor();
+
+ const onSubmit = (data) => {
+ alert(JSON.stringify(data, null, 2));
+ };
+
+ return (
+
+ form.setValue('content', value)}
+ >
+
+
+
+ Save
+
+ {
+ // Reset the editor
+ editor.tf.reset();
+ // Reset the form
+ form.reset();
+ }}
+ >
+ Reset
+
+
+ );
+}
+```
+
+- **`onChange`** -> updates form state.
+- **Reset** -> calls both `editor.tf.reset()` and `form.reset()` for consistency.
+
+## Migrating from a shadcn Textarea to Plate
+
+If you have a standard [TextareaForm from shadcn/ui docs](https://ui.shadcn.com/docs/components/textarea#form) and want to replace `` with a Plate editor, you can follow these steps:
+
+```tsx
+// 1. Original code (TextareaForm)
+ (
+
+ Bio
+
+
+
+
+ You can @mention other users and organizations.
+
+
+
+ )}
+/>
+```
+
+Create a new `EditorField` component:
+
+```tsx
+// EditorField.tsx
+"use client";
+
+import * as React from "react";
+import type { Value } from "platejs";
+import { Plate, PlateContent, usePlateEditor } from "platejs/react";
+
+/**
+ * A reusable editor component that works like a standard ,
+ * accepting `value`, `onChange`, and optional placeholder.
+ *
+ * Usage:
+ *
+ * (
+ *
+ * Bio
+ *
+ *
+ *
+ * Some helpful description...
+ *
+ *
+ * )}
+ * />
+ */
+export interface EditorFieldProps
+ extends React.HTMLAttributes {
+ /**
+ * The current Plate Value. Should be an array of Plate nodes.
+ */
+ value?: Value;
+
+ /**
+ * Called when the editor value changes.
+ */
+ onChange?: (value: Value) => void;
+
+ /**
+ * Placeholder text to display when editor is empty.
+ */
+ placeholder?: string;
+}
+
+export function EditorField({
+ value,
+ onChange,
+ placeholder = "Type here...",
+ ...props
+}: EditorFieldProps) {
+ // We create our editor instance with the provided initial `value`.
+ const editor = usePlateEditor({
+ value: value ?? [
+ { type: "p", children: [{ text: "" }] }, // Default empty paragraph
+ ],
+ });
+
+ return (
+ {
+ // Sync changes back to the caller via onChange prop
+ onChange?.(value);
+ }}
+ {...props}
+ >
+
+
+ );
+}
+```
+
+3. Replace the `` with a `` block:
+
+```tsx
+"use client";
+
+import { z } from "zod";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { EditorField } from "./EditorField"; // Import the component above
+
+// 1. Define our validation schema with zod
+const FormSchema = z.object({
+ bio: z
+ .string()
+ .min(10, { message: "Bio must be at least 10 characters." })
+ .max(160, { message: "Bio must not exceed 160 characters." }),
+});
+
+// 2. Build our main form component
+export function EditorForm() {
+ // 3. Setup the form
+ const form = useForm>({
+ resolver: zodResolver(FormSchema),
+ defaultValues: {
+ // Here "bio" is just a string, but our editor
+ // will interpret it as initial content if you parse it as JSON
+ bio: "",
+ },
+ });
+
+ // 4. Submission handler
+ function onSubmit(data: z.infer) {
+ alert("Submitted: " + JSON.stringify(data, null, 2));
+ }
+
+ return (
+
+
+ (
+
+ Bio
+
+
+
+
+ You can @mention other users and organizations.
+
+
+
+ )}
+ />
+
+ Submit
+
+
+
+ );
+}
+```
+
+- Any existing form validations or error messages remain the same.
+- For default values, ensure `form.defaultValues.bio` is a valid Plate value (array of nodes) instead of a string.
+- For controlled values, use `editor.tf.setValue(formStateValue)` with moderation.
+
+## Best Practices
+
+1. **Use an Uncontrolled Editor**: Let Plate manage its own state, updating the form only when necessary.
+2. **Minimize Replacements**: Avoid calling `editor.tf.setValue` too often; it can break selection, history, or degrade performance.
+3. **Validate at the Right Time**: Decide if you need instant validation (typing) or upon blur/submit.
+4. **Reset Both**: If you reset the form, call `editor.tf.reset()` to keep them in sync.
diff --git a/apps/www/content/docs/(guides)/playwright.cn.mdx b/apps/www/content/docs/(guides)/playwright.cn.mdx
new file mode 100644
index 0000000000..22e26403bb
--- /dev/null
+++ b/apps/www/content/docs/(guides)/playwright.cn.mdx
@@ -0,0 +1,296 @@
+---
+title: Playwright 测试
+description: 学习如何编写与 Plate 集成的 Playwright 测试。
+---
+
+[Playwright](https://playwright.dev/) 支持在无头浏览器中进行端到端测试。本指南介绍如何使用 `@platejs/playwright` 将 Playwright 与 Plate 集成。
+
+## 设置
+
+
+
+### 安装依赖
+
+按照 [Playwright 指南](https://playwright.dev/docs/intro) 在您的应用中安装 Playwright,并确保您可以编写基本的端到端测试。
+
+```bash
+npm install @platejs/playwright playwright
+```
+
+### 添加 PlaywrightPlugin
+
+为了让您的 Playwright 测试能够访问和操作编辑器,您需要在编辑器中添加 `PlaywrightPlugin`:
+
+```tsx
+const editor = createPlateEditor({
+ plugins: [
+ // 其他插件...
+ PlaywrightPlugin.configure({ enabled: process.env.NODE_ENV !== 'production' }),
+ ]
+})
+```
+
+这会在 `window.platePlaywrightAdapter` 上暴露各种工具函数。
+
+### 获取编辑器句柄
+
+
+ 大多数 Playwright 测试代码在非浏览器环境中运行。与 Plate 编辑器交互需要使用 Playwright 的 `evaluate` 和 `evaluateHandle` [API](https://playwright.dev/docs/evaluating) 在浏览器上下文中运行 JavaScript。
+
+ [句柄](https://playwright.dev/docs/handles) 引用浏览器中的 JavaScript 对象。编辑器句柄指的是您的 Plate 编辑器的 `editor` 实例(`JSHandle`)。
+
+
+在您的 Playwright 测试中,在与 Plate 交互之前获取编辑器句柄:
+
+```ts
+const editorHandle = await getEditorHandle(page);
+```
+
+对于多个编辑器,指定可编辑元素:
+
+```ts
+const editable = getEditable(page.getByTestId('my-editor-container'));
+const editorHandle = await getEditorHandle(page, editable);
+```
+
+定位器必须精确匹配一个 `[data-slate-editor]` 元素。
+
+### 开始编写测试
+
+有了 `editorHandle`,您现在可以开始为编辑器编写 Playwright 测试了。
+
+
+
+## 示例
+
+### 通过路径获取节点句柄
+
+使用 `getNodeByPath` 获取引用特定路径节点的句柄。要断言节点的值,使用 `.jsonValue()` 将其转换为 JSON。
+
+```ts
+const nodeHandle = await getNodeByPath(page, editorHandle, [0]);
+
+expect(await nodeHandle.jsonValue()).toBe({
+ type: 'p',
+ children: [{ text: '我的段落' }],
+});
+```
+
+### 获取节点的类型
+
+```ts
+const firstNodeType = await getTypeAtPath(page, editorHandle, [0]);
+expect(firstNodeType).toBe('h1');
+```
+
+### 获取节点的 DOM 节点
+
+在 Playwright 中,您经常需要引用特定的 DOM 元素以断言其状态或执行涉及它的操作。
+
+`getDOMNodeByPath` 返回与给定路径的 Plate 节点对应的 DOM 节点的 [ElementHandle](https://playwright.dev/docs/api/class-elementhandle)。
+
+```ts
+const firstNodeEl = await getDOMNodeByPath(page, elementHandle, [0]);
+await firstNodeEl.hover();
+```
+
+### 点击节点
+
+```ts
+await clickAtPath(page, elementHandle, [0]);
+```
+
+### 获取选区
+
+```ts
+const selection = await getSelection(page, editorHandle);
+
+expect(selection).toBe({
+ anchor: { path: [0, 0], offset: 0 },
+ focus: { path: [0, 0], offset: 7 },
+});
+```
+
+### 选择点或范围
+
+要在编辑器中的特定位置输入,您需要使用 `setSelection` 选择该点。
+
+如果您选择单个点(由 `path` 和 `offset` 组成),光标将放置在该点。如果您选择一个范围(由 `anchor` 和 `focus` 组成),将选中该范围。如果您选择一个路径,将选中该路径处的整个节点。
+
+在设置选区之前,请确保聚焦编辑器。在 WebKit 中,使用 `editable.focus()` 聚焦编辑器可能无法正常工作,因此最好的方法是使用 `clickAtPath`。
+
+```ts
+// 点击第一个段落以聚焦编辑器
+await clickAtPath(page, editorHandle, [0]);
+
+await setSelection(page, editorHandle, {
+ path: [0, 0],
+ offset: 2,
+});
+
+await page.keyboard.type('你好,世界!');
+```
+
+## 导入的查询和转换
+
+您可能想要将查询或转换(如 `getBlockAbove` 或 `insertNodes`)导入到 Playwright 测试中使用。
+
+不幸的是,这是不可能的。您只能在浏览器上下文中直接与 `editor` 实例交互(使用 `evaluate` 或 `evaluateHandle`),并且无法将导入的函数从 Playwright 的作用域传递到浏览器中。这是因为 `editor` 对象和 JavaScript 函数都无法充分序列化。
+
+最好的解决方案是以用户相同的方式与编辑器交互,而不使用任何导入的查询或转换。这将使您的 Playwright 测试更有可能捕获应用程序中的错误。
+
+如果这不可行,您可以在 `evaluate` 或 `evaluateHandle` 中调用 `editor` 对象的方法。(如果需要从浏览器返回对 DOM 节点或 JavaScript 对象的引用,请使用 `evaluateHandle`。如果需要返回 JavaScript 对象的序列化副本,或者不需要返回任何值,请使用 `evaluate`。)
+
+请注意,虽然这些查询和转换不能直接在 Playwright 测试中使用,但在应用程序代码中使用编辑器实例时它们是可用的。有关如何在应用程序中使用这些方法的更多信息,请参阅[编辑器方法](/docs/editor-methods)文档。
+
+有关 `evaluate` 和 `evaluateHandle` 的更多信息,请参阅 [Playwright 文档](https://playwright.dev/docs/evaluating)。
+
+```ts
+await editorHandle.evaluate((editor) => {
+ editor.tf.insertNodes(/* ... */);
+});
+```
+
+有关 `evaluate` 和 `evaluateHandle` 的更多信息,请参阅 [Playwright 文档](https://playwright.dev/docs/evaluating)。
+
+## API
+
+### `getEditorHandle`
+
+获取 Plate 编辑器实例的句柄。
+
+
+
+
+ Playwright 页面对象。
+
+
+ 可编辑元素的定位器。默认为第一个 [data-slate-editor]。
+
+
+
+
+ Plate 编辑器实例的句柄。
+
+
+
+### `getNodeByPath`
+
+获取指定路径的节点。
+
+
+
+
+ Playwright 页面对象。
+
+
+ 编辑器实例的句柄。
+
+
+ 节点的路径。
+
+
+
+
+ 路径处节点的句柄。
+
+
+
+### `getDOMNodeByPath`
+
+获取给定路径的 Plate 节点对应的 DOM 节点。
+
+
+
+
+ Playwright 页面对象。
+
+
+ 编辑器实例的句柄。
+
+
+ 节点的路径。
+
+
+
+
+ 对应 DOM 节点的 ElementHandle。
+
+
+
+### `clickAtPath`
+
+模拟点击指定路径的节点。
+
+
+
+
+ Playwright 页面对象。
+
+
+ 编辑器实例的句柄。
+
+
+ 要点击的节点的路径。
+
+
+
+
+### `getSelection`
+
+获取当前编辑器选区。
+
+
+
+
+ Playwright 页面对象。
+
+
+ 编辑器实例的句柄。
+
+
+
+
+ 当前编辑器选区。
+
+
+
+### `setSelection`
+
+将编辑器选区设置为指定范围。
+
+
+
+
+ Playwright 页面对象。
+
+
+ 编辑器实例的句柄。
+
+
+ 要设置选区的位置。
+
+
+
+
+### `getTypeAtPath`
+
+获取指定路径处节点的类型。
+
+
+
+
+ Playwright 页面对象。
+
+
+ 编辑器实例的句柄。
+
+
+ 节点的路径。
+
+
+
+
+ 路径处节点的类型。
+
+
diff --git a/apps/www/content/docs/(guides)/playwright.mdx b/apps/www/content/docs/(guides)/playwright.mdx
new file mode 100644
index 0000000000..44a546cb2a
--- /dev/null
+++ b/apps/www/content/docs/(guides)/playwright.mdx
@@ -0,0 +1,296 @@
+---
+title: Playwright Testing
+description: Learn how to write Playwright tests that integrate with Plate.
+---
+
+[Playwright](https://playwright.dev/) enables end-to-end testing in headless browsers. This guide covers integrating Playwright with Plate using `@platejs/playwright`.
+
+## Setup
+
+
+
+### Install Dependencies
+
+Follow [Playwright's guide](https://playwright.dev/docs/intro) to install Playwright in your app and ensure that you can write basic end-to-end tests.
+
+```bash
+npm install @platejs/playwright playwright
+```
+
+### Add PlaywrightPlugin
+
+In order for your Playwright tests to access and interact with the editor, you'll need to add `PlaywrightPlugin` to your editor:
+
+```tsx
+const editor = createPlateEditor({
+ plugins: [
+ // other plugins...
+ PlaywrightPlugin.configure({ enabled: process.env.NODE_ENV !== 'production' }),
+ ]
+})
+```
+
+This exposes various utilities on `window.platePlaywrightAdapter`.
+
+### Get Editor Handle
+
+
+ Most Playwright test code runs in a non-browser environment. Interacting with a Plate editor requires running JavaScript inside the browser context using Playwright's `evaluate` and `evaluateHandle` [APIs](https://playwright.dev/docs/evaluating).
+
+ A [handle](https://playwright.dev/docs/handles) references a JavaScript object within the browser. The editor handle refers to the `editor` instance of your Plate editor (`JSHandle`).
+
+
+In your Playwright test, get the editor handle before interacting with Plate:
+
+```ts
+const editorHandle = await getEditorHandle(page);
+```
+
+For multiple editors, specify the editable element:
+
+```ts
+const editable = getEditable(page.getByTestId('my-editor-container'));
+const editorHandle = await getEditorHandle(page, editable);
+```
+
+The locator must match exactly one `[data-slate-editor]` element.
+
+### Start Writing Tests
+
+With the `editorHandle`, you can now write Playwright tests for your editor.
+
+
+
+## Examples
+
+### Get a node handle by its path
+
+Use `getNodeByPath` to get a handle referencing the node at a specific path. To make assertions about the value of the node, convert it to JSON using `.jsonValue()`.
+
+```ts
+const nodeHandle = await getNodeByPath(page, editorHandle, [0]);
+
+expect(await nodeHandle.jsonValue()).toBe({
+ type: 'p',
+ children: [{ text: 'My paragraph' }],
+});
+```
+
+### Get the type of a node
+
+```ts
+const firstNodeType = await getTypeAtPath(page, editorHandle, [0]);
+expect(firstNodeType).toBe('h1');
+```
+
+### Get the DOM node for a node
+
+Often in Playwright, you'll want to reference a specific DOM element in order to make assertions about its state or perform operations involving it.
+
+`getDOMNodeByPath` returns an [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) for the DOM node corresponding to the Plate node at a given path.
+
+```ts
+const firstNodeEl = await getDOMNodeByPath(page, elementHandle, [0]);
+await firstNodeEl.hover();
+```
+
+### Click a node
+
+```ts
+await clickAtPath(page, elementHandle, [0]);
+```
+
+### Get the selection
+
+```ts
+const selection = await getSelection(page, editorHandle);
+
+expect(selection).toBe({
+ anchor: { path: [0, 0], offset: 0 },
+ focus: { path: [0, 0], offset: 7 },
+});
+```
+
+### Select a point or range
+
+In order to type at a specific point in the editor, you'll need to select that point using `setSelection`.
+
+If you select a single point (consisting of a `path` and an `offset`), the cursor will be placed at that point. If you select a range (consisting of an `anchor` and a `focus`), that range will be selected. If you select a path, the entire node at that path will be selected.
+
+Make sure you focus the editor before setting the selection. Focusing the editor using `editable.focus()` may not work correctly in WebKit, so the best way of doing this is with `clickAtPath`.
+
+```ts
+// Click the first paragraph to focus the editor
+await clickAtPath(page, editorHandle, [0]);
+
+await setSelection(page, editorHandle, {
+ path: [0, 0],
+ offset: 2,
+});
+
+await page.keyboard.type('Hello world!');
+```
+
+## Imported queries and transforms
+
+You may want to import a query or a transform such as `getBlockAbove` or `insertNodes` into your Playwright test and use it.
+
+Unfortunately, this is not possible. You can only interact directly with the `editor` instance inside the browser context (using `evaluate` or `evaluateHandle`), and it isn't possible to pass imported functions from Playwright's scope into the browser. This is because neither the `editor` object nor JavaScript functions can be adequately serialized.
+
+The best workaround is to interact with the editor in the same way that a user would, without using any imported queries or transforms. This will make your Playwright tests more likely to catch bugs in your application.
+
+If this isn't practical, you can instead call a method on the `editor` object inside an `evaluate` or `evaluateHandle`. (Use `evaluateHandle` if you need to return a reference to a DOM node or a JavaScript object from the browser. Use `evaluate` if you need to return a serialized copy of a JavaScript object, or if you don't need to return any value.)
+
+Note that while these queries and transforms can't be directly used in Playwright tests, they are available when working with the editor instance in your application code. For more information on how to use these methods in your application, refer to the [Editor Methods](/docs/editor-methods) documentation.
+
+See [Playwright's docs](https://playwright.dev/docs/evaluating) for more information about `evaluate` and `evaluateHandle`.
+
+```ts
+await editorHandle.evaluate((editor) => {
+ editor.tf.insertNodes(/* ... */);
+});
+```
+
+See [Playwright's docs](https://playwright.dev/docs/evaluating) for more about `evaluate` and `evaluateHandle`.
+
+## API
+
+### `getEditorHandle`
+
+Gets a handle to the Plate editor instance.
+
+
+
+
+ Playwright page object.
+
+
+ Locator for editable element. Defaults to first [data-slate-editor].
+
+
+
+
+ Handle to Plate editor instance.
+
+
+
+### `getNodeByPath`
+
+Retrieves a node at the specified path.
+
+
+
+
+ Playwright page object.
+
+
+ Handle to editor instance.
+
+
+ Path to node.
+
+
+
+
+ Handle to node at path.
+
+
+
+### `getDOMNodeByPath`
+
+Gets the DOM node for a Plate node at the given path.
+
+
+
+
+ Playwright page object.
+
+
+ Handle to editor instance.
+
+
+ Path to node.
+
+
+
+
+ ElementHandle for corresponding DOM node.
+
+
+
+### `clickAtPath`
+
+Simulates a click on the node at the specified path.
+
+
+
+
+ Playwright page object.
+
+
+ Handle to editor instance.
+
+
+ Path to node to click.
+
+
+
+
+### `getSelection`
+
+Retrieves the current editor selection.
+
+
+
+
+ Playwright page object.
+
+
+ Handle to editor instance.
+
+
+
+
+ Current editor selection.
+
+
+
+### `setSelection`
+
+Sets the editor selection to the specified range.
+
+
+
+
+ Playwright page object.
+
+
+ Handle to editor instance.
+
+
+ Location to set selection.
+
+
+
+
+### `getTypeAtPath`
+
+Gets the type of the node at the specified path.
+
+
+
+
+ Playwright page object.
+
+
+ Handle to editor instance.
+
+
+ Path to node.
+
+
+
+
+ Type of node at path.
+
+
diff --git a/apps/www/content/docs/(guides)/plugin-components.cn.mdx b/apps/www/content/docs/(guides)/plugin-components.cn.mdx
new file mode 100644
index 0000000000..d5192cb8c5
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-components.cn.mdx
@@ -0,0 +1,125 @@
+---
+title: 插件组件
+description: 学习如何为Plate插件创建和样式化自定义组件。
+---
+
+默认情况下,Plate插件是无头(headless)的,意味着所有节点都将以纯文本形式渲染。本指南将展示如何为编辑器创建和样式化自定义组件。
+
+## Plate UI
+
+除非您希望从零开始构建所有内容,否则我们建议使用[Plate UI](/docs/components)作为起点。Plate UI是一组可直接复制到您的应用中并根据需求修改的组件集合。
+
+无论您使用Plate UI还是从零开始构建自己的组件,创建和注册组件的过程都是相似的。
+
+## 定义组件
+
+使用`PlateElement`处理元素节点(如段落、标题),使用`PlateLeaf`处理文本叶子节点(如粗体、斜体)。这些组件负责将必要的Plate属性应用到您的自定义HTML元素上。
+
+确保无条件渲染`children`属性,即使对于void节点,这也是编辑器正常工作的必要条件。
+
+### 元素组件
+
+```tsx
+import { type PlateElementProps, PlateElement } from 'platejs/react';
+
+export function BlockquoteElement(props: PlateElementProps) {
+ // props包含attributes, children, element, editor等属性
+ // 以及您的插件可能传递的任何自定义属性
+ return (
+
+ );
+}
+```
+
+此示例定义了一个`BlockquoteElement`。`PlateElement`上的`as`属性指定它应渲染为HTML ``。`PlateElement`负责处理通过`{...props}`传递的`children`渲染。
+
+### 叶子组件
+
+```tsx
+import { type PlateLeafProps, PlateLeaf } from 'platejs/react';
+
+export function CodeLeaf(props: PlateLeafProps) {
+ // props包含attributes, children, leaf, text, editor等属性
+ // 以及您的插件可能传递的任何自定义属性
+ return (
+
+ );
+}
+```
+
+### 样式设计
+
+我们推荐使用Tailwind CSS来样式化组件,如Plate UI中所示。
+
+或者,Plate会生成类似`slate-<节点类型>`的类名(例如段落为`slate-p`,H1标题为`slate-h1`),您可以通过全局CSS定位这些类名:
+
+```css
+.slate-p {
+ margin-bottom: 1rem;
+}
+.slate-bold {
+ font-weight: bold;
+}
+```
+
+## 注册组件
+
+要使用您的自定义组件,需要将它们注册到相应的插件或直接注册到编辑器配置中。
+
+### 方法1:插件的`withComponent`(推荐)
+
+`withComponent`方法是关联组件与插件最直接的方式。
+
+```tsx
+const plugins = [
+ // 这等同于:
+ // ParagraphPlugin.configure({ node: { component: MyParagraphElement }});
+ ParagraphPlugin.withComponent(MyParagraphElement),
+ CodeBlockPlugin.withComponent(MyCodeBlockElement),
+ CodeLinePlugin.withComponent(MyCodeLineElement),
+ CodeSyntaxPlugin.withComponent(MyCodeSyntaxLeaf),
+]
+```
+
+### 方法2:插件的`override.components`
+
+对于管理多个组件部分的插件(如`CodeBlockPlugin`包含`code_block`、`code_line`和`code_syntax`),或者当您需要为特定插件实例覆盖组件时,在`configure`中使用`override.components`选项。
+
+```tsx
+const plugins = [
+ CodeBlockPlugin.configure({
+ override: {
+ components: {
+ [CodeBlockPlugin.key]: MyCodeBlockElement,
+ [CodeLinePlugin.key]: MyCodeLineElement,
+ [CodeSyntaxPlugin.key]: MyCodeSyntaxLeaf,
+ },
+ },
+ }),
+];
+```
+
+### 方法3:编辑器的`components`选项
+
+您可以在`createPlateEditor`(或`usePlateEditor`)中全局映射插件键到组件。这对于在一个地方管理所有组件,或由多个插件组成的插件特别有用。
+
+```tsx
+const editor = createPlateEditor({
+ plugins: [ParagraphPlugin, CodeBlockPlugin /* ...其他插件 */],
+ components: {
+ [ParagraphPlugin.key]: MyParagraphElement,
+ [CodeBlockPlugin.key]: MyCodeBlockElement,
+ [CodeLinePlugin.key]: MyCodeLineElement,
+ [CodeSyntaxPlugin.key]: MyCodeSyntaxLeaf,
+ // ...其他组件覆盖
+ },
+});
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/plugin-components.mdx b/apps/www/content/docs/(guides)/plugin-components.mdx
new file mode 100644
index 0000000000..1c33ffbaa7
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-components.mdx
@@ -0,0 +1,126 @@
+---
+title: Plugin Components
+description: Learn how to create and style custom components for Plate plugins.
+---
+
+By default, Plate plugins are headless, meaning all nodes will be rendered as plain text. This guide will show you how to create and style custom components for your editor.
+
+## Plate UI
+
+Unless you prefer to build everything from scratch, we recommend using [Plate UI](/docs/components) to get started. Plate UI is a collection of components that you can copy into your app and modify to suit your needs.
+
+The process of creating and registering components is similar whether you use Plate UI or build your own from scratch.
+
+## Defining Components
+
+Use `PlateElement` for element nodes (like paragraphs, headings) and `PlateLeaf` for text leaf nodes (like bold, italic). These components handle applying the necessary Plate props to your custom HTML elements.
+
+Ensure the `children` prop is rendered unconditionally for the editor to function correctly, even for void nodes.
+
+### Element Component
+
+```tsx
+import { type PlateElementProps, PlateElement } from 'platejs/react';
+
+export function BlockquoteElement(props: PlateElementProps) {
+ // props contains attributes, children, element, editor, etc.
+ // plus any custom props your plugin might pass.
+ return (
+
+ );
+}
+```
+
+This example defines a `BlockquoteElement`. The `as` prop on `PlateElement` specifies that it should render an HTML ``. `PlateElement` handles rendering the `children` passed via `{...props}`.
+
+### Leaf Component
+
+```tsx
+import { type PlateLeafProps, PlateLeaf } from 'platejs/react';
+
+export function CodeLeaf(props: PlateLeafProps) {
+ // props contains attributes, children, leaf, text, editor, etc.
+ // plus any custom props your plugin might pass.
+ return (
+
+ );
+}
+```
+
+### Styling
+
+We recommend styling components using Tailwind CSS, as demonstrated in Plate UI.
+
+Alternatively, Plate generates class names like `slate-` (e.g., `slate-p` for paragraphs, `slate-h1` for H1 headings) which you can target with global CSS:
+
+```css
+.slate-p {
+ margin-bottom: 1rem;
+}
+.slate-bold {
+ font-weight: bold;
+}
+```
+
+## Register Components
+
+To use your custom components, register them with the corresponding plugin or directly in the editor configuration.
+
+### Method 1: Plugin's `withComponent` (Recommended)
+
+The `withComponent` method is the most straightforward way to associate a component with a plugin.
+
+```tsx
+const plugins = [
+ // This is equivalent to:
+ // ParagraphPlugin.configure({ node: { component: MyParagraphElement }});
+ ParagraphPlugin.withComponent(MyParagraphElement),
+ CodeBlockPlugin.withComponent(MyCodeBlockElement),
+ CodeLinePlugin.withComponent(MyCodeLineElement),
+ CodeSyntaxPlugin.withComponent(MyCodeSyntaxLeaf),
+]
+```
+
+
+### Method 2: Plugin `override.components`
+
+For plugins that manage multiple component parts (like `CodeBlockPlugin` with `code_block`, `code_line`, and `code_syntax`), or when you need to override components for a specific plugin instance, use the `override.components` option within `configure`.
+
+```tsx
+const plugins = [
+ CodeBlockPlugin.configure({
+ override: {
+ components: {
+ [CodeBlockPlugin.key]: MyCodeBlockElement,
+ [CodeLinePlugin.key]: MyCodeLineElement,
+ [CodeSyntaxPlugin.key]: MyCodeSyntaxLeaf,
+ },
+ },
+ }),
+];
+```
+
+### Method 3: Editor `components` Option
+
+You can globally map plugin keys to components in `createPlateEditor` (or `usePlateEditor`). This is useful for managing all components in one place, or for plugins composed of multiple plugins.
+
+```tsx
+const editor = createPlateEditor({
+ plugins: [ParagraphPlugin, CodeBlockPlugin /* ...other plugins */],
+ components: {
+ [ParagraphPlugin.key]: MyParagraphElement,
+ [CodeBlockPlugin.key]: MyCodeBlockElement,
+ [CodeLinePlugin.key]: MyCodeLineElement,
+ [CodeSyntaxPlugin.key]: MyCodeSyntaxLeaf,
+ // ...other component overrides
+ },
+});
+```
diff --git a/apps/www/content/docs/(guides)/plugin-context.cn.mdx b/apps/www/content/docs/(guides)/plugin-context.cn.mdx
new file mode 100644
index 0000000000..b145322953
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-context.cn.mdx
@@ -0,0 +1,168 @@
+---
+title: 插件上下文
+description: 理解并利用 Plate 插件中的插件上下文。
+---
+
+插件上下文是一个在所有插件方法中都可用的对象,提供了对编辑器实例、插件配置和实用函数的访问。
+
+## 访问插件上下文
+
+### 插件方法
+
+插件上下文作为第一个参数在所有插件方法中可用:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ handlers: {
+ onKeyDown: (ctx) => {
+ // ctx 就是插件上下文
+ console.info(ctx.editor, ctx.plugin);
+ },
+ },
+});
+```
+
+### `getEditorPlugin`
+
+当您需要访问其他插件的上下文时,这个函数特别有用。它支持跨插件通信和交互,实现更复杂和相互关联的插件行为。
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ handlers: {
+ onKeyDown: ({ editor }) => {
+ const linkCtx = getEditorPlugin(LinkPlugin);
+ },
+ },
+});
+```
+
+### `useEditorPlugin`
+
+在 React 组件中,您可以使用 `useEditorPlugin` 钩子来访问插件上下文:
+
+```ts
+const MyComponent = () => {
+ const { editor, plugin, type } = useEditorPlugin(MyPlugin);
+
+ return {type}
;
+};
+```
+
+## 插件上下文属性
+
+### `editor`
+
+当前的 `PlateEditor` 实例:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ handlers: {
+ onChange: ({ editor }) => {
+ console.info('编辑器内容:', editor.children);
+ },
+ },
+});
+```
+
+### `plugin`
+
+当前插件的配置:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ handlers: {
+ onKeyDown: ({ plugin }) => {
+ console.info('插件键名:', plugin.key);
+ },
+ },
+});
+```
+
+### `getOption`
+
+获取插件特定选项值的函数:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: { myOption: 'default' },
+ handlers: {
+ onClick: ({ getOption }) => {
+ const myOption = getOption('myOption');
+ console.info('选项值:', myOption);
+ },
+ },
+});
+```
+
+### `getOptions`
+
+获取插件所有选项的函数:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: { option1: 'value1', option2: 'value2' },
+ handlers: {
+ onClick: ({ getOptions }) => {
+ const options = getOptions();
+ console.info('所有选项:', options);
+ },
+ },
+});
+```
+
+### `setOption`
+
+设置插件特定选项值的函数:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: { count: 0 },
+ handlers: {
+ onClick: ({ setOption }) => {
+ setOption('count', 1);
+ },
+ },
+});
+```
+
+### `setOptions`
+
+设置插件多个选项的函数:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: { option1: 'value1', option2: 'value2' },
+ handlers: {
+ onClick: ({ setOptions }) => {
+ setOptions({
+ option1: 'newValue1',
+ option2: 'newValue2',
+ });
+ },
+ },
+});
+```
+
+### `type`
+
+与插件关联的节点类型:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ node: { type: 'myNodeType' },
+ handlers: {
+ onKeyDown: ({ type }) => {
+ console.info('节点类型:', type);
+ },
+ },
+});
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/plugin-context.mdx b/apps/www/content/docs/(guides)/plugin-context.mdx
new file mode 100644
index 0000000000..66676b8317
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-context.mdx
@@ -0,0 +1,168 @@
+---
+title: Plugin Context
+description: Understanding and utilizing the Plugin Context in Plate plugins.
+---
+
+The Plugin Context is an object available in all plugin methods, providing access to the editor instance, plugin configuration, and utility functions.
+
+## Accessing Plugin Context
+
+### Plugin Methods
+
+The Plugin Context is available as the first parameter in all plugin methods:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ handlers: {
+ onKeyDown: (ctx) => {
+ // ctx is the Plugin Context
+ console.info(ctx.editor, ctx.plugin);
+ },
+ },
+});
+```
+
+### `getEditorPlugin`
+
+This function is particularly useful when you need to access the context of another plugin. It allows for cross-plugin communication and interaction, enabling more complex and interconnected plugin behaviors.
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ handlers: {
+ onKeyDown: ({ editor }) => {
+ const linkCtx = getEditorPlugin(LinkPlugin);
+ },
+ },
+});
+```
+
+### `useEditorPlugin`
+
+In React components, you can use the `useEditorPlugin` hook to access the Plugin Context:
+
+```ts
+const MyComponent = () => {
+ const { editor, plugin, type } = useEditorPlugin(MyPlugin);
+
+ return {type}
;
+};
+```
+
+## Plugin Context Properties
+
+### `editor`
+
+The current `PlateEditor` instance:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ handlers: {
+ onChange: ({ editor }) => {
+ console.info('Editor value:', editor.children);
+ },
+ },
+});
+```
+
+### `plugin`
+
+The current plugin configuration:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ handlers: {
+ onKeyDown: ({ plugin }) => {
+ console.info('Plugin key:', plugin.key);
+ },
+ },
+});
+```
+
+### `getOption`
+
+A function to get a specific option value for the plugin:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: { myOption: 'default' },
+ handlers: {
+ onClick: ({ getOption }) => {
+ const myOption = getOption('myOption');
+ console.info('My option value:', myOption);
+ },
+ },
+});
+```
+
+### `getOptions`
+
+A function to get all options for the plugin:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: { option1: 'value1', option2: 'value2' },
+ handlers: {
+ onClick: ({ getOptions }) => {
+ const options = getOptions();
+ console.info('All options:', options);
+ },
+ },
+});
+```
+
+### `setOption`
+
+A function to set a specific option value for the plugin:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: { count: 0 },
+ handlers: {
+ onClick: ({ setOption }) => {
+ setOption('count', 1);
+ },
+ },
+});
+```
+
+### `setOptions`
+
+A function to set multiple options for the plugin:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: { option1: 'value1', option2: 'value2' },
+ handlers: {
+ onClick: ({ setOptions }) => {
+ setOptions({
+ option1: 'newValue1',
+ option2: 'newValue2',
+ });
+ },
+ },
+});
+```
+
+### `type`
+
+The node type associated with the plugin:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ node: { type: 'myNodeType' },
+ handlers: {
+ onKeyDown: ({ type }) => {
+ console.info('Node type:', type);
+ },
+ },
+});
+```
diff --git a/apps/www/content/docs/(guides)/plugin-methods.cn.mdx b/apps/www/content/docs/(guides)/plugin-methods.cn.mdx
new file mode 100644
index 0000000000..c50b5a55c4
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-methods.cn.mdx
@@ -0,0 +1,329 @@
+---
+title: 插件方法
+description: 探索可用于扩展Plate插件的各种方法。
+---
+
+## 配置方法
+
+扩展插件时,默认情况下所有属性都会进行深度合并,但有两个例外:数组会被完全替换,而`options`对象会进行浅合并。
+
+### .configure
+
+`.configure`方法允许你覆盖插件的配置。
+
+```ts
+const ConfiguredPlugin = MyPlugin.configure({
+ options: {
+ myOption: 'new value',
+ },
+});
+```
+
+你也可以使用函数来访问当前配置:
+
+```ts
+const ConfiguredPlugin = MyPlugin.configure(({ getOptions }) => ({
+ options: {
+ ...getOptions(),
+ myOption: `${getOptions().myOption} + extra`,
+ },
+}));
+```
+
+- 用于修改插件的现有属性
+- 不会向插件添加新属性
+- 最后应用的配置会被编辑器使用
+- 不返回扩展类型,保持原始插件类型
+
+### .configurePlugin
+
+`.configurePlugin`方法允许你配置嵌套插件的属性:
+
+```ts
+const TablePlugin = createPlatePlugin({
+ key: 'table',
+ plugins: [TableCellPlugin],
+}).configurePlugin(TableCellPlugin, {
+ options: {
+ cellOption: 'modified',
+ },
+});
+```
+
+- 用于配置父插件中的嵌套插件
+- 与`.configure`类似,修改现有属性但不添加新属性
+- 适合在不扩展子插件类型的情况下调整其行为
+
+### .extend
+
+`.extend`方法允许你扩展插件的配置和功能。
+
+```ts
+const ExtendedPlugin = MyPlugin.extend({
+ options: {
+ newOption: 'new value',
+ },
+});
+```
+
+你也可以使用函数来访问当前配置和编辑器:
+
+```ts
+const ExtendedPlugin = MyPlugin.extend(({ editor, plugin }) => ({
+ options: {
+ newOption: 'new value',
+ },
+ handlers: {
+ onKeyDown: () => {
+ // 自定义按键逻辑
+ },
+ },
+}));
+```
+
+- 用于向插件添加新属性或修改现有属性
+- 返回具有扩展类型的新插件实例
+- 支持链式调用,允许多个扩展顺序应用
+
+### .extendPlugin
+
+`.extendPlugin`方法允许你扩展嵌套插件的配置和功能:
+
+```ts
+const TablePlugin = createPlatePlugin({
+ key: 'table',
+ plugins: [TableCellPlugin],
+}).extendPlugin(TableCellPlugin, {
+ options: {
+ newCellOption: 'added',
+ },
+ handlers: {
+ onKeyDown: () => {
+ // 表格单元格的自定义按键逻辑
+ },
+ },
+});
+```
+
+- 用于扩展父插件中的嵌套插件
+- 可以向嵌套插件添加新属性并修改现有属性
+- 返回包含扩展嵌套插件的新父插件实例
+
+### .configure与.extend的区别
+
+虽然这两种方法都可以用于修改插件配置,但存在一些关键差异:
+
+1. 链式调用:`.extend`支持链式调用,而`.configure`不支持
+2. 类型扩展:`.extend`返回具有扩展类型的新插件实例,而`.configure`保持原始类型
+3. 新属性:`.extend`可以向插件配置添加新属性,而`.configure`仅修改现有属性
+
+根据是否需要扩展插件类型和功能(使用`.extend`)或仅修改现有配置(使用`.configure`)来选择适当的方法。
+
+### .extendSelectors
+
+`extendSelectors`方法允许你向插件添加可订阅的选择器:
+
+```ts
+const CounterPlugin = createPlatePlugin({
+ key: 'counter',
+ options: {
+ count: 0,
+ },
+}).extendSelectors(({ getOptions }) => ({
+ doubleCount: () => getOptions().count * 2,
+ isEven: () => getOptions().count % 2 === 0,
+}));
+```
+
+然后你可以在组件或其他插件方法中使用这些选择器:
+
+```tsx
+const CounterComponent = () => {
+ const count = usePluginOption(CounterPlugin, 'count');
+ const doubleCount = usePluginOption(CounterPlugin, 'doubleCount');
+ const isEven = usePluginOption(CounterPlugin, 'isEven');
+
+ return (
+
+
Count: {count}
+
Double Count: {doubleCount}
+
Is Even: {isEven ? 'Yes' : 'No'}
+
+ );
+};
+```
+
+- 允许你从插件选项创建派生状态或计算值
+- 使用`getOption`读取值
+- 使用`usePluginOption`订阅值,当选项变化时重新计算,仅在结果变化时重新渲染。**这是与`.extendApi`的主要区别**
+
+### .withComponent
+
+`withComponent`方法允许你设置或替换与插件关联的组件。
+
+```ts
+const ParagraphPlugin = createPlatePlugin({
+ key: 'p',
+ node: {
+ isElement: true,
+ type: 'p',
+ },
+}).withComponent(ParagraphElement);
+```
+
+## API和转换方法
+
+插件可以定义自己的API方法和转换方法,这些方法将被合并到编辑器的API和转换中。这是通过`extendApi`、`extendEditorApi`、`extendTransforms`和`extendEditorTransforms`方法实现的。
+
+### .extendApi
+
+使用`extendApi`添加插件特定的API方法:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).extendApi(() => ({
+ pluginMethod: () => 'plugin method result',
+}));
+
+// 访问插件的API
+editor.api.myPlugin.api.pluginMethod();
+```
+
+### .extendEditorApi
+
+使用`extendEditorApi`添加根级API方法:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).extendEditorApi(({ getOptions }) => ({
+ editorMethod: () => getOptions().someOption,
+}));
+
+// 访问插件的API
+editor.api.editorMethod();
+```
+
+### .extendTransforms
+
+使用`extendTransforms`添加插件特定的转换方法:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).extendTransforms(() => ({
+ pluginTransform: () => {
+ // 自定义转换逻辑
+ },
+}));
+
+// 访问插件的转换方法
+editor.tf.myPlugin.pluginTransform();
+
+// 注意:`editor.tf`是`editor.transforms`的简写
+editor.transforms.myPlugin.pluginTransform();
+```
+
+### .extendEditorTransforms
+
+使用`extendEditorTransforms`添加根级转换方法:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).extendEditorTransforms(({ editor }) => ({
+ editorTransform: () => {
+ // 自定义编辑器转换逻辑
+ },
+}));
+
+// 访问插件的转换方法
+editor.tf.editorTransform();
+```
+
+### .overrideEditor
+
+`overrideEditor`方法专门用于在不改变插件类型的情况下覆盖现有的编辑器方法:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).overrideEditor(({ editor, tf: { insertText }, api: { isInline } }) => ({
+ transforms: {
+ insertText(text, options) {
+ // 覆盖insertText行为
+ insertText(text, options);
+ },
+ },
+ api: {
+ isInline(element) {
+ // 覆盖isInline行为
+ return isInline(element);
+ },
+ },
+}));
+```
+
+- 专门用于覆盖现有的编辑器方法
+- 返回包装在`transforms`或`api`对象中的重写方法
+- 不能添加新方法(使用`extendEditorTransforms`或`extendEditorApi`替代)
+- 通过上下文提供对原始方法的访问
+
+### API与转换方法的区别
+
+虽然目前Plate中API和转换方法在核心上没有区别,但它们服务于不同的目的,并为未来的可扩展性而设计:
+
+- **转换方法:**
+ - 存储所有Slate转换和编辑器操作。结构设计为未来可能支持中间件,允许更复杂的转换管道和开发工具。
+ - 通常用于修改编辑器状态的操作,如插入、删除或转换内容。
+ - 示例:`editor.tf.toggleBlock()`、`editor.tf.toggleMark('bold')`
+
+- **API方法:**
+ - 存储所有查询、实用函数和其他不直接修改编辑器状态的方法。
+ - 示例:`editor.api.save()`、`editor.api.debug.log()`
+
+### 链式扩展
+
+你可以链式调用这些方法来创建全面的插件:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: {
+ baseValue: 5,
+ },
+})
+ .extendApi(() => ({
+ pluginMethod: () => 'plugin method',
+ }))
+ .extendEditorApi(({ getOptions }) => ({
+ multiply: (factor: number) => getOptions().baseValue * factor,
+ }))
+ .extendTransforms(() => ({
+ pluginTransform: () => {
+ // 插件特定的转换
+ },
+ }))
+ .extendEditorTransforms(({ editor }) => ({
+ editorTransform: () => {
+ // 编辑器特定的转换
+ },
+ }));
+
+editor.api.myPlugin.api.pluginMethod();
+editor.api.multiply(3);
+editor.tf.myPlugin.pluginTransform();
+editor.tf.editorTransform();
+```
+
+## 将Slate插件转换为Plate插件
+
+要将类型化的Slate插件转换为Plate插件,可以使用`toPlatePlugin`:
+
+```ts
+const CodeBlockPlugin = toPlatePlugin(createSlatePlugin({ key: 'code_block' }), {
+ handlers: {},
+ options: { hotkey: ['mod+opt+8', 'mod+shift+8'] },
+});
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/plugin-methods.mdx b/apps/www/content/docs/(guides)/plugin-methods.mdx
new file mode 100644
index 0000000000..a21ecd2cc8
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-methods.mdx
@@ -0,0 +1,331 @@
+---
+title: Plugin Methods
+description: Explore the various methods available for extending Plate plugins.
+---
+
+## Configuration Methods
+
+When extending plugins, all properties are deeply merged by default, with two exceptions: arrays are replaced entirely, and the `options` object is shallow merged.
+
+### .configure
+
+The `.configure` method allows you to override the plugin's configuration.
+
+```ts
+const ConfiguredPlugin = MyPlugin.configure({
+ options: {
+ myOption: 'new value',
+ },
+});
+```
+
+You can also use a function to access the current configuration:
+
+```ts
+const ConfiguredPlugin = MyPlugin.configure(({ getOptions }) => ({
+ options: {
+ ...getOptions(),
+ myOption: `${getOptions().myOption} + extra`,
+ },
+}));
+```
+
+- It's used to modify existing properties of the plugin.
+- It doesn't add new properties to the plugin.
+- The last configuration applied is the one used by the editor.
+- It doesn't return an extended type, maintaining the original plugin type.
+
+### .configurePlugin
+
+The `.configurePlugin` method allows you to configure the properties of a nested plugin:
+
+```ts
+const TablePlugin = createPlatePlugin({
+ key: 'table',
+ plugins: [TableCellPlugin],
+}).configurePlugin(TableCellPlugin, {
+ options: {
+ cellOption: 'modified',
+ },
+});
+```
+
+- It's used to configure nested plugins within a parent plugin.
+- Like `.configure`, it modifies existing properties but doesn't add new ones.
+- It's useful for adjusting the behavior of sub-plugins without extending their types.
+
+
+### .extend
+
+The `.extend` method allows you to extend the plugin's configuration and functionality.
+
+```ts
+const ExtendedPlugin = MyPlugin.extend({
+ options: {
+ newOption: 'new value',
+ },
+});
+```
+
+You can also use a function to access the current configuration and editor:
+
+```ts
+const ExtendedPlugin = MyPlugin.extend(({ editor, plugin }) => ({
+ options: {
+ newOption: 'new value',
+ },
+ handlers: {
+ onKeyDown: () => {
+ // Custom key down logic
+ },
+ },
+}));
+```
+
+- It's used to add new properties or modify existing ones in the plugin.
+- It returns a new plugin instance with extended types.
+- It's chainable, allowing multiple extensions to be applied sequentially.
+
+### .extendPlugin
+
+The `.extendPlugin` method allows you to extend the configuration and functionality of a nested plugin:
+
+```ts
+const TablePlugin = createPlatePlugin({
+ key: 'table',
+ plugins: [TableCellPlugin],
+}).extendPlugin(TableCellPlugin, {
+ options: {
+ newCellOption: 'added',
+ },
+ handlers: {
+ onKeyDown: () => {
+ // Custom key down logic for table cells
+ },
+ },
+});
+```
+
+- It's used to extend nested plugins within a parent plugin.
+- It can add new properties and modify existing ones in the nested plugin.
+- It returns a new parent plugin instance with the extended nested plugin.
+
+
+### Difference between .configure and .extend
+
+While both methods can be used to modify plugin configuration, they have some key differences:
+
+1. Chaining: `.extend` is chainable, while `.configure` is not.
+2. Type extension: `.extend` returns a new plugin instance with extended types, while `.configure` maintains the original type.
+3. New properties: `.extend` can add new properties to the plugin configuration, while `.configure` only modifies existing ones.
+
+Choose the appropriate method based on whether you need to extend the plugin's type and functionality (use `.extend`) or simply modify existing configuration (use `.configure`).
+
+### .extendSelectors
+
+The `extendSelectors` method allows you to add subscribable selectors to your plugin:
+
+```ts
+const CounterPlugin = createPlatePlugin({
+ key: 'counter',
+ options: {
+ count: 0,
+ },
+}).extendSelectors(({ getOptions }) => ({
+ doubleCount: () => getOptions().count * 2,
+ isEven: () => getOptions().count % 2 === 0,
+}));
+```
+
+You can then use those selectors in your components or other plugin methods:
+
+```tsx
+const CounterComponent = () => {
+ const count = usePluginOption(CounterPlugin, 'count');
+ const doubleCount = usePluginOption(CounterPlugin, 'doubleCount');
+ const isEven = usePluginOption(CounterPlugin, 'isEven');
+
+ return (
+
+
Count: {count}
+
Double Count: {doubleCount}
+
Is Even: {isEven ? 'Yes' : 'No'}
+
+ );
+};
+```
+
+- It allows you to create derived state or computed values from plugin options.
+- Read the value using `getOption`
+- Subscribe to the value using `usePluginOption`, recomputed whenever options change, re-rendering only when the result changes. **This is the main difference with `.extendApi`**.
+
+### .withComponent
+
+The `withComponent` method allows you to set or replace the component associated with a plugin.
+
+```ts
+const ParagraphPlugin = createPlatePlugin({
+ key: 'p',
+ node: {
+ isElement: true,
+ type: 'p',
+ },
+}).withComponent(ParagraphElement);
+```
+
+## API and Transforms
+
+Plugins can define their own API methods and transforms that will be merged into the editor's API and transforms. This is done using the `extendApi`, `extendEditorApi`, `extendTransforms`, and `extendEditorTransforms` methods.
+
+### .extendApi
+
+Use `extendApi` to add plugin-specific API methods:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).extendApi(() => ({
+ pluginMethod: () => 'plugin method result',
+}));
+
+// Access the plugin's API
+editor.api.myPlugin.api.pluginMethod();
+```
+
+### .extendEditorApi
+
+Use `extendEditorApi` to add root-level API methods:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).extendEditorApi(({ getOptions }) => ({
+ editorMethod: () => getOptions().someOption,
+}));
+
+// Access the plugin's API
+editor.api.editorMethod();
+```
+
+### .extendTransforms
+
+Use `extendTransforms` to add plugin-specific transform methods:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).extendTransforms(() => ({
+ pluginTransform: () => {
+ // Custom transform logic
+ },
+}));
+
+// Access the plugin's transform
+editor.tf.myPlugin.pluginTransform();
+
+// NOTE: `editor.tf` in a short alias to `editor.transforms`
+editor.transforms.myPlugin.pluginTransform();
+```
+
+### .extendEditorTransforms
+
+Use `extendEditorTransforms` to add root-level transform methods:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).extendEditorTransforms(({ editor }) => ({
+ editorTransform: () => {
+ // Custom editor transform logic
+ },
+}));
+
+// Access the plugin's transform
+editor.tf.editorTransform();
+```
+
+### .overrideEditor
+
+The `overrideEditor` method is used specifically for overriding existing editor methods without altering the plugin's type:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+}).overrideEditor(({ editor, tf: { insertText }, api: { isInline } }) => ({
+ transforms: {
+ insertText(text, options) {
+ // Override insertText behavior
+ insertText(text, options);
+ },
+ },
+ api: {
+ isInline(element) {
+ // Override isInline behavior
+ return isInline(element);
+ },
+ },
+}));
+```
+
+- Used specifically for overriding existing editor methods
+- Returns the overridden methods wrapped in `transforms` or `api` objects
+- Cannot add new methods (use `extendEditorTransforms` or `extendEditorApi` instead)
+- Provides access to original methods through the context
+
+### Difference between API and Transforms
+
+While there is currently no core difference between API and Transforms in Plate, they serve distinct purposes and are designed with future extensibility in mind:
+
+- **Transforms:**
+ - Store all Slate transforms and editor operations here. Structured to potentially support middlewares in the future, allowing for more complex transform pipelines and devtools.
+ - Typically used for operations that modify the editor state, such as inserting, deleting, or transforming content.
+ - Example: `editor.tf.toggleBlock()`, `editor.tf.toggleMark('bold')`
+
+- **API:**
+ - Store all queries, utility functions, and other methods that don't directly modify the editor state.
+ - Example: `editor.api.save()`, `editor.api.debug.log()`
+
+### Chaining Extensions
+
+You can chain these methods to create a comprehensive plugin:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: {
+ baseValue: 5,
+ },
+})
+ .extendApi(() => ({
+ pluginMethod: () => 'plugin method',
+ }))
+ .extendEditorApi(({ getOptions }) => ({
+ multiply: (factor: number) => getOptions().baseValue * factor,
+ }))
+ .extendTransforms(() => ({
+ pluginTransform: () => {
+ // Plugin-specific transform
+ },
+ }))
+ .extendEditorTransforms(({ editor }) => ({
+ editorTransform: () => {
+ // Editor-specific transform
+ },
+ }));
+
+editor.api.myPlugin.api.pluginMethod();
+editor.api.multiply(3);
+editor.tf.myPlugin.pluginTransform();
+editor.tf.editorTransform();
+```
+
+## Convert a Slate Plugin to a Plate Plugin
+
+To convert a typed Slate plugin to a Plate plugin, you can use `toPlatePlugin`:
+
+```ts
+const CodeBlockPlugin = toPlatePlugin(createSlatePlugin({ key: 'code_block' }), {
+ handlers: {},
+ options: { hotkey: ['mod+opt+8', 'mod+shift+8'] },
+});
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/plugin-rules.cn.mdx b/apps/www/content/docs/(guides)/plugin-rules.cn.mdx
new file mode 100644
index 0000000000..5320cf8a4e
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-rules.cn.mdx
@@ -0,0 +1,852 @@
+---
+title: 插件规则
+description: 配置常见的编辑行为。
+---
+
+插件规则控制编辑器节点如何响应常见的用户操作。您可以直接在插件的 `rules` 属性上配置这些行为,而无需重写编辑器方法。
+
+本指南展示如何使用 `rules.break`、`rules.delete`、`rules.merge`、`rules.normalize`、`rules.selection` 和 `rules.match` 来创建直观的编辑体验。
+
+
+
+## 操作类型
+
+插件规则使用特定的操作名称来定义行为:
+
+- **`'default'`**: Slate 的默认行为。
+- **`'reset'`**: 将当前块重置为默认段落,保留内容。
+- **`'exit'`**: 退出当前块结构,在其后插入新段落。详见 [Exit Break](/docs/exit-break) 了解此行为的更多信息。
+- **`'deleteExit'`**: 删除内容后退出块。
+- **`'lineBreak'`**: 插入换行符 (`\n`) 而非拆分块。
+
+### `default`
+
+标准 Slate 行为。对于 `rules.break` 会拆分块,对于 `rules.delete` 会与前一个块合并。
+
+```tsx
+
+ Hello world|
+
+```
+
+按下 `Enter` 后:
+
+```tsx
+Hello world
+
+ |
+
+```
+
+按下 `Backspace` 后:
+
+```tsx
+Hello world|
+```
+
+### `reset`
+
+将当前块转换为默认段落,同时保留内容。自定义属性将被移除。
+
+```tsx
+
+ |
+
+```
+
+配置 `rules: { break: { empty: 'reset' } }` 后按下 `Enter`:
+
+```tsx
+
+ |
+
+```
+
+### `exit`
+
+通过在其后插入新段落来退出当前块结构。
+
+```tsx
+
+ |
+
+```
+
+配置 `rules: { break: { empty: 'exit' } }` 后按下 `Enter`:
+
+```tsx
+
+
+
+
+ |
+
+```
+
+### `deleteExit`
+
+删除内容后退出块。
+
+```tsx
+
+ line1
+ |
+
+```
+
+配置 `rules: { break: { emptyLineEnd: 'deleteExit' } }` 后按下 `Enter`:
+
+```tsx
+line1
+
+ |
+
+```
+
+### `lineBreak`
+
+插入软换行符 (`\n`) 而非拆分块。
+
+```tsx
+
+ Hello|
+
+```
+
+配置 `rules: { break: { default: 'lineBreak' } }` 后按下 `Enter`:
+
+```tsx
+
+ Hello
+ |
+
+```
+
+## `rules.break`
+
+控制用户在特定块类型内按下 `Enter` 时的行为。
+
+### 配置
+
+```tsx
+BlockquotePlugin.configure({
+ rules: {
+ break: {
+ // 正常按下 Enter 时的操作
+ default: 'default' | 'lineBreak' | 'exit' | 'deleteExit',
+
+ // 在空块中按下 Enter 时的操作
+ empty: 'default' | 'reset' | 'exit' | 'deleteExit',
+
+ // 在空行末尾按下 Enter 时的操作
+ emptyLineEnd: 'default' | 'exit' | 'deleteExit',
+
+ // 如果为 true,拆分后的新块将被重置
+ splitReset: boolean,
+ },
+ },
+});
+```
+
+每个属性控制特定场景:
+
+- `default`
+ - [`'default'`](#default)
+ - [`'lineBreak'`](#linebreak)
+ - [`'exit'`](#exit)
+ - [`'deleteExit'`](#deleteexit)
+
+- `empty`
+ - [`'default'`](#default)
+ - [`'reset'`](#reset)
+ - [`'exit'`](#exit)
+ - [`'deleteExit'`](#deleteexit)
+
+- `emptyLineEnd`
+ - [`'default'`](#default)
+ - [`'exit'`](#exit)
+ - [`'deleteExit'`](#deleteexit)
+
+- `splitReset`: 如果为 `true`,拆分后的新块将被重置为默认类型。这对于退出格式化块(如标题)很有用。
+
+### 示例
+
+**标题拆分时重置:**
+
+```tsx
+import { H1Plugin } from '@platejs/heading/react';
+
+const plugins = [
+ // ...其他插件
+ H1Plugin.configure({
+ rules: {
+ break: {
+ splitReset: true,
+ },
+ },
+ }),
+];
+```
+
+按下 `Enter` 前:
+
+```tsx
+
+ Heading|text
+
+```
+
+按下后(拆分并重置):
+
+```tsx
+
+ Heading
+
+
+ |text
+
+```
+
+**带换行和智能退出的块引用:**
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...其他插件
+ BlockquotePlugin.configure({
+ rules: {
+ break: {
+ default: 'lineBreak',
+ empty: 'reset',
+ emptyLineEnd: 'deleteExit',
+ },
+ },
+ }),
+];
+```
+
+在块引用中按下 `Enter` 前:
+```tsx
+
+ Quote text|
+
+```
+
+按下后(换行):
+```tsx
+
+ Quote text
+ |
+
+```
+
+**带自定义空块处理的代码块:**
+
+```tsx
+import { CodeBlockPlugin } from '@platejs/code-block/react';
+
+const plugins = [
+ // ...其他插件
+ CodeBlockPlugin.configure({
+ rules: {
+ delete: { empty: 'reset' },
+ match: ({ editor, rule }) => {
+ return rule === 'delete.empty' && isCodeBlockEmpty(editor);
+ },
+ },
+ }),
+];
+```
+
+在空代码块中按下 `Backspace` 前:
+```tsx
+
+
+ |
+
+
+```
+
+按下后(重置):
+```tsx
+
+ |
+
+```
+
+## `rules.delete`
+
+控制用户在特定位置按下 `Backspace` 时的行为。
+
+### 配置
+
+```tsx
+HeadingPlugin.configure({
+ rules: {
+ delete: {
+ // 在块起始处按下 Backspace 时的操作
+ start: 'default' | 'reset',
+
+ // 在空块中按下 Backspace 时的操作
+ empty: 'default' | 'reset',
+ },
+ },
+});
+```
+
+每个属性控制特定场景:
+
+- `start`
+ - [`'default'`](#default)
+ - [`'reset'`](#reset)
+
+- `empty`
+ - [`'default'`](#default)
+ - [`'reset'`](#reset)
+
+### 示例
+
+**在起始处重置块引用:**
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...其他插件
+ BlockquotePlugin.configure({
+ rules: {
+ delete: { start: 'reset' },
+ },
+ }),
+];
+```
+
+在起始处按下 `Backspace` 前:
+```tsx
+
+ |Quote content
+
+```
+
+按下后(重置):
+```tsx
+
+ |Quote content
+
+```
+
+**带起始重置的列表项:**
+
+```tsx
+import { ListPlugin } from '@platejs/list/react';
+
+const plugins = [
+ // ...其他插件
+ ListPlugin.configure({
+ rules: {
+ delete: { start: 'reset' },
+ match: ({ rule, node }) => {
+ return rule === 'delete.start' && Boolean(node.listStyleType);
+ },
+ },
+ }),
+];
+```
+
+在列表项起始处按下 `Backspace` 前:
+```tsx
+
+ |List item content
+
+```
+
+按下后(重置):
+```tsx
+
+ |List item content
+
+```
+
+## `rules.merge`
+
+控制块与前一个块合并时的行为。
+
+### 配置
+
+```tsx
+ParagraphPlugin.configure({
+ rules: {
+ merge: {
+ // 合并时是否移除空块
+ removeEmpty: boolean,
+ },
+ },
+});
+```
+
+### 示例
+
+默认情况下,只有段落和标题插件启用移除功能。大多数其他插件使用 `false`:
+
+```tsx
+import { H1Plugin, ParagraphPlugin } from 'platejs/react';
+
+const plugins = [
+ // ...其他插件
+ H1Plugin, // 默认 rules.merge: { removeEmpty: true }
+ ParagraphPlugin, // 默认 rules.merge: { removeEmpty: true }
+];
+```
+
+在起始处按下 `Backspace` 前:
+```tsx
+
+
+
+
+ |Heading content
+
+```
+
+按下后(空段落被移除):
+```tsx
+
+ |Heading content
+
+```
+
+**禁用移除的块引用:**
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...其他插件
+ BlockquotePlugin.configure({
+ rules: {
+ merge: { removeEmpty: false }, // 默认
+ },
+ }),
+];
+```
+
+在起始处按下 `Backspace` 前:
+```tsx
+
+
+
+
+ |Code content
+
+```
+
+按下后(保留空段落):
+```tsx
+
+ |Code content
+
+```
+
+**表格单元格在合并时保留结构:**
+
+```tsx
+import { TablePlugin } from '@platejs/table/react';
+
+const plugins = [
+ // ...其他插件
+ TablePlugin, // 表格单元格有 rules.merge: { removeEmpty: false }
+];
+```
+
+在段落末尾按下 `Delete` 前:
+```tsx
+
+ Content|
+
+
+
+
+ Cell data
+
+
+ More data
+
+
+
+```
+
+按下后(合并单元格内容,保留结构):
+```tsx
+
+ Content|Cell data
+
+
+
+
+
+
+
+
+
+ More data
+
+
+
+```
+
+
+Slate 的默认值为 `true`,因为默认块(段落)是一等公民,而 Plate 插件很可能用于定义其他节点行为,这些行为不应自动移除空的前驱块。
+
+
+## `rules.normalize`
+
+控制在规范化过程中如何规范化节点。
+
+### 配置
+
+```tsx
+LinkPlugin.configure({
+ rules: {
+ normalize: {
+ // 是否移除空文本节点
+ removeEmpty: boolean,
+ },
+ },
+});
+```
+
+### 示例
+
+**移除空链接节点:**
+
+```tsx
+import { LinkPlugin } from '@platejs/link/react';
+
+const plugins = [
+ // ...其他插件
+ LinkPlugin.configure({
+ rules: {
+ normalize: { removeEmpty: true },
+ },
+ }),
+];
+```
+
+规范化前:
+```tsx
+
+
+
+
+
+
+```
+
+规范化后(移除空链接):
+```tsx
+
+
+
+```
+
+## `rules.match`
+
+插件规则中的 `match` 函数允许您基于节点属性(而不仅仅是类型匹配)覆盖特定插件的默认行为。这在您想为现有节点类型扩展新行为时特别有用。
+
+### 示例
+
+**带自定义空块检测的代码块:**
+
+```tsx
+import { CodeBlockPlugin } from '@platejs/code-block/react';
+
+const plugins = [
+ // ...其他插件
+ CodeBlockPlugin.configure({
+ rules: {
+ delete: { empty: 'reset' },
+ match: ({ rule, node }) => {
+ return rule === 'delete.empty' && isCodeBlockEmpty(editor);
+ },
+ },
+ }),
+];
+```
+
+由于列表插件扩展了已有自己的插件配置的现有块(如 `ParagraphPlugin`),使用 `rules.match` 允许您覆盖这些行为。
+
+**段落的列表覆盖:**
+
+```tsx
+import { ListPlugin } from '@platejs/list/react';
+
+const plugins = [
+ // ...其他插件
+ ListPlugin.configure({
+ rules: {
+ match: ({ editor, rule }) => {
+ return rule === 'delete.empty' && isCodeBlockEmpty(editor);
+ },
+ },
+ }),
+];
+```
+
+## 自定义重置逻辑
+
+某些插件需要超出标准段落转换的特殊重置行为。您可以覆盖 `resetBlock` 转换:
+
+**列表插件重置(缩进而非转换为段落):**
+
+```tsx
+const ListPlugin = createPlatePlugin({
+ key: 'list',
+ // ... 其他配置
+}).overrideEditor(({ editor, tf: { resetBlock } }) => ({
+ transforms: {
+ resetBlock(options) {
+ if (editor.api.block(options)?.[0]?.listStyleType) {
+ outdentList();
+ return;
+ }
+
+ return resetBlock(options);
+ },
+ },
+}));
+```
+
+**代码块重置(解包而非转换):**
+
+```tsx
+const CodeBlockPlugin = createPlatePlugin({
+ key: 'code_block',
+ // ... 其他配置
+}).overrideEditor(({ editor, tf: { resetBlock } }) => ({
+ transforms: {
+ resetBlock(options) {
+ if (editor.api.block({
+ at: options?.at,
+ match: { type: 'code_block' },
+ })) {
+ unwrapCodeBlock();
+ return;
+ }
+
+ return resetBlock(options);
+ },
+ },
+}));
+```
+
+## 组合规则
+
+您可以组合不同的规则来实现全面的块行为:
+
+```tsx
+import { H1Plugin } from '@platejs/heading/react';
+
+const plugins = [
+ // ...其他插件
+ H1Plugin.configure({
+ rules: {
+ break: {
+ empty: 'reset',
+ splitReset: true,
+ },
+ delete: {
+ start: 'reset',
+ },
+ },
+ }),
+];
+```
+
+**换行行为(默认):**
+```tsx
+
+ Hello|
+
+```
+
+按下 `Enter` 后:
+```tsx
+
+ Hello
+ |
+
+```
+
+**空块重置行为:**
+```tsx
+
+ |
+
+```
+
+按下 `Enter` 后:
+```tsx
+
+ |
+
+```
+
+**起始处重置行为:**
+```tsx
+
+ |Quote content
+
+```
+按下 `Backspace` 后:
+```tsx
+
+ |Quote content
+
+```
+
+## 高级用法
+
+对于超出简单规则的复杂场景,您可以直接使用 [`.overrideEditor`](/docs/plugin-methods#overrideeditor) 覆盖编辑器转换。这使您可以完全控制 [`resetBlock`](/docs/plugin-methods#extendtransforms) 和 [`insertExitBreak`](/docs/plugin-methods#extendtransforms) 等转换:
+
+```tsx
+const CustomPlugin = createPlatePlugin({
+ key: 'custom',
+ // ... 其他配置
+}).overrideEditor(({ editor, tf: { insertBreak, deleteBackward, resetBlock } }) => ({
+ transforms: {
+ insertBreak() {
+ const block = editor.api.block();
+
+ if (/* 自定义条件 */) {
+ // 自定义行为
+ return;
+ }
+
+ // 默认行为
+ insertBreak();
+ },
+
+ deleteBackward(unit) {
+ const block = editor.api.block();
+
+ if (/* 自定义条件 */) {
+ // 自定义行为
+ return;
+ }
+
+ deleteBackward(unit);
+ },
+
+ resetBlock(options) {
+ if (/* 自定义条件 */) {
+ // 自定义行为
+ return true;
+ }
+
+ return resetBlock(options);
+ },
+ },
+}));
+```
+
+## `rules.selection`
+
+控制光标定位和文本插入在节点边界的行为,特别是对于标记和内联元素。
+
+### 配置
+
+```tsx
+BoldPlugin.configure({
+ rules: {
+ selection: {
+ // 定义边界处的选择行为
+ affinity: 'default' | 'directional' | 'outward' | 'hard',
+ },
+ },
+});
+```
+
+### 亲和性选项
+
+`affinity` 属性决定光标在不同标记或内联元素边界处的行为:
+
+#### `default`
+
+使用 Slate 的默认行为。对于标记,光标在起始边缘具有向外亲和性(在标记前输入不会应用它),在结束边缘具有向内亲和性(在标记后输入会扩展它)。
+
+**在标记结束处(向内亲和性):**
+```tsx
+
+ Bold text| Normal text
+
+```
+
+输入会将粗体格式扩展到新文本。
+
+**在标记起始处(向外亲和性):**
+```tsx
+
+ Normal text| Bold text
+
+```
+
+输入不会将粗体格式应用于新文本。
+
+#### `directional`
+
+选择亲和性由光标移动方向决定。当光标移动到边界时,基于其来源位置保持亲和性。
+
+```tsx
+import { BoldPlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...其他插件
+ BoldPlugin.configure({
+ rules: {
+ selection: { affinity: 'directional' },
+ },
+ }),
+];
+```
+
+**从右侧移动(向内亲和性):**
+```tsx
+
+ Normal B|old text
+
+```
+
+按下 `←` 后:
+```tsx
+
+ Normal |Bold text
+
+```
+
+输入会扩展粗体格式,这在 `default` 亲和性下是不可能的。
+
+```tsx
+import { LinkPlugin } from '@platejs/link/react';
+
+const plugins = [
+ // ...其他插件
+ LinkPlugin.configure({
+ rules: {
+ selection: { affinity: 'directional' },
+ },
+ }),
+];
+```
+
+**从右侧移动(向外亲和性):**
+```tsx
+
+ Visit our website |for more information text.
+
+```
+
+按下 `←` 后:
+```tsx
+
+ Visit our website
+
+## Actions
+
+Plugin rules use specific action names to define behavior:
+
+- **`'default'`**: Default Slate behavior.
+- **`'reset'`**: Changes the current block to a default paragraph, keeping content.
+- **`'exit'`**: Exits the current block, inserting a new paragraph after it. See [Exit Break](/docs/exit-break) to learn more about this behavior.
+- **`'deleteExit'`**: Deletes content then exits the block.
+- **`'lineBreak'`**: Inserts a line break (`\n`) instead of splitting the block.
+
+### `default`
+
+Standard Slate behavior. For `rules.break`, splits the block. For `rules.delete`, merges with the previous block.
+
+```tsx
+
+ Hello world|
+
+```
+
+After pressing `Enter`:
+
+```tsx
+Hello world
+
+ |
+
+```
+
+After pressing `Backspace`:
+
+```tsx
+Hello world|
+```
+
+### `reset`
+
+Converts the current block to a default paragraph while preserving content. Custom properties are removed.
+
+```tsx
+
+ |
+
+```
+
+After pressing `Enter` with `rules: { break: { empty: 'reset' } }`:
+
+```tsx
+
+ |
+
+```
+
+### `exit`
+
+Exits the current block structure by inserting a new paragraph after it.
+
+```tsx
+
+ |
+
+```
+
+After pressing `Enter` with `rules: { break: { empty: 'exit' } }`:
+
+```tsx
+
+
+
+
+ |
+
+```
+
+### `deleteExit`
+
+Deletes content then exits the block.
+
+```tsx
+
+ line1
+ |
+
+```
+
+After pressing `Enter` with `rules: { break: { emptyLineEnd: 'deleteExit' } }`:
+
+```tsx
+line1
+
+ |
+
+```
+
+### `lineBreak`
+
+Inserts a soft line break (`\n`) instead of splitting the block.
+
+```tsx
+
+ Hello|
+
+```
+
+After pressing `Enter` with `rules: { break: { default: 'lineBreak' } }`:
+
+```tsx
+
+ Hello
+ |
+
+```
+
+## `rules.break`
+
+Controls what happens when users press `Enter` within specific block types.
+
+### Configuration
+
+```tsx
+BlockquotePlugin.configure({
+ rules: {
+ break: {
+ // Action when Enter is pressed normally
+ default: 'default' | 'lineBreak' | 'exit' | 'deleteExit',
+
+ // Action when Enter is pressed in an empty block
+ empty: 'default' | 'reset' | 'exit' | 'deleteExit',
+
+ // Action when Enter is pressed at end of empty line
+ emptyLineEnd: 'default' | 'exit' | 'deleteExit',
+
+ // If true, the new block after splitting will be reset
+ splitReset: boolean,
+ },
+ },
+});
+```
+
+Each property controls a specific scenario:
+
+- `default`
+ - [`'default'`](#default)
+ - [`'lineBreak'`](#linebreak)
+ - [`'exit'`](#exit)
+ - [`'deleteExit'`](#deleteexit)
+
+- `empty`
+ - [`'default'`](#default)
+ - [`'reset'`](#reset)
+ - [`'exit'`](#exit)
+ - [`'deleteExit'`](#deleteexit)
+
+- `emptyLineEnd`
+ - [`'default'`](#default)
+ - [`'exit'`](#exit)
+ - [`'deleteExit'`](#deleteexit)
+
+- `splitReset`: If `true`, resets the new block to the default type after a split. This is useful for exiting a formatted block like a heading.
+
+### Examples
+
+**Reset heading on break:**
+
+```tsx
+import { H1Plugin } from '@platejs/heading/react';
+
+const plugins = [
+ // ...otherPlugins,
+ H1Plugin.configure({
+ rules: {
+ break: {
+ splitReset: true,
+ },
+ },
+ }),
+];
+```
+
+Before pressing `Enter`:
+
+```tsx
+
+ Heading|text
+
+```
+
+After (split and reset):
+
+```tsx
+
+ Heading
+
+
+ |text
+
+```
+
+**Blockquote with line breaks and smart exits:**
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...otherPlugins,
+ BlockquotePlugin.configure({
+ rules: {
+ break: {
+ default: 'lineBreak',
+ empty: 'reset',
+ emptyLineEnd: 'deleteExit',
+ },
+ },
+ }),
+];
+```
+
+Before pressing `Enter` in blockquote:
+```tsx
+
+ Quote text|
+
+```
+
+After (line break):
+```tsx
+
+ Quote text
+ |
+
+```
+
+**Code block with custom empty handling:**
+
+```tsx
+import { CodeBlockPlugin } from '@platejs/code-block/react';
+
+const plugins = [
+ // ...otherPlugins,
+ CodeBlockPlugin.configure({
+ rules: {
+ delete: { empty: 'reset' },
+ match: ({ editor, rule }) => {
+ return rule === 'delete.empty' && isCodeBlockEmpty(editor);
+ },
+ },
+ }),
+];
+```
+
+Before pressing `Backspace` in empty code block:
+```tsx
+
+
+ |
+
+
+```
+
+After (reset):
+```tsx
+
+ |
+
+```
+
+## `rules.delete`
+
+Controls what happens when users press `Backspace` at specific positions.
+
+### Configuration
+
+```tsx
+HeadingPlugin.configure({
+ rules: {
+ delete: {
+ // Action when Backspace is pressed at block start
+ start: 'default' | 'reset',
+
+ // Action when Backspace is pressed in empty block
+ empty: 'default' | 'reset',
+ },
+ },
+});
+```
+
+Each property controls a specific scenario:
+
+- `start`
+ - [`'default'`](#default)
+ - [`'reset'`](#reset)
+
+- `empty`
+ - [`'default'`](#default)
+ - [`'reset'`](#reset)
+
+### Examples
+
+**Reset blockquotes at start:**
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...otherPlugins,
+ BlockquotePlugin.configure({
+ rules: {
+ delete: { start: 'reset' },
+ },
+ }),
+];
+```
+
+Before pressing `Backspace` at start:
+```tsx
+
+ |Quote content
+
+```
+
+After (reset):
+```tsx
+
+ |Quote content
+
+```
+
+**List items with start reset:**
+
+```tsx
+import { ListPlugin } from '@platejs/list/react';
+
+const plugins = [
+ // ...otherPlugins,
+ ListPlugin.configure({
+ rules: {
+ delete: { start: 'reset' },
+ match: ({ rule, node }) => {
+ return rule === 'delete.start' && Boolean(node.listStyleType);
+ },
+ },
+ }),
+];
+```
+
+Before pressing `Backspace` at start of list item:
+```tsx
+
+ |List item content
+
+```
+
+After (reset):
+```tsx
+
+ |List item content
+
+```
+
+## `rules.merge`
+
+Controls how blocks behave when merging with previous blocks.
+
+### Configuration
+
+```tsx
+ParagraphPlugin.configure({
+ rules: {
+ merge: {
+ // Whether to remove empty blocks when merging
+ removeEmpty: boolean,
+ },
+ },
+});
+```
+
+### Examples
+
+Only paragraph and heading plugins enable removal by default. Most other plugins use `false`:
+
+```tsx
+import { H1Plugin, ParagraphPlugin } from 'platejs/react';
+
+const plugins = [
+ // ...otherPlugins,
+ H1Plugin, // rules.merge: { removeEmpty: true } by default
+ ParagraphPlugin, // rules.merge: { removeEmpty: true } by default
+];
+```
+
+Before pressing `Backspace` at start:
+```tsx
+
+
+
+
+ |Heading content
+
+```
+
+After (empty paragraph removed):
+```tsx
+
+ |Heading content
+
+```
+
+**Blockquote with removal disabled:**
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...otherPlugins,
+ BlockquotePlugin.configure({
+ rules: {
+ merge: { removeEmpty: false }, // Default
+ },
+ }),
+];
+```
+
+Before pressing `Backspace` at start:
+```tsx
+
+
+
+
+ |Code content
+
+```
+
+After (empty paragraph preserved):
+```tsx
+
+ |Code content
+
+```
+
+**Table cells preserve structure during merge:**
+
+```tsx
+import { TablePlugin } from '@platejs/table/react';
+
+const plugins = [
+ // ...otherPlugins,
+ TablePlugin, // Table cells have rules.merge: { removeEmpty: false }
+];
+```
+
+Before pressing `Delete` at end of paragraph:
+```tsx
+
+ Content|
+
+
+
+
+ Cell data
+
+
+ More data
+
+
+
+```
+
+After (cell content merged, structure preserved):
+```tsx
+
+ Content|Cell data
+
+
+
+
+
+
+
+
+
+ More data
+
+
+
+```
+
+
+Slate's default is `true` since the default block (paragraph) is first-class, while Plate plugins are likely used to define other node behaviors that shouldn't automatically remove empty predecessors.
+
+
+## `rules.normalize`
+
+Controls how nodes are normalized during the normalization process.
+
+### Configuration
+
+```tsx
+LinkPlugin.configure({
+ rules: {
+ normalize: {
+ // Whether to remove nodes with empty text
+ removeEmpty: boolean,
+ },
+ },
+});
+```
+
+### Examples
+
+**Remove empty link nodes:**
+
+```tsx
+import { LinkPlugin } from '@platejs/link/react';
+
+const plugins = [
+ // ...otherPlugins,
+ LinkPlugin.configure({
+ rules: {
+ normalize: { removeEmpty: true },
+ },
+ }),
+];
+```
+
+Before normalization:
+```tsx
+
+
+
+
+
+
+```
+
+After normalization (empty link removed):
+```tsx
+
+
+
+```
+
+## `rules.match`
+
+The `match` function in plugin rules allows you to override the default behavior of specific plugins based on node properties beyond just type matching. This is particularly useful when you want to extend existing node types with new behaviors.
+
+### Examples
+
+**Code block with custom empty detection:**
+
+```tsx
+import { CodeBlockPlugin } from '@platejs/code-block/react';
+
+const plugins = [
+ // ...otherPlugins,
+ CodeBlockPlugin.configure({
+ rules: {
+ delete: { empty: 'reset' },
+ match: ({ rule, node }) => {
+ return rule === 'delete.empty' && isCodeBlockEmpty(editor);
+ },
+ },
+ }),
+];
+```
+
+Since the list plugin extends existing blocks that already have their own plugin configuration (e.g. `ParagraphPlugin`), using `rules.match` allows you to override those behaviors.
+
+**List override for paragraphs:**
+
+```tsx
+import { ListPlugin } from '@platejs/list/react';
+
+const plugins = [
+ // ...otherPlugins,
+ ListPlugin.configure({
+ rules: {
+ match: ({ editor, rule }) => {
+ return rule === 'delete.empty' && isCodeBlockEmpty(editor);
+ },
+ },
+ }),
+];
+```
+
+## Custom Reset Logic
+
+Some plugins need special reset behavior beyond the standard paragraph conversion. You can override the `resetBlock` transform:
+
+**List plugin reset (outdents instead of converting to paragraph):**
+
+```tsx
+const ListPlugin = createPlatePlugin({
+ key: 'list',
+ // ... other config
+}).overrideEditor(({ editor, tf: { resetBlock } }) => ({
+ transforms: {
+ resetBlock(options) {
+ if (editor.api.block(options)?.[0]?.listStyleType) {
+ outdentList();
+ return;
+ }
+
+ return resetBlock(options);
+ },
+ },
+}));
+```
+
+**Code block reset (unwraps instead of converting):**
+
+```tsx
+const CodeBlockPlugin = createPlatePlugin({
+ key: 'code_block',
+ // ... other config
+}).overrideEditor(({ editor, tf: { resetBlock } }) => ({
+ transforms: {
+ resetBlock(options) {
+ if (editor.api.block({
+ at: options?.at,
+ match: { type: 'code_block' },
+ })) {
+ unwrapCodeBlock();
+ return;
+ }
+
+ return resetBlock(options);
+ },
+ },
+}));
+```
+
+## Combining Rules
+
+You can combine different rules for comprehensive block behavior:
+
+```tsx
+import { H1Plugin } from '@platejs/heading/react';
+
+const plugins = [
+ // ...otherPlugins,
+ H1Plugin.configure({
+ rules: {
+ break: {
+ empty: 'reset',
+ splitReset: true,
+ },
+ delete: {
+ start: 'reset',
+ },
+ },
+ }),
+];
+```
+
+**Line break behavior (default):**
+```tsx
+
+ Hello|
+
+```
+
+After `Enter`:
+```tsx
+
+ Hello
+ |
+
+```
+
+**Empty reset behavior:**
+```tsx
+
+ |
+
+```
+
+After `Enter`:
+```tsx
+
+ |
+
+```
+
+**Start reset behavior:**
+```tsx
+
+ |Quote content
+
+```
+After `Backspace`:
+```tsx
+
+ |Quote content
+
+```
+
+## Advanced
+
+For complex scenarios beyond simple rules, you can override editor transforms directly using [`.overrideEditor`](/docs/plugin-methods#overrideeditor). This gives you complete control over transforms like [`resetBlock`](/docs/plugin-methods#extendtransforms) and [`insertExitBreak`](/docs/plugin-methods#extendtransforms):
+
+```tsx
+const CustomPlugin = createPlatePlugin({
+ key: 'custom',
+ // ... other config
+}).overrideEditor(({ editor, tf: { insertBreak, deleteBackward, resetBlock } }) => ({
+ transforms: {
+ insertBreak() {
+ const block = editor.api.block();
+
+ if (/* Custom condition */) {
+ // Custom behavior
+ return;
+ }
+
+ // Default behavior
+ insertBreak();
+ },
+
+ deleteBackward(unit) {
+ const block = editor.api.block();
+
+ if (/* Custom condition */) {
+ // Custom behavior
+ return;
+ }
+
+ deleteBackward(unit);
+ },
+
+ resetBlock(options) {
+ if (/* Custom condition */) {
+ // Custom behavior
+ return true;
+ }
+
+ return resetBlock(options);
+ },
+ },
+}));
+```
+
+## `rules.selection`
+
+Controls how cursor positioning and text insertion behave at node boundaries, particularly for marks and inline elements.
+
+### Configuration
+
+```tsx
+BoldPlugin.configure({
+ rules: {
+ selection: {
+ // Define selection behavior at boundaries
+ affinity: 'default' | 'directional' | 'outward' | 'hard',
+ },
+ },
+});
+```
+
+### Affinity Options
+
+The `affinity` property determines how the cursor behaves when positioned at the boundary between different marks or inline elements:
+
+#### `default`
+
+Uses Slate's default behavior. For marks, the cursor has outward affinity at the start edge (typing before the mark doesn't apply it) and inward affinity at the end edge (typing after the mark extends it).
+
+**At end of mark (inward affinity):**
+```tsx
+
+ Bold text| Normal text
+
+```
+
+Typing would extend the bold formatting to new text.
+
+**At start of mark (outward affinity):**
+```tsx
+
+ Normal text| Bold text
+
+```
+
+Typing would not apply bold formatting to new text.
+
+#### `directional`
+
+Selection affinity is determined by the direction of cursor movement. When the cursor moves to a boundary, it maintains the affinity based on where it came from.
+
+```tsx
+import { BoldPlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...otherPlugins,
+ BoldPlugin.configure({
+ rules: {
+ selection: { affinity: 'directional' },
+ },
+ }),
+];
+```
+
+**Movement from right (inward affinity):**
+```tsx
+
+ Normal B|old text
+
+```
+
+After pressing `←`:
+```tsx
+
+ Normal |Bold text
+
+```
+
+Typing would extend the bold formatting, which is not possible with `default` affinity.
+
+```tsx
+import { LinkPlugin } from '@platejs/link/react';
+
+const plugins = [
+ // ...otherPlugins,
+ LinkPlugin.configure({
+ rules: {
+ selection: { affinity: 'directional' },
+ },
+ }),
+];
+```
+
+**Movement from right (outward affinity):**
+```tsx
+
+ Visit our website |for more information text.
+
+```
+
+After pressing `←`:
+```tsx
+
+ Visit our website | for more information text.
+
+```
+
+Cursor movement direction determines whether new text extends the link or creates new text outside it.
+
+#### `outward`
+
+Forces outward affinity, automatically clearing marks when typing at their boundaries. This creates a natural "exit" behavior from formatted text.
+
+```tsx
+import { CommentPlugin } from '@platejs/comment/react';
+
+const plugins = [
+ // ...otherPlugins,
+ CommentPlugin.configure({
+ rules: {
+ selection: { affinity: 'outward' },
+ },
+ }),
+];
+```
+
+**At end of marked text:**
+```tsx
+
+ Commented text| Normal
+
+```
+
+After typing:
+```tsx
+
+ Commented text x|Normal
+
+```
+
+Users automatically exit comment formatting by typing at the end of commented text.
+
+#### `hard`
+
+Creates a "hard" edge that requires two key presses to move across. This provides precise cursor control for elements that need exact positioning.
+
+```tsx
+import { CodePlugin } from '@platejs/basic-nodes/react';
+
+const plugins = [
+ // ...otherPlugins,
+ CodePlugin.configure({
+ rules: {
+ selection: { affinity: 'hard' },
+ },
+ }),
+];
+```
+
+**Moving across hard edges:**
+```tsx
+
+ Before code| After
+
+```
+
+First `→` press changes affinity:
+```tsx
+
+ Before code |After
+
+```
+
+Second `→` press moves cursor:
+```tsx
+
+ Before code A|fter
+
+```
+
+This allows users to position the cursor precisely at the boundary and choose whether new text should be inside or outside the code formatting.
diff --git a/apps/www/content/docs/(guides)/plugin-shortcuts.cn.mdx b/apps/www/content/docs/(guides)/plugin-shortcuts.cn.mdx
new file mode 100644
index 0000000000..6c94f43c91
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-shortcuts.cn.mdx
@@ -0,0 +1,254 @@
+---
+title: 插件快捷键
+description: 学习如何配置键盘快捷键。
+---
+
+键盘快捷键是实现快速高效编辑工作流的关键。Plate 允许您轻松为编辑器插件定义和自定义快捷键。
+
+## 定义快捷键
+
+在创建或配置插件时(例如使用 `createPlatePlugin().extend({...})` 或 `ExistingPlugin.configure({...})`),您可以为任何插件添加或修改快捷键。快捷键定义在插件配置的 `shortcuts` 字段中。
+
+有两种主要方式定义快捷键行为:
+
+### 1. 关联插件方法(推荐)
+
+创建快捷键最直接的方式是将其关联到插件中已有的方法。这可以是 [transform 方法](/docs/plugin-methods#extendtransforms) 或 [API 方法](/docs/plugin-methods#extendapi)。transform 是修改编辑器状态的函数(例如切换标记、插入元素),而 API 方法提供其他功能。
+
+操作步骤:
+1. 确保 `shortcuts` 配置对象中的快捷键名称与方法名称匹配(例如名为 `toggle` 的快捷键会查找名为 `toggle` 的 transform,如果没有 transform 则查找名为 `toggle` 的 API 方法)。
+2. 提供快捷键的 `keys`(按键组合)。
+
+当按下指定按键时,Plate 会自动找到并调用对应方法。
+
+```tsx title="plugins/my-document-plugin.ts"
+import { createPlatePlugin, Key } from 'platejs/react';
+
+// 示例:包含 transform 和 API 的简化插件
+export const MyDocumentPlugin = createPlatePlugin({
+ key: 'doc',
+})
+// 定义 editor.tf.doc.format()
+.extendTransforms(({ editor, type }) => ({
+ format: () => {
+ editor.tf.normalize({ force: true });
+ },
+}))
+// 定义 editor.api.doc.format()
+.extendApi(({ editor, type }) => ({
+ save: async () => {
+ // 保存文档
+ // await fetch(...);
+ },
+}))
+.extend({ // 或使用 .configure() 扩展现有插件
+ shortcuts: {
+ // 这会调用 editor.tf.doc.format()
+ format: {
+ keys: [[Key.Mod, Key.Shift, 'f']], // 例如 Cmd/Ctrl + Shift + F
+ },
+ // 这会调用 editor.api.doc.save()
+ save: {
+ keys: [[Key.Mod, 's']], // 例如 Cmd/Ctrl + S
+ },
+ },
+});
+```
+
+
+ 快捷键名称(例如示例中的 `toggle`)非常重要,Plate 用它来定位插件上的匹配方法。它首先查找 transform 方法,如果没有找到则回退到 API 方法。
+
+
+### 2. 使用自定义处理函数
+
+对于需要更复杂逻辑、依赖键盘事件或没有与现有 transform 名称直接一对一映射的操作,您可以提供自定义 `handler` 函数。当快捷键激活时,此函数将被执行。
+
+```tsx title="plugins/custom-logger-plugin.ts"
+import { createPlatePlugin, Key } from 'platejs/react';
+
+export const CustomLoggerPlugin = createPlatePlugin({
+ key: 'customLogger',
+}).extend({
+ shortcuts: {
+ logEditorState: {
+ keys: [[Key.Mod, Key.Alt, 's']], // 例如 Cmd/Ctrl + Alt + S
+ handler: ({ editor, event, eventDetails }) => {
+ // 'editor' 是 PlateEditor 实例
+ // 'event' 是原始 KeyboardEvent
+ // 'eventDetails' 提供来自热键库的更多上下文
+ console.info('当前编辑器值:', editor.children);
+ console.info('按下的按键:', eventDetails.keys);
+ // 您可能希望阻止其他操作或浏览器默认行为
+ // event.preventDefault();
+ },
+ },
+ },
+});
+```
+
+## 快捷键配置属性
+
+定义或配置快捷键时,可以在其配置对象中使用以下属性:
+
+- `keys`: **必填。** 触发快捷键的按键组合。
+ - 可以是字符串如 `'mod+b'` 或使用 `Key` 枚举的数组以获得更明确的控制(例如 `[[Key.Mod, Key.Shift, 'x']]`)。
+ - `Key.Mod` 是一种便捷方式,在 macOS 上指定 `Cmd`,在其他操作系统上指定 `Ctrl`。
+- `handler`: (可选)当快捷键激活时调用的函数。其签名为:
+ `({ editor: PlateEditor; event: KeyboardEvent; eventDetails: HotkeysEvent; }) => void;`
+ 如果省略 `handler`,Plate 将尝试调用与快捷键名称匹配的 transform。
+ **注意**:如果您的 transform 或 handler 返回 `false`(例如未处理),将不会调用 `preventDefault`,允许其他处理程序或浏览器默认行为接管。任何其他返回值将使用默认的 `preventDefault` 行为。
+- `preventDefault`: (可选)布尔值。如果设置为 `true`,则阻止该按键组合的浏览器默认行为(例如 `Mod+B` 通常在浏览器中会加粗文本)。**默认为 `true`**。这适用于大多数编辑器特定的快捷键。设置为 `false` 如果您需要允许浏览器的默认行为或让其他处理程序处理事件,特别是如果您的处理程序可能不总是执行操作(例如在当前上下文中不应用的缩进命令)。
+- `priority`: (可选)数字。如果多个插件为完全相同的 `keys` 定义了快捷键,具有更高 `priority` 数字的快捷键将优先。这对于解决冲突很有用。
+- *(其他选项)*: 您还可以包含与底层 `@udecode/react-hotkeys` 库的 `useHotkeys` 钩子兼容的其他选项,例如 `enabled`、`enableOnContentEditable` 等,以微调行为。
+
+## Plate 插件中的默认快捷键
+
+许多官方 Plate 插件为其常见操作预配置了快捷键。这些默认值通常链接到插件的内部 transform 方法。目前,以下基本标记插件包含默认快捷键:
+
+- **BoldPlugin**: `Mod+B`
+- **ItalicPlugin**: `Mod+I`
+- **UnderlinePlugin**: `Mod+U`
+
+其他插件,如 `CodePlugin`、`StrikethroughPlugin` 等,提供可以轻松链接到快捷键的 transform(例如,`toggle` 快捷键将链接到 `editor.tf..toggle()`),但您需要为它们明确定义快捷键 `keys`。
+
+
+ 粗体、斜体和下划线的特定默认按键组合在每个插件的默认配置中定义。您始终可以覆盖这些默认值或为其他插件定义快捷键,如果它们不符合您的需求(参见下面的“覆盖和禁用快捷键”)。
+
+
+## 管理多个快捷键
+
+单个插件不限于一个快捷键;您可以根据需要定义任意数量:
+
+```tsx title="plugins/my-formatting-tools.ts"
+import { createPlatePlugin, Key } from 'platejs/react';
+
+export const MyFormattingTools = createPlatePlugin({
+ key: 'myFormatting',
+ // 假设存在 transform 如 editor.tf.myFormatting.applyHeader
+ // 和 editor.tf.myFormatting.applyCodeStyle。
+})
+.extend({
+ shortcuts: {
+ applyHeader: {
+ keys: [[Key.Mod, Key.Alt, '1']],
+ },
+ applyCodeStyle: {
+ keys: [[Key.Mod, Key.Alt, 'c']],
+ },
+ // 带有自定义处理函数的快捷键
+ logSomething: {
+ keys: [[Key.Mod, 'l']],
+ handler: () => console.info('来自 MyFormattingTools 的日志记录!'),
+ },
+ },
+});
+```
+
+## 快捷键优先级
+
+如果多个快捷键(可能来自不同插件)配置为使用完全相同的按键组合(例如 `Mod+Shift+P`),快捷键配置对象中的 `priority` 属性决定执行哪个快捷键的操作。
+
+数字越大优先级越高。如果未在快捷键上显式设置 `priority`,则使用其父插件的 `priority` 作为回退。这允许在按键组合重叠时精细控制哪个操作优先。
+
+```tsx
+const PluginA = createPlatePlugin({ key: 'pluginA', priority: 10 }).extend({
+ shortcuts: {
+ doSomethingImportant: {
+ keys: 'mod+shift+p',
+ handler: () => console.info('插件 A: 在 Mod+Shift+P 上的重要操作!'),
+ priority: 100, // 显式,此特定快捷键的高优先级
+ }
+ }
+});
+
+const PluginB = createPlatePlugin({ key: 'pluginB', priority: 20 }).extend({
+ shortcuts: {
+ doSomethingLessImportant: {
+ keys: 'mod+shift+p', // 与 PluginA 的快捷键相同的按键组合
+ handler: () => console.info('插件 B: 在 Mod+Shift+P 上的不太重要的操作。'),
+ // 没有显式的快捷键优先级,将使用 PluginB 的优先级 (20)
+ }
+ }
+});
+
+// 如果两个插件都激活,按下 Mod+Shift+P 将执行 PluginA 的 'doSomethingImportant' 处理程序
+// 因为其快捷键具有更高的优先级 (100 vs 20)。
+```
+
+## 覆盖和禁用快捷键
+
+在配置特定插件时,您可以更改或禁用其快捷键。
+
+**更改插件的快捷键:**
+在配置插件时(例如 `BoldPlugin.configure({ ... })`),您可以通过其名称(如 `toggle`)定义快捷键。如果插件已有该名称的快捷键(可能是默认的),您的新配置将用于该插件。您可以更改其 `keys`,提供新的 `handler`,或调整其他属性。
+
+```tsx
+import { BoldPlugin, Key } from '@platejs/basic-nodes/react';
+
+// BoldPlugin 有一个名为 'toggle' 的默认快捷键(通常是 Mod+B)。
+// 让我们将 BoldPlugin 的按键组合更改为 Mod+Shift+B。
+const MyCustomBoldPlugin = BoldPlugin.configure({
+ shortcuts: {
+ toggle: { // 这会重新配置 BoldPlugin 的 'toggle' 快捷键
+ keys: [[Key.Mod, Key.Shift, 'b']], // 新的按键组合
+ // 原始处理程序(链接到 'toggle' transform)通常会被保留
+ // 除非在此处指定新的 'handler'。
+ },
+ },
+});
+```
+
+**禁用插件的快捷键:**
+在该插件的 `shortcuts` 对象中将快捷键配置设置为 `null`。这将移除该特定快捷键(例如 `ItalicPlugin` 的 `toggle`)。
+
+```tsx
+import { ItalicPlugin } from '@platejs/basic-nodes/react';
+
+// 示例:禁用 ItalicPlugin 的 'toggle' 快捷键
+const MyCustomItalicPlugin = ItalicPlugin.configure({
+ shortcuts: {
+ toggle: null, // 这将移除/禁用 ItalicPlugin 的 'toggle' 快捷键。
+ },
+});
+```
+
+## 全局快捷键(编辑器级别)
+
+除了插件特定的快捷键,您还可以在使用 `createPlateEditor` 创建编辑器时直接在编辑器实例上定义全局快捷键。这些快捷键的行为类似于插件快捷键。
+
+```tsx title="editor-config.ts"
+import { createPlateEditor, Key } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [/* ...您的插件数组... */],
+ shortcuts: {
+ // 全局快捷键,可能用于保存文档
+ saveDocument: {
+ keys: [[Key.Mod, 's']],
+ handler: ({ editor, event }) => {
+ console.info('尝试保存文档内容:', editor.children);
+ // 由于此快捷键的 preventDefault 设置为 false,
+ // 浏览器的保存对话框将默认出现。
+ // 如果您想有条件地阻止浏览器的默认行为
+ // (例如,仅在满足某些条件时阻止保存),
+ // 您可以在处理程序中根据需要调用 event.preventDefault():
+ // if (shouldPrevent) event.preventDefault();
+ },
+ preventDefault: false,
+ },
+ anotherGlobalAction: {
+ keys: [[Key.Ctrl, Key.Alt, 'g']],
+ handler: () => alert('全局操作触发!'),
+ }
+ },
+});
+```
+编辑器级别的快捷键通常具有较高的默认优先级,但如果存在冲突,仍可能受到单个插件快捷键的 `priority` 设置的影响。
+
+## 最佳实践
+
+- **链接到 Transform**:为了清晰和保持代码 DRY,通过将快捷键名称与 transform 名称匹配,将快捷键链接到现有的 transform 方法。
+- **`preventDefault`**:大多数编辑器快捷键应阻止该按键组合的浏览器默认行为。Plate 通过默认将 `preventDefault` 设置为 `true` 来处理此问题。您通常不需要显式设置它。但是,如果您的快捷键处理程序有条件地执行操作(例如仅在满足某些条件时应用的缩进命令),并且您希望在其他操作不运行时让其他处理程序或浏览器的默认行为接管,请为该快捷键设置 `preventDefault: false`。
+- **保持一致性**:努力实现直观且一致的按键组合。考虑流行文本编辑器中找到的标准快捷键或在应用程序上下文中合乎逻辑的快捷键。
+- **管理冲突的优先级**:如果您预期或遇到多个插件可能尝试处理相同按键组合的情况,请使用 `priority` 属性明确定义哪个快捷键应优先。
+- **提供用户反馈**:对于不立即可见的操作触发的快捷键(如“保存”操作),考虑提供某种形式的用户反馈,例如简短的 [toast](https://ui.shadcn.com/docs/components/sonner) 通知。
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/plugin-shortcuts.mdx b/apps/www/content/docs/(guides)/plugin-shortcuts.mdx
new file mode 100644
index 0000000000..6a5af10f42
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin-shortcuts.mdx
@@ -0,0 +1,254 @@
+---
+title: Plugin Shortcuts
+description: Learn how to configure keyboard shortcuts.
+---
+
+Keyboard shortcuts are essential for a fast and productive editing workflow. Plate allows you to easily define and customize shortcuts for your editor plugins.
+
+## Defining Shortcuts
+
+You can add or modify shortcuts for any plugin when you create or configure it (e.g., using `createPlatePlugin().extend({...})` or `ExistingPlugin.configure({...})`). Shortcuts are defined within the `shortcuts` field of your plugin configuration.
+
+There are two primary ways to define what a shortcut does:
+
+### 1. Linking to Plugin Methods (Recommended)
+
+The most straightforward way to create a shortcut is by linking it to an existing method within your plugin. This can be either a [transform method](/docs/plugin-methods#extendtransforms) or an [API method](/docs/plugin-methods#extendapi). Transforms are functions that modify the editor's state (e.g., toggling a mark, inserting an element), while API methods provide other functionality.
+
+To do this:
+1. Ensure the name of your shortcut in the `shortcuts` configuration object matches the name of the method (e.g., a shortcut named `toggle` will look for a transform named `toggle`, or if no transform exists, an API method named `toggle`).
+2. Provide the `keys` (the key combination) for the shortcut.
+
+Plate will automatically find and call the corresponding method when the specified keys are pressed.
+
+```tsx title="plugins/my-document-plugin.ts"
+import { createPlatePlugin, Key } from 'platejs/react';
+
+// Example: A simplified plugin with both transforms and API
+export const MyDocumentPlugin = createPlatePlugin({
+ key: 'doc',
+})
+// Define editor.tf.doc.format()
+.extendTransforms(({ editor, type }) => ({
+ format: () => {
+ editor.tf.normalize({ force: true });
+ },
+}))
+// Define editor.api.doc.format()
+.extendApi(({ editor, type }) => ({
+ save: async () => {
+ // Save the document
+ // await fetch(...);
+ },
+}))
+.extend({ // Or .configure() if extending an existing plugin
+ shortcuts: {
+ // This will call editor.tf.doc.format()
+ format: {
+ keys: [[Key.Mod, Key.Shift, 'f']], // e.g., Cmd/Ctrl + Shift + F
+ },
+ // This will call editor.api.doc.save()
+ save: {
+ keys: [[Key.Mod, 's']], // e.g., Cmd/Ctrl + S
+ },
+ },
+});
+```
+
+
+ The name of the shortcut (e.g., `toggle` in the example) is crucial as Plate uses it to locate the matching method on the plugin. It first looks for a transform method, then falls back to an API method if no transform exists with that name.
+
+
+### 2. Using a Custom Handler
+
+For actions that require more complex logic, depends on the keyboard event, or if there isn't a direct one-to-one mapping with an existing transform name, you can provide a custom `handler` function. This function will be executed when the shortcut is activated.
+
+```tsx title="plugins/custom-logger-plugin.ts"
+import { createPlatePlugin, Key } from 'platejs/react';
+
+export const CustomLoggerPlugin = createPlatePlugin({
+ key: 'customLogger',
+}).extend({
+ shortcuts: {
+ logEditorState: {
+ keys: [[Key.Mod, Key.Alt, 's']], // e.g., Cmd/Ctrl + Alt + S
+ handler: ({ editor, event, eventDetails }) => {
+ // 'editor' is the PlateEditor instance
+ // 'event' is the raw KeyboardEvent
+ // 'eventDetails' provides more context from the hotkey library
+ console.info('Current editor value:', editor.children);
+ console.info('Pressed keys:', eventDetails.keys);
+ // You might want to prevent other actions or browser defaults
+ // event.preventDefault();
+ },
+ },
+ },
+});
+```
+
+## Shortcut Configuration Properties
+
+When defining or configuring a shortcut, you can use the following properties in its configuration object:
+
+- `keys`: **Required.** The key combination(s) that trigger the shortcut.
+ - This can be a string like `'mod+b'` or an array using the `Key` enum for more explicit control (e.g., `[[Key.Mod, Key.Shift, 'x']]`).
+ - `Key.Mod` is a convenient way to specify `Cmd` on macOS and `Ctrl` on other operating systems.
+- `handler`: (Optional) A function that is called when the shortcut is activated. Its signature is:
+ `({ editor: PlateEditor; event: KeyboardEvent; eventDetails: HotkeysEvent; }) => void;`
+ If you omit the `handler`, Plate will attempt to call a matching transform based on the shortcut's name.
+ **Note**: If your transform or handler returns `false` (e.g. not handled), `preventDefault` will NOT be called, allowing other handlers or browser defaults to take over. Any other return value will use the default `preventDefault` behavior.
+- `preventDefault`: (Optional) A boolean. If set to `true`, it prevents the browser's default action for that key combination (e.g., `Mod+B` typically bolds text in the browser itself). **Defaults to `true`**. This is suitable for most editor-specific shortcuts. Set to `false` if you need to allow the browser's default action or enable other handlers to process the event, especially if your handler might not always perform an action (e.g., an indent command that doesn't apply in the current context).
+- `priority`: (Optional) A number. If multiple plugins define shortcuts for the exact same `keys`, the shortcut with the higher `priority` number will take precedence. This is useful for resolving conflicts.
+- *(Other options)*: You can also include other options compatible with the underlying `useHotkeys` hook from the `@udecode/react-hotkeys` library, such as `enabled`, `enableOnContentEditable`, etc., to fine-tune behavior.
+
+## Default Shortcuts in Plate Plugins
+
+Many official Plate plugins come with pre-configured shortcuts for their common actions. These defaults typically link to the plugin's internal transform methods. Currently, the following basic mark plugins include default shortcuts:
+
+- **BoldPlugin**: `Mod+B`
+- **ItalicPlugin**: `Mod+I`
+- **UnderlinePlugin**: `Mod+U`
+
+Other plugins, like `CodePlugin`, `StrikethroughPlugin`, etc., provide transforms that can be easily linked to shortcuts (e.g., a `toggle` shortcut will link to `editor.tf..toggle()`), but you need to define the shortcut `keys` for them explicitly.
+
+
+ The specific default key combinations for Bold, Italic, and Underline are defined within each plugin's default configuration. You can always override these defaults or define shortcuts for other plugins if they don't fit your needs (see "Overriding and Disabling Shortcuts" below).
+
+
+## Managing Multiple Shortcuts
+
+A single plugin isn't limited to one shortcut; you can define as many as needed:
+
+```tsx title="plugins/my-formatting-tools.ts"
+import { createPlatePlugin, Key } from 'platejs/react';
+
+export const MyFormattingTools = createPlatePlugin({
+ key: 'myFormatting',
+ // Assuming transforms like editor.tf.myFormatting.applyHeader
+ // and editor.tf.myFormatting.applyCodeStyle exist.
+})
+.extend({
+ shortcuts: {
+ applyHeader: {
+ keys: [[Key.Mod, Key.Alt, '1']],
+ },
+ applyCodeStyle: {
+ keys: [[Key.Mod, Key.Alt, 'c']],
+ },
+ // A shortcut with a custom handler
+ logSomething: {
+ keys: [[Key.Mod, 'l']],
+ handler: () => console.info('Logging from MyFormattingTools!'),
+ },
+ },
+});
+```
+
+## Shortcut Priority
+
+If multiple shortcuts (potentially from different plugins) are configured to use the exact same key combination (e.g., `Mod+Shift+P`), the `priority` property on the shortcut configuration object determines which shortcut's action is executed.
+
+A higher number indicates higher priority. If `priority` is not explicitly set on a shortcut, the `priority` of its parent plugin is used as a fallback. This allows fine-grained control over which action takes precedence when key combinations overlap.
+
+```tsx
+const PluginA = createPlatePlugin({ key: 'pluginA', priority: 10 }).extend({
+ shortcuts: {
+ doSomethingImportant: {
+ keys: 'mod+shift+p',
+ handler: () => console.info('Plugin A: Important action on Mod+Shift+P!'),
+ priority: 100, // Explicit, high priority for this specific shortcut
+ }
+ }
+});
+
+const PluginB = createPlatePlugin({ key: 'pluginB', priority: 20 }).extend({
+ shortcuts: {
+ doSomethingLessImportant: {
+ keys: 'mod+shift+p', // Same key combination as PluginA's shortcut
+ handler: () => console.info('Plugin B: Less important action on Mod+Shift+P.'),
+ // No explicit shortcut priority, will use PluginB's priority (20)
+ }
+ }
+});
+
+// If both plugins are active, pressing Mod+Shift+P will execute PluginA's handler
+// for 'doSomethingImportant' because its shortcut has a higher priority (100 vs 20).
+```
+
+## Overriding and Disabling Shortcuts
+
+You can change or disable shortcuts for a specific plugin when you configure it.
+
+**To change a plugin's shortcut:**
+When you configure a plugin (e.g., `BoldPlugin.configure({ ... })`), you can define a shortcut by its name (like `toggle`). If the plugin already has a shortcut with that name (perhaps a default one), your new configuration for `toggle` will be used for that plugin. You can change its `keys`, provide a new `handler`, or adjust other properties.
+
+```tsx
+import { BoldPlugin, Key } from '@platejs/basic-nodes/react';
+
+// BoldPlugin has a default shortcut named 'toggle' (typically Mod+B).
+// Let's change its key combination to Mod+Shift+B for BoldPlugin.
+const MyCustomBoldPlugin = BoldPlugin.configure({
+ shortcuts: {
+ toggle: { // This re-configures BoldPlugin's 'toggle' shortcut
+ keys: [[Key.Mod, Key.Shift, 'b']], // New key combination
+ // The original handler (linking to the 'toggle' transform) is often preserved
+ // unless a new 'handler' is specified here.
+ },
+ },
+});
+```
+
+**To disable a plugin's shortcut:**
+Set the shortcut's configuration to `null` in that plugin's `shortcuts` object. This will remove that specific shortcut (e.g., `toggle` for `ItalicPlugin`).
+
+```tsx
+import { ItalicPlugin } from '@platejs/basic-nodes/react';
+
+// Example: Disable the 'toggle' shortcut for the ItalicPlugin
+const MyCustomItalicPlugin = ItalicPlugin.configure({
+ shortcuts: {
+ toggle: null, // This will remove/disable the ItalicPlugin's 'toggle' shortcut.
+ },
+});
+```
+
+## Global Shortcuts (Editor Level)
+
+In addition to plugin-specific shortcuts, you can define global shortcuts directly on the editor instance when you create it using `createPlateEditor`. These shortcuts behave similarly to plugin shortcuts.
+
+```tsx title="editor-config.ts"
+import { createPlateEditor, Key } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [/* ...your array of plugins... */],
+ shortcuts: {
+ // A global shortcut, perhaps for saving the document
+ saveDocument: {
+ keys: [[Key.Mod, 's']],
+ handler: ({ editor, event }) => {
+ console.info('Attempting to save document content:', editor.children);
+ // Since preventDefault is set to false for this shortcut,
+ // the browser's save dialog will appear by default.
+ // If you want to conditionally prevent the default browser behavior
+ // (for example, only prevent saving if certain conditions are met),
+ // you can call event.preventDefault() inside your handler as needed:
+ // if (shouldPrevent) event.preventDefault();
+ },
+ preventDefault: false,
+ },
+ anotherGlobalAction: {
+ keys: [[Key.Ctrl, Key.Alt, 'g']],
+ handler: () => alert('Global action triggered!'),
+ }
+ },
+});
+```
+Editor-level shortcuts generally have a high default priority but can still be influenced by the `priority` settings of individual plugin shortcuts if there are conflicts.
+
+## Best Practices
+
+- **Link to Transforms**: For clarity and to keep your code DRY, link shortcuts to existing transform methods by matching the shortcut name to the transform name.
+- **`preventDefault`**: Most editor shortcuts should prevent the browser's default action for the key combination. Plate handles this by defaulting `preventDefault` to `true`. You generally don't need to set it explicitly. However, if your shortcut handler conditionally performs an action (e.g., an indent command that only applies if certain conditions are met), and you want other handlers or the browser's default behavior to take over if your action doesn't run, set `preventDefault: false` for that shortcut.
+- **Maintain Consistency**: Strive for intuitive and consistent key combinations. Consider standard shortcuts found in popular text editors or those that make logical sense within your application's context.
+- **Manage Priorities for Conflicts**: If you anticipate or encounter situations where multiple plugins might try to handle the same key combination, use the `priority` property to explicitly define which shortcut should take precedence.
+- **Provide User Feedback**: For actions triggered by shortcuts that aren't immediately visible (like a "Save" action), consider providing some form of user feedback, such as a brief [toast](https://ui.shadcn.com/docs/components/sonner) notification.
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/plugin.cn.mdx b/apps/www/content/docs/(guides)/plugin.cn.mdx
new file mode 100644
index 0000000000..f14b4ab375
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin.cn.mdx
@@ -0,0 +1,468 @@
+---
+title: 插件配置
+description: 如何配置和自定义Plate插件。
+---
+
+Plate插件具有高度可配置性,允许您根据需要定制其行为。本指南将介绍最常见的配置选项及使用方法。
+
+- [入门指南:组件](/docs/installation#components) - 添加插件到编辑器的说明
+- [PlatePlugin API](/docs/api/core/plate-plugin) - 创建插件的完整API参考
+
+## 基础插件配置
+
+### 新建插件
+
+最基本的插件配置只需一个`key`:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'minimal',
+});
+```
+
+虽然这个插件目前没有任何功能,但它是更复杂配置的起点。
+
+### 现有插件
+
+使用`.configure`方法可以配置现有插件:
+
+```ts
+const ConfiguredPlugin = MyPlugin.configure({
+ options: {
+ myOption: 'new value',
+ },
+});
+```
+
+## 节点插件
+
+节点插件通过`node`属性定义编辑器中的新节点类型,可以是元素(块级或行内)或叶子节点(用于文本级格式)。
+
+### 元素
+
+要创建新元素类型,使用`node.isElement`选项:
+
+```ts
+const ParagraphPlugin = createPlatePlugin({
+ key: 'p',
+ node: {
+ isElement: true,
+ type: 'p',
+ },
+});
+```
+
+您可以为元素关联组件。详见[插件组件](/docs/plugin-components)。
+
+```ts
+const ParagraphPlugin = createPlatePlugin({
+ key: 'p',
+ node: {
+ isElement: true,
+ type: 'p',
+ component: ParagraphElement,
+ },
+});
+```
+
+### 行内元素、Void元素和叶子节点
+
+对于行内元素、void元素或叶子节点,使用相应的节点选项:
+
+```ts
+const LinkPlugin = createPlatePlugin({
+ key: 'link',
+ node: {
+ isElement: true,
+ isInline: true,
+ type: 'a',
+ },
+});
+
+const ImagePlugin = createPlatePlugin({
+ key: 'image',
+ node: {
+ isElement: true,
+ isVoid: true,
+ type: 'img',
+ },
+});
+
+const BoldPlugin = createPlatePlugin({
+ key: 'bold',
+ node: {
+ isLeaf: true,
+ },
+});
+```
+
+## 行为插件
+
+除了渲染元素或标记外,您可能还想自定义编辑器的行为。Plate提供了多种插件选项来修改编辑器行为。
+
+### 插件规则
+
+`rules`属性允许您配置常见的编辑行为,如拆分、删除和合并节点,而无需重写编辑器方法。这是为自定义元素定义直观交互的强大方式。
+
+例如,您可以定义当用户在空标题中按`Enter`或在块引用开头按`Backspace`时发生的情况。
+
+```ts
+import { H1Plugin } from '@platejs/heading/react';
+
+H1Plugin.configure({
+ rules: {
+ break: { empty: 'reset' },
+ },
+});
+```
+
+完整规则列表和可用操作请见[插件规则指南](/docs/plugin-rules)。
+
+### 事件处理器
+
+推荐通过`handlers`插件选项响应插件内部用户生成的事件。处理器应是一个接收`PlatePluginContext & { event }`对象的函数。
+
+`onChange`处理器(当编辑器值变化时调用)是个例外,其上下文对象包含变化的`value`而非`event`。
+
+```ts showLineNumbers
+const ExamplePlugin = createPlatePlugin({
+ key: 'example',
+ handlers: {
+ onChange: ({ editor, value }) => {
+ console.info(editor, value);
+ },
+ onKeyDown: ({ editor, event }) => {
+ console.info(`You pressed ${event.key}`);
+ },
+ },
+});
+```
+
+### 注入属性
+
+您可能希望向具有特定属性的任何节点注入类名或CSS属性。例如,以下插件为具有`align`属性的段落设置`textAlign` CSS属性。
+
+```ts showLineNumbers
+import { KEYS } from 'platejs';
+
+const TextAlignPlugin = createPlatePlugin({
+ key: 'align',
+ inject: {
+ nodeProps: {
+ defaultNodeValue: 'start',
+ nodeKey: 'align',
+ styleKey: 'textAlign',
+ validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify'],
+ },
+ targetPlugins: [KEYS.p],
+ // 这将注入到所有`targetPlugins`中。本例中,ParagraphPlugin将能够反序列化`textAlign`样式。
+ targetPluginToInject: ({ editor, plugin }) => ({
+ parsers: {
+ html: {
+ deserializer: {
+ parse: ({ element, node }) => {
+ if (element.style.textAlign) {
+ node[editor.getType('align')] = element.style.textAlign;
+ }
+ },
+ },
+ },
+ },
+ }),
+ },
+});
+```
+
+受上述插件影响的段落节点示例如下:
+
+```ts showLineNumbers {3}
+{
+ type: 'p',
+ align: 'right',
+ children: [{ text: '本段落右对齐!' }],
+}
+```
+
+### 重写编辑器方法
+
+`overrideEditor`方法提供了一种在保持访问原始实现的同时重写现有编辑器方法的方式。这在您想修改核心编辑器功能行为时特别有用。
+
+```ts
+const CustomPlugin = createPlatePlugin({
+ key: 'custom',
+}).overrideEditor(({ editor, tf: { deleteForward }, api: { isInline } }) => ({
+ // 重写转换
+ transforms: {
+ deleteForward(options) {
+ // 删除前的自定义逻辑
+ console.info('向前删除中...');
+
+ // 调用原始转换
+ deleteForward(options);
+
+ // 删除后的自定义逻辑
+ console.info('已向前删除');
+ },
+ },
+ // 重写API方法
+ api: {
+ isInline(element) {
+ // 自定义行内元素检查
+ if (element.type === 'custom-inline') {
+ return true;
+ }
+
+ // 回退到原始行为
+ return isInline(element);
+ },
+ },
+}));
+```
+
+- 通过解构的`tf`(转换)和`api`参数访问原始方法
+- 现有方法的类型安全重写
+- 转换和API方法的清晰分离
+- 插件上下文和选项访问
+
+带类型选项的示例:
+
+```ts
+type CustomConfig = PluginConfig<
+ 'custom',
+ { allowDelete: boolean }
+>;
+
+const CustomPlugin = createTPlatePlugin({
+ key: 'custom',
+ options: { allowDelete: true },
+}).overrideEditor(({ editor, tf: { deleteForward }, getOptions }) => ({
+ transforms: {
+ deleteForward(options) {
+ // 使用类型化选项控制行为
+ if (!getOptions().allowDelete) {
+ return;
+ }
+
+ deleteForward(options);
+ },
+ },
+}));
+```
+
+### 扩展编辑器(高级)
+
+对于复杂功能,您可以直接扩展编辑器。使用`extendEditor`插件选项在创建后直接修改`editor`对象的属性。
+
+```ts showLineNumbers {20}
+const CustomNormalizerPlugin = createPlatePlugin({
+ key: 'customNormalizer',
+ extendEditor: ({ editor }) => {
+ editor.customState = true;
+
+ return editor;
+ },
+});
+```
+
+
+- 当集成需要直接编辑器变异的传统Slate插件(如`withYjs`)时使用`extendEditor`。每个插件只有一个`extendEditor`。
+- 修改编辑器行为时优先使用`overrideEditor`,因为它具有单一职责和更好的类型安全性。可以多次调用以分层不同的重写。
+
+
+## 高级插件配置
+
+### 插件存储
+
+每个插件都有自己的存储,可用于管理插件特定状态。
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: {
+ count: 0,
+ },
+}).extend(({ editor, plugin, setOption }) => ({
+ handlers: {
+ onClick: () => {
+ setOption('count', 1);
+ },
+ },
+}));
+```
+
+您可以使用以下方法访问和更新存储:
+
+```ts
+// 获取当前值
+const count = editor.getOption(MyPlugin, 'count');
+
+// 设置新值
+editor.setOption(MyPlugin, 'count', 5);
+
+// 基于先前状态更新值
+editor.setOption(MyPlugin, 'count', (prev) => prev + 1);
+```
+
+在React组件中,可以使用`usePluginOption`或`usePluginOptions`钩子订阅存储变更:
+
+```tsx
+const MyComponent = () => {
+ const count = usePluginOption(MyPlugin, 'count');
+ return 计数: {count}
;
+};
+```
+
+更多内容请见[插件上下文](/docs/plugin-context)和[编辑器方法](/docs/editor-methods)指南。
+
+### 依赖项
+
+使用`dependencies`属性指定插件依赖项,确保当前插件加载前所需插件已加载。
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ dependencies: ['paragraphPlugin', 'listPlugin'],
+});
+```
+
+### 启用标志
+
+`enabled`属性允许您有条件地启用或禁用插件:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ enabled: true, // 或false禁用
+});
+```
+
+### 嵌套插件
+
+Plate支持嵌套插件,允许创建插件层次结构。使用`plugins`属性定义子插件:
+
+```ts
+const ParentPlugin = createPlatePlugin({
+ key: 'parent',
+ plugins: [
+ createPlatePlugin({ key: 'child1' }),
+ createPlatePlugin({ key: 'child2' }),
+ ],
+});
+```
+
+### 插件优先级
+
+`priority`属性决定插件注册和执行的顺序。优先级值较高的插件优先处理:
+
+```ts
+const HighPriorityPlugin = createPlatePlugin({
+ key: 'highPriority',
+ priority: 100,
+});
+
+const LowPriorityPlugin = createPlatePlugin({
+ key: 'lowPriority',
+ priority: 50,
+});
+```
+
+这在需要确保某些插件先于其他插件初始化或运行时特别有用。
+
+### 自定义解析器
+
+`parsers`属性接受字符串键来构建自己的解析器:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ parsers: {
+ myCustomParser: {
+ deserializer: {
+ parse: // ...
+ },
+ serializer: {
+ parse: // ...
+ }
+ },
+ },
+});
+```
+
+核心插件包含`html`和`htmlReact`解析器。
+
+## 类型化插件
+
+使用上述方法时,插件类型会根据给定配置自动推断。
+
+如果需要显式传递泛型类型,可以使用`createTPlatePlugin`。
+
+### 使用createTPlatePlugin
+
+`createTPlatePlugin`函数允许创建类型化插件:
+
+```ts
+type CodeBlockConfig = PluginConfig<
+ // key
+ 'code_block',
+ // options
+ { syntax: boolean; syntaxPopularFirst: boolean },
+ // api
+ {
+ plugin: {
+ getSyntaxState: () => boolean;
+ };
+ toggleSyntax: () => void;
+ },
+ // transforms
+ {
+ insert: {
+ codeBlock: (options: { language: string }) => void;
+ }
+ }
+>;
+
+const CodeBlockPlugin = createTPlatePlugin({
+ key: 'code_block',
+ options: { syntax: true, syntaxPopularFirst: false },
+}).extendEditorApi(() => ({
+ plugin: {
+ getSyntaxState: () => true,
+ },
+ toggleSyntax: () => {},
+})).extendEditorTransforms(() => ({
+ insert: {
+ codeBlock: ({ editor, getOptions }) => {
+ editor.tf.insertBlock({ type: 'code_block', language: getOptions().language });
+ },
+ },
+}));
+```
+
+### 使用类型化插件
+
+使用类型化插件时,您将获得完整的类型检查和自动补全功能 ✨
+
+```ts
+const editor = createPlateEditor({
+ plugins: [ExtendedCodeBlockPlugin],
+});
+
+// 类型安全的选项访问
+const options = editor.getOptions(ExtendedCodeBlockPlugin);
+options.syntax;
+options.syntaxPopularFirst;
+options.hotkey;
+
+// 类型安全的API
+editor.api.toggleSyntax();
+editor.api.plugin.getSyntaxState();
+editor.api.plugin2.setLanguage('python');
+editor.api.plugin.getLanguage();
+
+// 类型安全的转换
+editor.tf.insert.codeBlock({ language: 'typescript' });
+```
+
+## 另请参阅
+
+更多插件选项请见[PlatePlugin API](/docs/api/core/plate-plugin)。
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/plugin.mdx b/apps/www/content/docs/(guides)/plugin.mdx
new file mode 100644
index 0000000000..9501618af8
--- /dev/null
+++ b/apps/www/content/docs/(guides)/plugin.mdx
@@ -0,0 +1,470 @@
+---
+title: Plugin Configuration
+description: How to configure and customize Plate plugins.
+---
+
+Plate plugins are highly configurable, allowing you to customize their behavior to suit your needs. This guide will walk you through the most common configuration options and how to use them.
+
+- [Getting Started: Components](/docs/installation#components) - Instructions for adding plugins to your editor
+- [PlatePlugin API](/docs/api/core/plate-plugin) - The complete API reference for creating plugins
+
+## Basic Plugin Configuration
+
+### New Plugin
+
+The most basic plugin configuration requires only a `key`:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'minimal',
+});
+```
+
+While this plugin doesn't do anything yet, it's a starting point for more complex configurations.
+
+### Existing Plugin
+
+The `.configure` method allows you to configure an existing plugin:
+
+```ts
+const ConfiguredPlugin = MyPlugin.configure({
+ options: {
+ myOption: 'new value',
+ },
+});
+```
+
+## Node Plugins
+
+Node plugins are used to define new types of nodes in your editor using the `node` property. These can be elements (either block or inline) or leaf nodes (for text-level formatting).
+
+
+### Elements
+
+To create a new type of element, use the `node.isElement` option:
+
+```ts
+const ParagraphPlugin = createPlatePlugin({
+ key: 'p',
+ node: {
+ isElement: true,
+ type: 'p',
+ },
+});
+```
+
+You can associate a component with your element. See [Plugin Components](/docs/plugin-components) for more information.
+
+```ts
+const ParagraphPlugin = createPlatePlugin({
+ key: 'p',
+ node: {
+ isElement: true,
+ type: 'p',
+ component: ParagraphElement,
+ },
+});
+```
+
+### Inline, Void, and Leaf Nodes
+
+For inline elements, void elements, or leaf nodes, use the appropriate node options:
+
+```ts
+const LinkPlugin = createPlatePlugin({
+ key: 'link',
+ node: {
+ isElement: true,
+ isInline: true,
+ type: 'a',
+ },
+});
+
+const ImagePlugin = createPlatePlugin({
+ key: 'image',
+ node: {
+ isElement: true,
+ isVoid: true,
+ type: 'img',
+ },
+});
+
+const BoldPlugin = createPlatePlugin({
+ key: 'bold',
+ node: {
+ isLeaf: true,
+ },
+});
+```
+
+## Behavioral Plugins
+
+Rather than render an element or a mark, you may want to customize the behavior of your editor. Various plugin options are available to modify the behavior of Plate.
+
+### Plugin Rules
+
+The `rules` property allows you to configure common editing behaviors like breaking, deleting, and merging nodes without overriding editor methods. This is a powerful way to define intuitive interactions for your custom elements.
+
+For example, you can define what happens when a user presses `Enter` in an empty heading, or `Backspace` at the start of a blockquote.
+
+```ts
+import { H1Plugin } from '@platejs/heading/react';
+
+H1Plugin.configure({
+ rules: {
+ break: { empty: 'reset' },
+ },
+});
+```
+
+See the [Plugin Rules guide](/docs/plugin-rules) for a complete list of available rules and actions.
+
+### Event Handlers
+
+The recommended way to respond to user-generated events from inside a plugin is with the `handlers` plugin option. A handler should be a function that takes a `PlatePluginContext & { event }` object.
+
+The `onChange` handler, which is called when the editor value changes, is an exception to this rule; the context object includes the changed `value` instead of `event`.
+
+```ts showLineNumbers
+const ExamplePlugin = createPlatePlugin({
+ key: 'example',
+ handlers: {
+ onChange: ({ editor, value }) => {
+ console.info(editor, value);
+ },
+ onKeyDown: ({ editor, event }) => {
+ console.info(`You pressed ${event.key}`);
+ },
+ },
+});
+```
+
+### Inject Props
+
+You may want to inject a class name or CSS property into any node having a certain property. For example, the following plugin sets the `textAlign` CSS property on paragraphs with an `align` property.
+
+```ts showLineNumbers
+import { KEYS } from 'platejs';
+
+const TextAlignPlugin = createPlatePlugin({
+ key: 'align',
+ inject: {
+ nodeProps: {
+ defaultNodeValue: 'start',
+ nodeKey: 'align',
+ styleKey: 'textAlign',
+ validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify'],
+ },
+ targetPlugins: [KEYS.p],
+ // This is injected into all `targetPlugins`. In this example, ParagraphPlugin will be able to deserialize `textAlign` style.
+ targetPluginToInject: ({ editor, plugin }) => ({
+ parsers: {
+ html: {
+ deserializer: {
+ parse: ({ element, node }) => {
+ if (element.style.textAlign) {
+ node[editor.getType('align')] = element.style.textAlign;
+ }
+ },
+ },
+ },
+ },
+ }),
+ },
+});
+```
+
+A paragraph node affected by the above plugin would look like this:
+
+```ts showLineNumbers {3}
+{
+ type: 'p',
+ align: 'right',
+ children: [{ text: 'This paragraph is aligned to the right!' }],
+}
+```
+
+### Override Editor Methods
+
+The `overrideEditor` method provides a way to override existing editor methods while maintaining access to the original implementations. This is particularly useful when you want to modify the behavior of core editor functionality.
+
+```ts
+const CustomPlugin = createPlatePlugin({
+ key: 'custom',
+}).overrideEditor(({ editor, tf: { deleteForward }, api: { isInline } }) => ({
+ // Override transforms
+ transforms: {
+ deleteForward(options) {
+ // Custom logic before deletion
+ console.info('Deleting forward...');
+
+ // Call original transform
+ deleteForward(options);
+
+ // Custom logic after deletion
+ console.info('Deleted forward');
+ },
+ },
+ // Override API methods
+ api: {
+ isInline(element) {
+ // Custom inline element check
+ if (element.type === 'custom-inline') {
+ return true;
+ }
+
+ // Fall back to original behavior
+ return isInline(element);
+ },
+ },
+}));
+```
+
+- Access to original methods via destructured `tf` (transforms) and `api` parameters
+- Type-safe overrides of existing methods
+- Clean separation between transforms and API methods
+- Plugin context and options access
+
+Example with typed options:
+
+```ts
+type CustomConfig = PluginConfig<
+ 'custom',
+ { allowDelete: boolean }
+>;
+
+const CustomPlugin = createTPlatePlugin({
+ key: 'custom',
+ options: { allowDelete: true },
+}).overrideEditor(({ editor, tf: { deleteForward }, getOptions }) => ({
+ transforms: {
+ deleteForward(options) {
+ // Use typed options to control behavior
+ if (!getOptions().allowDelete) {
+ return;
+ }
+
+ deleteForward(options);
+ },
+ },
+}));
+```
+
+### Extend Editor (Advanced)
+
+You can extend the editor for complex functionality. To do this, you can use the `extendEditor` plugin option to directly mutate properties of the `editor` object after its creation.
+
+```ts showLineNumbers {20}
+const CustomNormalizerPlugin = createPlatePlugin({
+ key: 'customNormalizer',
+ extendEditor: ({ editor }) => {
+ editor.customState = true;
+
+ return editor;
+ },
+});
+```
+
+
+- Use `extendEditor` when integrating legacy Slate plugins like `withYjs` that need direct editor mutation. There is only one `extendEditor` per plugin.
+- Prefer using `overrideEditor` for modifying editor behavior as it has single purpose responsibility and better type safety. It can be called multiple times to layer different overrides.
+
+
+## Advanced Plugin Configuration
+
+### Plugin Store
+
+Each plugin has its own store, which can be used to manage plugin-specific state.
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ options: {
+ count: 0,
+ },
+}).extend(({ editor, plugin, setOption }) => ({
+ handlers: {
+ onClick: () => {
+ setOption('count', 1);
+ },
+ },
+}));
+```
+
+You can access and update the store using the following methods:
+
+```ts
+// Get the current value
+const count = editor.getOption(MyPlugin, 'count');
+
+// Set a new value
+editor.setOption(MyPlugin, 'count', 5);
+
+// Update the value based on the previous state
+editor.setOption(MyPlugin, 'count', (prev) => prev + 1);
+```
+
+In React components, you can use the `usePluginOption` or `usePluginOptions` hook to subscribe to store changes:
+
+```tsx
+const MyComponent = () => {
+ const count = usePluginOption(MyPlugin, 'count');
+ return Count: {count}
;
+};
+```
+
+See more in [Plugin Context](/docs/plugin-context) and [Editor Methods](/docs/editor-methods) guides.
+
+
+### Dependencies
+
+You can specify plugin dependencies using the `dependencies` property. This ensures that the required plugins are loaded before the current plugin.
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ dependencies: ['paragraphPlugin', 'listPlugin'],
+});
+```
+
+### Enabled Flag
+
+The `enabled` property allows you to conditionally enable or disable a plugin:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ enabled: true, // or false to disable
+});
+```
+
+### Nested Plugins
+
+Plate supports nested plugins, allowing you to create plugin hierarchies. Use the `plugins` property to define child plugins:
+
+```ts
+const ParentPlugin = createPlatePlugin({
+ key: 'parent',
+ plugins: [
+ createPlatePlugin({ key: 'child1' }),
+ createPlatePlugin({ key: 'child2' }),
+ ],
+});
+```
+
+### Plugin Priority
+
+The `priority` property determines the order in which plugins are registered and executed. Plugins with higher priority values are processed first:
+
+```ts
+const HighPriorityPlugin = createPlatePlugin({
+ key: 'highPriority',
+ priority: 100,
+});
+
+const LowPriorityPlugin = createPlatePlugin({
+ key: 'lowPriority',
+ priority: 50,
+});
+```
+
+This is particularly useful when you need to ensure certain plugins are initialized or run before others.
+
+### Custom Parsers
+
+The `parsers` property accepts string keys to build your own parsers:
+
+```ts
+const MyPlugin = createPlatePlugin({
+ key: 'myPlugin',
+ parsers: {
+ myCustomParser: {
+ deserializer: {
+ parse: // ...
+ },
+ serializer: {
+ parse: // ...
+ }
+ },
+ },
+});
+```
+
+Core plugins includes `html` and `htmlReact` parsers.
+
+## Typed Plugins
+
+Using above methods, plugin types are automatically inferred from the given configuration.
+
+If you need to pass an explicit type as generic, you can use `createTPlatePlugin`.
+
+### Using createTPlatePlugin
+
+The `createTPlatePlugin` function allows you to create a typed plugin:
+
+```ts
+type CodeBlockConfig = PluginConfig<
+ // key
+ 'code_block',
+ // options
+ { syntax: boolean; syntaxPopularFirst: boolean },
+ // api
+ {
+ plugin: {
+ getSyntaxState: () => boolean;
+ };
+ toggleSyntax: () => void;
+ },
+ // transforms
+ {
+ insert: {
+ codeBlock: (options: { language: string }) => void;
+ }
+ }
+>;
+
+const CodeBlockPlugin = createTPlatePlugin({
+ key: 'code_block',
+ options: { syntax: true, syntaxPopularFirst: false },
+}).extendEditorApi(() => ({
+ plugin: {
+ getSyntaxState: () => true,
+ },
+ toggleSyntax: () => {},
+})).extendEditorTransforms(() => ({
+ insert: {
+ codeBlock: ({ editor, getOptions }) => {
+ editor.tf.insertBlock({ type: 'code_block', language: getOptions().language });
+ },
+ },
+}));
+```
+
+### Using Typed Plugins
+
+When using typed plugins, you get full type checking and autocompletion ✨
+
+```ts
+const editor = createPlateEditor({
+ plugins: [ExtendedCodeBlockPlugin],
+});
+
+// Type-safe access to options
+const options = editor.getOptions(ExtendedCodeBlockPlugin);
+options.syntax;
+options.syntaxPopularFirst;
+options.hotkey;
+
+// Type-safe API
+editor.api.toggleSyntax();
+editor.api.plugin.getSyntaxState();
+editor.api.plugin2.setLanguage('python');
+editor.api.plugin.getLanguage();
+
+// Type-safe Transforms
+editor.tf.insert.codeBlock({ language: 'typescript' });
+```
+
+## See also
+
+See the [PlatePlugin API](/docs/api/core/plate-plugin) for more plugin options.
diff --git a/apps/www/content/docs/(guides)/static.cn.mdx b/apps/www/content/docs/(guides)/static.cn.mdx
new file mode 100644
index 0000000000..f6b345a15b
--- /dev/null
+++ b/apps/www/content/docs/(guides)/static.cn.mdx
@@ -0,0 +1,366 @@
+---
+title: 静态渲染
+description: 一个最小化、记忆化、只读版本的 Plate,支持 RSC/SSR。
+---
+
+`` 是一个**快速、只读**的 React 组件,用于渲染 Plate 内容,针对**服务器端**或 **React Server Component** (RSC) 环境进行了优化。它避免了客户端编辑逻辑,并对节点渲染进行记忆化,相比在只读模式下使用 [``](/docs/api/core/plate-components),具有更好的性能。
+
+它是 [`serializeHtml`](/docs/api/core/plate-plugin#serializehtml) 用于 HTML 导出的核心部分,非常适合任何需要非交互式、展示性 Plate 内容视图的服务器或 RSC 环境。
+
+## 主要优势
+
+- **服务器安全:** 无浏览器 API 依赖;可在 SSR/RSC 中工作。
+- **无 Plate 编辑器开销:** 排除交互功能,如选择或事件处理程序。
+- **记忆化渲染:** 使用 `_memo` 和结构检查,仅重新渲染已更改的节点。
+- **部分重新渲染:** 文档某一部分的更改不会强制完全重新渲染。
+- **轻量级:** 由于省略了交互式编辑器代码,打包体积更小。
+
+## 何时使用 ``
+
+- 使用 [HTML 序列化](/docs/html) 生成 HTML。
+- 在 Next.js 中显示服务器渲染的预览(特别是使用 RSC)。
+- 构建具有只读 Plate 内容的静态站点。
+- 优化性能关键的只读视图。
+- 渲染 AI 流式内容。
+
+
+ 对于交互式只读功能(如评论弹出框或选择),请在浏览器中使用标准 `` 组件。对于纯服务器渲染的、非交互式的内容,`` 是推荐的选择。
+
+
+## Kit 使用
+
+
+
+### 安装
+
+启用静态渲染的最快方法是使用 `BaseEditorKit`,它包含了预配置的基础插件,可以与服务器端渲染无缝配合。
+
+
+
+### 添加 Kit
+
+```tsx
+import { createSlateEditor, PlateStatic } from 'platejs';
+import { BaseEditorKit } from '@/components/editor/editor-base-kit';
+
+const editor = createSlateEditor({
+ plugins: BaseEditorKit,
+ value: [
+ { type: 'h1', children: [{ text: '服务器渲染的标题' }] },
+ { type: 'p', children: [{ text: '此内容是静态渲染的。' }] },
+ ],
+});
+
+// 静态渲染
+export default function MyStaticPage() {
+ return ;
+}
+```
+
+### 示例
+
+查看完整的服务器端静态渲染示例:
+
+
+
+
+
+## 手动使用
+
+
+
+### 创建 Slate 编辑器
+
+使用 `createSlateEditor` 初始化一个 Slate 编辑器实例,包含所需的插件和组件。这类似于为交互式 `` 组件使用 `usePlateEditor`。
+
+```tsx title="lib/plate-static-editor.ts"
+import { createSlateEditor } from 'platejs';
+// 导入所需的基础插件(例如 BaseHeadingPlugin、MarkdownPlugin)
+// 确保在服务器环境中不要从 /react 子路径导入。
+
+const editor = createSlateEditor({
+ plugins: [
+ // 在此添加您的基础插件列表
+ // 示例:BaseHeadingPlugin, MarkdownPlugin.configure({...})
+ ],
+ value: [ // 示例初始值
+ {
+ type: 'p',
+ children: [{ text: '来自静态 Plate 编辑器的问候!' }],
+ },
+ ],
+});
+```
+
+### 定义静态节点组件
+
+如果您的交互式编辑器使用客户端组件(例如带有 `use client` 或事件处理程序),您**必须**创建静态的、服务器安全的等效组件。这些组件应该渲染纯 HTML,不包含浏览器特定的逻辑。
+
+```tsx title="components/ui/paragraph-node-static.tsx"
+import React from 'react';
+import type { SlateElementProps } from 'platejs';
+
+export function ParagraphElementStatic(props: SlateElementProps) {
+ return (
+
+ {props.children}
+
+ );
+}
+```
+为标题、图片、链接等创建类似的静态组件。
+
+### 映射插件键到静态组件
+
+创建一个对象,将插件键或节点类型映射到相应的静态 React 组件,然后将其传递给编辑器。
+
+```ts title="components/static-components.ts"
+import { ParagraphElementStatic } from './ui/paragraph-node-static';
+import { HeadingElementStatic } from './ui/heading-node-static';
+// ... 导入其他静态组件
+
+export const staticComponents = {
+ p: ParagraphElementStatic,
+ h1: HeadingElementStatic,
+ // ... 为所有元素和叶子类型添加映射
+};
+```
+
+### 渲染 ``
+
+使用 `` 组件,提供配置了组件的 `editor` 实例。
+
+```tsx title="app/my-static-page/page.tsx (RSC 示例)"
+import { PlateStatic } from 'platejs';
+import { createSlateEditor } from 'platejs';
+// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes'; // 等等
+import { staticComponents } from '@/components/static-components';
+
+export default async function MyStaticPage() {
+ // 示例:获取或定义编辑器值
+ const initialValue = [
+ { type: 'h1', children: [{ text: '服务器渲染的标题' }] },
+ { type: 'p', children: [{ text: '静态渲染的内容。' }] },
+ ];
+
+ const editor = createSlateEditor({
+ plugins: [/* 您的基础插件 */],
+ components: staticComponents,
+ value: initialValue,
+ });
+
+ return (
+
+ );
+}
+```
+
+
+ 如果您直接向 `` 传递 `value` 属性,它将覆盖 `editor.children`。
+ ```tsx
+
+ ```
+
+
+### 记忆化详情
+
+`` 通过记忆化提升性能:
+- 每个 `` 和 `` 都被 `React.memo` 包装。
+- **引用相等性:** 未更改的节点引用可防止重新渲染。
+- **`_memo` 字段:** 在元素或叶子上设置 `node._memo = true`(或任何稳定值)可以强制 Plate 跳过重新渲染该特定节点,即使其内容发生变化。这对于更新的细粒度控制很有用。
+
+
+
+## 客户端替代方案:`PlateView`
+
+对于需要与静态内容进行**最小交互**的情况,请使用 ``。该组件包装了 `` 并添加了客户端事件处理程序来处理用户交互,同时保持静态渲染的性能优势。
+
+### 示例:具有两种静态视图的服务器组件
+
+```tsx title="app/document/page.tsx"
+import { createStaticEditor } from 'platejs';
+import { PlateStatic } from 'platejs';
+import { BaseEditorKit } from '@/components/editor/editor-base-kit';
+import { InteractiveViewer } from './interactive-viewer';
+
+export default async function DocumentPage() {
+ const content = await fetchDocument(); // 您的文档数据
+
+ // 服务器端静态编辑器
+ const editor = createStaticEditor({
+ plugins: BaseEditorKit,
+ value: content,
+ });
+
+ return (
+
+ {/* 纯静态渲染 - 无交互性 */}
+
+
+ {/* 交互式视图 - 在客户端渲染 */}
+
+
交互式视图
+
+
+
+ );
+}
+```
+
+### 示例:使用 PlateView 的客户端组件
+
+```tsx title="app/document/interactive-viewer.tsx"
+'use client';
+
+import { usePlateViewEditor } from 'platejs/react';
+import { PlateView } from 'platejs/react';
+import { BaseEditorKit } from '@/components/editor/editor-base-kit';
+
+export function InteractiveViewer({ value }) {
+ const editor = usePlateViewEditor({
+ plugins: BaseEditorKit,
+ value,
+ });
+
+ return ;
+}
+```
+
+### `PlateView` 的主要功能
+
+- **仅客户端:** 需要 `'use client'` 指令
+- **添加交互性:** 启用用户与内容的交互(例如文本选择、复制、未来的交互如工具提示、高亮等)
+- **最小开销:** 内部仍使用 `PlateStatic` 进行渲染
+- **与 `usePlateViewEditor` 配合使用:** 创建一个为仅查看 React 组件优化的静态编辑器
+- **包含 ViewPlugin:** 静态编辑器自动包含 `ViewPlugin`,提供事件处理功能
+
+
+ `PlateView` 不能在服务器组件中使用。如果您从服务器组件向客户端组件传递编辑器,将遇到序列化错误。在服务器端使用 `PlateStatic`,或在客户端使用 `usePlateViewEditor` 创建编辑器。
+
+
+## `PlateStatic` vs. `PlateView` vs. `Plate` + `readOnly`
+
+| 方面 | `` | `` | `` + `readOnly` |
+| --------------------- | ----------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------ |
+| **环境** | 服务器/客户端(SSR/RSC 安全) | 仅客户端 | 仅客户端 |
+| **交互性** | 无 | 最小(选择、复制、工具栏等) | 完整的交互功能(仅浏览器) |
+| **浏览器 API** | 不使用 | 最小(事件处理程序) | 完全使用 |
+| **性能** | 最佳 - 仅静态 HTML | 良好 - 静态渲染 + 事件委托 | 较重 - 完整的编辑器内部结构 |
+| **打包体积** | 最小 | 小 | 最大 |
+| **使用场景** | 服务器渲染、HTML 导出 | 需要基本交互的客户端内容 | 需要所有功能的完整只读编辑器 |
+| **推荐** | 无任何交互的 SSR/RSC | 需要轻量级交互的客户端内容 | 具有复杂交互需求的客户端 |
+
+## RSC/SSR 示例
+
+在 Next.js App Router(或类似的 RSC 环境)中,`` 可以直接在服务器组件中使用:
+
+```tsx title="app/preview/page.tsx (RSC)"
+import { PlateStatic } from 'platejs';
+import { createSlateEditor } from 'platejs';
+// 示例基础插件(确保非 /react 导入)
+// import { BaseHeadingPlugin } from '@platejs/basic-nodes';
+import { staticComponents } from '@/components/static-components'; // 您的静态组件映射
+
+export default async function Page() {
+ // 在服务器端获取或定义内容
+ const serverContent = [
+ { type: 'h1', children: [{ text: '在服务器上渲染! 🎉' }] },
+ { type: 'p', children: [{ text: '此内容是静态的且在服务器端渲染。' }] },
+ ];
+
+ const editor = createSlateEditor({
+ // plugins: [BaseHeadingPlugin, /* ...其他基础插件 */],
+ plugins: [], // 添加您的基础插件
+ components: staticComponents,
+ value: serverContent,
+ });
+
+ return (
+
+ );
+}
+```
+这会在服务器上将内容渲染为 HTML,而无需为 `PlateStatic` 本身提供客户端 JavaScript 包。
+
+## 与 `serializeHtml` 配合使用
+
+要生成完整的 HTML 字符串(例如用于电子邮件、PDF 或外部系统),请使用 `serializeHtml`。它在内部使用 ``。
+
+```ts title="lib/html-serializer.ts"
+import { createSlateEditor, serializeHtml } from 'platejs';
+import { staticComponents } from '@/components/static-components';
+// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes';
+
+async function getDocumentAsHtml(value: any[]) {
+ const editor = createSlateEditor({
+ plugins: [/* ...您的基础插件... */],
+ components: staticComponents,
+ value,
+ });
+
+ const html = await serializeHtml(editor, {
+ // editorComponent: PlateStatic, // 可选:默认为 PlateStatic
+ props: { className: 'prose max-w-none' }, // 示例:向根 div 传递属性
+ });
+
+ return html;
+}
+
+// 使用示例:
+// const mySlateValue = [ { type: 'h1', children: [{ text: '我的文档' }] } ];
+// getDocumentAsHtml(mySlateValue).then(console.log);
+```
+有关更多详细信息,请参阅 [HTML 序列化指南](/docs/html)。
+
+## API 参考
+
+### `` 属性
+
+```ts
+import type React from 'react';
+import type { Descendant } from 'slate';
+import type { PlateEditor } from 'platejs/core'; // 根据您的设置调整导入
+
+interface PlateStaticProps extends React.HTMLAttributes {
+ /**
+ * Plate 编辑器实例,通过 `createSlateEditor` 创建。
+ * 必须包含与要渲染的内容相关的插件和组件。
+ */
+ editor: PlateEditor;
+
+ /**
+ * 可选的 Plate `Value`(`Descendant` 节点数组)。
+ * 如果提供,将用于渲染而不是 `editor.children`。
+ */
+ value?: Descendant[];
+
+ /** 根 `div` 元素的内联 CSS 样式。 */
+ style?: React.CSSProperties;
+
+ // 其他 HTMLDivElement 属性如 `className`、`id` 等也受支持。
+}
+```
+
+- **`editor`**:使用 `createSlateEditor` 创建的 `PlateEditor` 实例,包括组件配置。
+- **`value`**:可选。如果提供,此 `Descendant` 节点数组将被渲染,覆盖当前在 `editor.children` 中的内容。
+
+## 下一步
+
+- 探索 [HTML 序列化](/docs/html) 以导出内容。
+- 了解在 [React Server Components](/docs/installation/rsc) 中使用 Plate。
+- 参考各个插件文档以了解它们的基础(非 React)导入。
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/static.mdx b/apps/www/content/docs/(guides)/static.mdx
new file mode 100644
index 0000000000..b091f65976
--- /dev/null
+++ b/apps/www/content/docs/(guides)/static.mdx
@@ -0,0 +1,366 @@
+---
+title: Static Rendering
+description: A minimal, memoized, read-only version of Plate with RSC/SSR support.
+---
+
+`` is a **fast, read-only** React component for rendering Plate content, optimized for **server-side** or **React Server Component** (RSC) environments. It avoids client-side editing logic and memoizes node renders for better performance compared to using [``](/docs/api/core/plate-components) in read-only mode.
+
+It's a core part of [`serializeHtml`](/docs/api/core/plate-plugin#serializehtml) for HTML export and is ideal for any server or RSC context needing a non-interactive, presentational view of Plate content.
+
+## Key Advantages
+
+- **Server-Safe:** No browser API dependencies; works in SSR/RSC.
+- **No Plate Editor Overhead:** Excludes interactive features like selections or event handlers.
+- **Memoized Rendering:** Uses `_memo` and structural checks to re-render only changed nodes.
+- **Partial Re-Renders:** Changes in one part of the document don't force a full re-render.
+- **Lightweight:** Smaller bundle size as it omits interactive editor code.
+
+## When to Use ``
+
+- Generating HTML with [HTML Serialization](/docs/html).
+- Displaying server-rendered previews in Next.js (especially with RSC).
+- Building static sites with read-only Plate content.
+- Optimizing performance-critical read-only views.
+- Rendering AI-streaming content.
+
+
+ For interactive read-only features (like comment popovers or selections), use the standard `` component in the browser. For purely server-rendered, non-interactive content, `` is the recommended choice.
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to enable static rendering is with the `BaseEditorKit`, which includes pre-configured base plugins that work seamlessly with server-side rendering.
+
+
+
+### Add Kit
+
+```tsx
+import { createSlateEditor, PlateStatic } from 'platejs';
+import { BaseEditorKit } from '@/components/editor/editor-base-kit';
+
+const editor = createSlateEditor({
+ plugins: BaseEditorKit,
+ value: [
+ { type: 'h1', children: [{ text: 'Server-Rendered Title' }] },
+ { type: 'p', children: [{ text: 'This content is rendered statically.' }] },
+ ],
+});
+
+// Render statically
+export default function MyStaticPage() {
+ return ;
+}
+```
+
+### Example
+
+See a complete server-side static rendering example:
+
+
+
+
+
+## Manual Usage
+
+
+
+### Create a Slate Editor
+
+Initialize a Slate editor instance using `createSlateEditor` with your required plugins and components. This is analogous to using `usePlateEditor` for the interactive `` component.
+
+```tsx title="lib/plate-static-editor.ts"
+import { createSlateEditor } from 'platejs';
+// Import your desired base plugins (e.g., BaseHeadingPlugin, MarkdownPlugin)
+// Ensure you are NOT importing from /react subpaths for server environments.
+
+const editor = createSlateEditor({
+ plugins: [
+ // Add your list of base plugins here
+ // Example: BaseHeadingPlugin, MarkdownPlugin.configure({...})
+ ],
+ value: [ // Example initial value
+ {
+ type: 'p',
+ children: [{ text: 'Hello from a static Plate editor!' }],
+ },
+ ],
+});
+```
+
+### Define Static Node Components
+
+If your interactive editor uses client-side components (e.g., with `use client` or event handlers), you **must** create static, server-safe equivalents. These components should render pure HTML without browser-specific logic.
+
+```tsx title="components/ui/paragraph-node-static.tsx"
+import React from 'react';
+import type { SlateElementProps } from 'platejs';
+
+export function ParagraphElementStatic(props: SlateElementProps) {
+ return (
+
+ {props.children}
+
+ );
+}
+```
+Create similar static components for headings, images, links, etc.
+
+### Map Plugin Keys to Static Components
+
+Create an object that maps plugin keys or node types to their corresponding static React components, then pass it to the editor.
+
+```ts title="components/static-components.ts"
+import { ParagraphElementStatic } from './ui/paragraph-node-static';
+import { HeadingElementStatic } from './ui/heading-node-static';
+// ... import other static components
+
+export const staticComponents = {
+ p: ParagraphElementStatic,
+ h1: HeadingElementStatic,
+ // ... add mappings for all your element and leaf types
+};
+```
+
+### Render ``
+
+Use the `` component, providing the `editor` instance configured with your components.
+
+```tsx title="app/my-static-page/page.tsx (RSC Example)"
+import { PlateStatic } from 'platejs';
+import { createSlateEditor } from 'platejs';
+// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes'; // etc.
+import { staticComponents } from '@/components/static-components';
+
+export default async function MyStaticPage() {
+ // Example: Fetch or define editor value
+ const initialValue = [
+ { type: 'h1', children: [{ text: 'Server-Rendered Title' }] },
+ { type: 'p', children: [{ text: 'Content rendered statically.' }] },
+ ];
+
+ const editor = createSlateEditor({
+ plugins: [/* your base plugins */],
+ components: staticComponents,
+ value: initialValue,
+ });
+
+ return (
+
+ );
+}
+```
+
+
+ If you pass a `value` prop directly to ``, it will override `editor.children`.
+ ```tsx
+
+ ```
+
+
+### Memoization Details
+
+`` enhances performance through memoization:
+- Each `` and `` is wrapped in `React.memo`.
+- **Reference Equality:** Unchanged node references prevent re-renders.
+- **`_memo` Field:** Setting `node._memo = true` (or any stable value) on an element or leaf can force Plate to skip re-rendering that specific node, even if its content changes. This is useful for fine-grained control over updates.
+
+
+
+## Client-Side Alternative: `PlateView`
+
+For cases where you need **minimal interactivity** with static content, use ``. This component wraps `` and adds client-side event handlers for user interactions while maintaining the performance benefits of static rendering.
+
+### Example: Server Component with Both Static Views
+
+```tsx title="app/document/page.tsx"
+import { createStaticEditor } from 'platejs';
+import { PlateStatic } from 'platejs';
+import { BaseEditorKit } from '@/components/editor/editor-base-kit';
+import { InteractiveViewer } from './interactive-viewer';
+
+export default async function DocumentPage() {
+ const content = await fetchDocument(); // Your document data
+
+ // Server-side static editor
+ const editor = createStaticEditor({
+ plugins: BaseEditorKit,
+ value: content,
+ });
+
+ return (
+
+ {/* Pure static rendering - no interactivity */}
+
+
Static View (Server Rendered)
+
+
+
+ {/* Interactive view - rendered on client */}
+
+
Interactive View
+
+
+
+ );
+}
+```
+
+### Example: Client Component with PlateView
+
+```tsx title="app/document/interactive-viewer.tsx"
+'use client';
+
+import { usePlateViewEditor } from 'platejs/react';
+import { PlateView } from 'platejs/react';
+import { BaseEditorKit } from '@/components/editor/editor-base-kit';
+
+export function InteractiveViewer({ value }) {
+ const editor = usePlateViewEditor({
+ plugins: BaseEditorKit,
+ value,
+ });
+
+ return ;
+}
+```
+
+### Key Features of `PlateView`
+
+- **Client-side only**: Requires `'use client'` directive
+- **Adds interactivity**: Enables user interactions with the content (e.g., text selection, copying, future interactions like tooltips, highlights, etc.)
+- **Minimal overhead**: Still uses `PlateStatic` internally for rendering
+- **Use with `usePlateViewEditor`**: Creates a static editor optimized for view-only React components
+- **ViewPlugin included**: The static editor automatically includes `ViewPlugin` which provides event handling capabilities
+
+
+ `PlateView` cannot be used in Server Components. If you're passing an editor from a server component to a client component, you'll encounter serialization errors. Use `PlateStatic` on the server side, or create the editor client-side with `usePlateViewEditor`.
+
+
+## `PlateStatic` vs. `PlateView` vs. `Plate` + `readOnly`
+
+| Aspect | `` | `` | `` + `readOnly` |
+| --------------------- | ----------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------ |
+| **Environment** | Server/Client (SSR/RSC safe) | Client-only | Client-only |
+| **Interactivity** | None | Minimal (selection, copy, toolbar, etc.) | Full interactive features (browser-only) |
+| **Browser APIs** | Not used | Minimal (event handlers) | Full usage |
+| **Performance** | Best - static HTML only | Good - static rendering + event delegation | Heavier - full editor internals |
+| **Bundle Size** | Smallest | Small | Largest |
+| **Use Cases** | Server rendering, HTML export | Client-side content with basic interactions | Full read-only editor with all features |
+| **Recommendation** | SSR/RSC without any interactions | Client-side content needing light interactivity | Client-side with complex interactive needs |
+
+## RSC/SSR Example
+
+In a Next.js App Router (or similar RSC environment), `` can be used directly in Server Components:
+
+```tsx title="app/preview/page.tsx (RSC)"
+import { PlateStatic } from 'platejs';
+import { createSlateEditor } from 'platejs';
+// Example base plugins (ensure non-/react imports)
+// import { BaseHeadingPlugin } from '@platejs/basic-nodes';
+import { staticComponents } from '@/components/static-components'; // Your static components mapping
+
+export default async function Page() {
+ // Fetch or define content server-side
+ const serverContent = [
+ { type: 'h1', children: [{ text: 'Rendered on the Server! 🎉' }] },
+ { type: 'p', children: [{ text: 'This content is static and server-rendered.' }] },
+ ];
+
+ const editor = createSlateEditor({
+ // plugins: [BaseHeadingPlugin, /* ...other base plugins */],
+ plugins: [], // Add your base plugins
+ components: staticComponents,
+ value: serverContent,
+ });
+
+ return (
+
+ );
+}
+```
+This renders the content to HTML on the server without needing a client-side JavaScript bundle for `PlateStatic` itself.
+
+## Pairing with `serializeHtml`
+
+For generating a complete HTML string (e.g., for emails, PDFs, or external systems), use `serializeHtml`. It utilizes `` internally.
+
+```ts title="lib/html-serializer.ts"
+import { createSlateEditor, serializeHtml } from 'platejs';
+import { staticComponents } from '@/components/static-components';
+// import { BaseHeadingPlugin, ... } from '@platejs/basic-nodes';
+
+async function getDocumentAsHtml(value: any[]) {
+ const editor = createSlateEditor({
+ plugins: [/* ...your base plugins... */],
+ components: staticComponents,
+ value,
+ });
+
+ const html = await serializeHtml(editor, {
+ // editorComponent: PlateStatic, // Optional: Defaults to PlateStatic
+ props: { className: 'prose max-w-none' }, // Example: Pass props to the root div
+ });
+
+ return html;
+}
+
+// Example Usage:
+// const mySlateValue = [ { type: 'h1', children: [{ text: 'My Document' }] } ];
+// getDocumentAsHtml(mySlateValue).then(console.log);
+```
+For more details, see the [HTML Serialization guide](/docs/html).
+
+## API Reference
+
+### `` Props
+
+```ts
+import type React from 'react';
+import type { Descendant } from 'slate';
+import type { PlateEditor } from 'platejs/core'; // Adjust imports as per your setup
+
+interface PlateStaticProps extends React.HTMLAttributes {
+ /**
+ * The Plate editor instance, created via `createSlateEditor`.
+ * Must include plugins and components relevant to the content being rendered.
+ */
+ editor: PlateEditor;
+
+ /**
+ * Optional Plate `Value` (array of `Descendant` nodes).
+ * If provided, this will be used for rendering instead of `editor.children`.
+ */
+ value?: Descendant[];
+
+ /** Inline CSS styles for the root `div` element. */
+ style?: React.CSSProperties;
+
+ // Other HTMLDivElement attributes like `className`, `id`, etc., are also supported.
+}
+```
+
+- **`editor`**: An instance of `PlateEditor` created with `createSlateEditor`, including components configuration.
+- **`value`**: Optional. If provided, this array of `Descendant` nodes will be rendered, overriding the content currently in `editor.children`.
+
+## Next Steps
+
+- Explore [HTML Serialization](/docs/html) for exporting content.
+- Learn about using Plate in [React Server Components](/docs/installation/rsc).
+- Refer to individual plugin documentation for their base (non-React) imports.
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/troubleshooting.cn.mdx b/apps/www/content/docs/(guides)/troubleshooting.cn.mdx
new file mode 100644
index 0000000000..59866ab05d
--- /dev/null
+++ b/apps/www/content/docs/(guides)/troubleshooting.cn.mdx
@@ -0,0 +1,88 @@
+---
+title: 问题排查
+description: 使用 Plate 时常见问题的解决方案。
+---
+
+## 依赖冲突
+
+使用 Plate 的项目中,依赖版本不匹配或冲突是常见问题来源。本节介绍如何识别和解决这类问题。
+
+### 使用 `depset` 管理 Plate 包版本
+
+确保所有 ``@udecode/*`` 包(包括 Plate 及其相关插件)同步到一致且兼容的版本集,推荐使用 [`depset`](https://npmjs.com/package/depset) 命令行工具。
+
+**为什么选择 `depset`?**
+- 它简化了 ``@udecode`` 范围内多个包的升级或对齐操作
+- 防止因部分 Plate 包使用一个版本而其他包使用可能不兼容的不同版本导致的问题
+
+**使用方法:**
+
+要将 ``@udecode`` 范围内的所有包升级或对齐到特定目标版本(如 ``45.0.1``),在项目根目录运行:
+```bash
+npx depset@latest @udecode 45.0.1
+```
+
+要将所有 ``@udecode`` 包升级到主版本 ``46`` 以下的最新版本(例如如果 ``45.x.y`` 是最新发布版本,则会选择这些):
+```bash
+npx depset@latest @udecode 45
+```
+
+- 将 ````(如 ``45.0.1`` 或 ``45``)替换为你想要的版本标识符
+- ``depset`` 会更新你的 ``package.json``
+
+### 示例:多个 Plate 实例
+
+**问题:** 出现意外行为或"hooks can only be called inside a component"错误
+
+**根本原因:** 项目中存在不兼容版本的 Plate 包。通常意味着不同的 ``platejs*`` 包或 ``@platejs/core`` 使用了不同版本且未设计为协同工作
+
+**诊断方法:** 检查多个 Plate 包版本:
+
+```bash
+# npm
+npm ls platejs @platejs/core
+
+# pnpm 或 yarn
+pnpm why platejs
+pnpm why @platejs/core
+```
+
+**解决方案:**
+主要解决方案是确保所有 ``@udecode/*`` 包都更新到各自最新且设计为相互兼容的版本。这样可以防止项目中某些 Plate 包版本过旧或过新导致的不匹配问题。使用上述的 ``depset`` 工具。
+
+### 示例:多个 Slate 实例
+
+**问题:** 编辑器功能可能无法正常工作
+
+**根本原因:** 包管理器有时会安装不匹配的 Slate 依赖版本。例如,`pnpm` 可能安装 `slate` 0.112.2 版本而非所需的 0.111.0 版本
+
+**诊断方法:** 检查多个 Slate 版本:
+
+```bash
+# npm
+npm ls slate slate-react slate-dom
+
+# pnpm 或 yarn
+pnpm why slate
+pnpm why slate-react
+pnpm why slate-dom
+```
+
+**解决方案:** 按顺序尝试以下解决方案:
+
+1. 从 `package.json` 中移除 `slate*` 依赖(如果有)。Plate 会管理这些依赖
+
+2. 使用上述的 ``depset`` 工具
+
+3. 强制使用一致的 Slate 依赖版本:
+
+```jsonc
+// package.json
+{
+ "resolutions": {
+ "slate": "0.114.0",
+ "slate-dom": "0.114.0",
+ "slate-react": "0.114.2"
+ }
+}
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/troubleshooting.mdx b/apps/www/content/docs/(guides)/troubleshooting.mdx
new file mode 100644
index 0000000000..4df829df77
--- /dev/null
+++ b/apps/www/content/docs/(guides)/troubleshooting.mdx
@@ -0,0 +1,89 @@
+---
+title: Troubleshooting
+description: Solutions for common issues when working with Plate.
+---
+
+## Dependency Conflicts
+
+A common source of issues in projects using Plate is mismatched or conflicting versions of dependencies. This section outlines how to identify and resolve such conflicts.
+
+### Managing Plate Package Versions with `depset`
+
+The recommended way to ensure all your ``@udecode/*`` packages (including Plate and its related plugins) are synchronized to a consistent and compatible set of versions is by using the [`depset`](https://npmjs.com/package/depset) command-line tool.
+
+**Why `depset`?**
+- It simplifies upgrading or aligning multiple packages within the ``@udecode`` scope.
+- It helps prevent issues caused by having some Plate packages on one version and others on a different, potentially incompatible, version.
+
+**Usage:**
+
+To upgrade or align all packages in the ``@udecode`` scope to a specific target version (e.g., ``45.0.1``), run the following in your project root:
+```bash
+npx depset@latest @udecode 45.0.1
+```
+
+To upgrade all ``@udecode`` packages to the latest versions that are less than major version ``46`` (e.g., if ``45.x.y`` are the latest releases, it will pick those):
+```bash
+npx depset@latest @udecode 45
+```
+
+- Replace ```` (e.g., ``45.0.1`` or ``45``) with your desired version specifier.
+- ``depset`` will update your ``package.json``.
+
+### Example: Multiple Plate Instances
+
+**Problem:** Unexpected behavior or "hooks can only be called inside a component" errors.
+
+**Root Cause:** Having incompatible versions of Plate packages in your project. This often means different ``platejs*`` packages or ``@platejs/core`` are at different versions that weren't designed to work together.
+
+**Diagnosis:** Check for multiple Plate package versions:
+
+```bash
+# npm
+npm ls platejs @platejs/core
+
+# pnpm or yarn
+pnpm why platejs
+pnpm why @platejs/core
+```
+
+**Solution:**
+The primary solution is to ensure all your ``@udecode/*`` packages are updated to their latest respective versions that are compatible and designed to work together. This prevents mismatches where one Plate package might be too old or too new for others in your project. Use the ``depset`` tool as described above.
+
+### Example: Multiple Slate Instances
+
+**Problem:** Editor features may not work correctly.
+
+**Root Cause:** Package managers sometimes install mismatched versions of Slate dependencies. For example, `pnpm` might install `slate` version 0.112.2 instead of the required 0.111.0.
+
+**Diagnosis:** Check for multiple Slate versions:
+
+```bash
+# npm
+npm ls slate slate-react slate-dom
+
+# pnpm or yarn
+pnpm why slate
+pnpm why slate-react
+pnpm why slate-dom
+```
+
+**Solution:** Try these solutions in the order they are listed:
+
+1. Remove `slate*` dependencies from your `package.json` if any. Plate is managing those.
+
+2. Use the ``depset`` tool as described above.
+
+2. Force consistent Slate dependency versions:
+
+```jsonc
+// package.json
+{
+ "resolutions": {
+ "slate": "0.114.0",
+ "slate-dom": "0.114.0",
+ "slate-react": "0.114.2"
+ }
+}
+```
+
diff --git a/apps/www/content/docs/(guides)/typescript.cn.mdx b/apps/www/content/docs/(guides)/typescript.cn.mdx
new file mode 100644
index 0000000000..51fdf45d13
--- /dev/null
+++ b/apps/www/content/docs/(guides)/typescript.cn.mdx
@@ -0,0 +1,222 @@
+---
+title: TypeScript
+description: 配置 TypeScript (tsconfig) 以使用 Plate,包括模块解析解决方案。
+---
+
+Plate 提供 ESM 格式的包,这需要特定的 TypeScript(和打包工具)配置来确保兼容性,特别是在导入子路径模块如 `platejs/react` 时。以下是几种解决方案和变通方法,以确保 TypeScript 正常工作。
+
+## 快速总结
+
+1. **推荐(最简单):** 使用 TypeScript **5.0+** 并在 `tsconfig.json` 中设置 `"moduleResolution": "bundler"`。
+2. **替代方案(Node 解析):** 保留 `"moduleResolution": "node"` 并将路径映射到 `dist/react`(并可能在打包工具配置中添加别名)。
+3. **更新依赖包:** 使用 `depset` 升级 Plate 依赖项。
+
+## 推荐方案:`"moduleResolution": "bundler"`
+
+对于现代打包工具(如 Vite、Next.js 14 等),最简单的方法是启用 TypeScript 新的 "bundler" 解析模式。示例:
+
+```jsonc
+// tsconfig.json
+{
+ "compilerOptions": {
+ // ...
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ // ...
+ }
+}
+```
+
+这使 TypeScript 的解析逻辑更接近现代打包工具和 ESM 包的行为。以下是 [Plate 模板](https://github.com/udecode/plate-template) 中的一个工作示例:
+
+```jsonc
+{
+ "compilerOptions": {
+ "strict": false,
+ "strictNullChecks": true,
+ "allowUnusedLabels": false,
+ "allowUnreachableCode": false,
+ "exactOptionalPropertyTypes": false,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitOverride": true,
+ "noImplicitReturns": false,
+ "noPropertyAccessFromIndexSignature": false,
+ "noUncheckedIndexedAccess": false,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+
+ "isolatedModules": true,
+
+ "allowJs": true,
+ "checkJs": false,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "jsx": "preserve",
+ "module": "esnext",
+ "target": "es2022",
+ "moduleResolution": "bundler",
+ "moduleDetection": "force",
+ "resolveJsonModule": true,
+ "noEmit": true,
+ "incremental": true,
+ "sourceMap": true,
+
+ "baseUrl": "src",
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ ".next/types/**/*.ts",
+ "src/**/*.ts",
+ "src/**/*.tsx"
+ ],
+ "exclude": ["node_modules"]
+}
+```
+
+- **`"moduleResolution": "bundler"`** 是在 TypeScript 5.0 中引入的。
+- 如果你的 TypeScript 版本低于 5.0,**必须**升级或继续使用 `"moduleResolution": "node"` 并手动配置路径别名。
+
+```jsonc
+// package.json
+{
+ "devDependencies": {
+ "typescript": "^5.0.0"
+ }
+}
+```
+
+如果看到类似 `TS5023: Unknown compiler option 'moduleResolution'` 的错误(针对 `bundler`),很可能是因为你的 TypeScript 版本低于 5.0。
+
+## 替代方案:`"moduleResolution": "node"` + 路径别名
+
+如果无法升级到 TS 5.0 或更改解析模式:
+
+1. 保留 `"moduleResolution": "node"`。
+2. 在 `tsconfig.json` 中使用 `paths` 将每个 Plate 子路径导入映射到其 `dist/react` 类型。
+3. 在打包工具配置中为这些路径添加别名。
+
+### 示例 `tsconfig.json`
+
+```jsonc
+{
+ "compilerOptions": {
+ "moduleResolution": "node",
+ "paths": {
+ "platejs/react": [
+ "./node_modules/platejs/dist/react/index.d.ts"
+ ],
+ "@platejs/core/react": [
+ "./node_modules/@platejs/core/dist/react/index.d.ts"
+ ],
+ "@platejs/list/react": [
+ "./node_modules/@platejs/list/dist/react/index.d.ts"
+ ]
+ // ...为所有 @platejs/*/react 包重复此操作
+ }
+ }
+}
+```
+
+### 示例 `vite.config.ts`
+
+```ts
+import { defineConfig } from 'vite';
+import path from 'path';
+
+export default defineConfig({
+ resolve: {
+ alias: {
+ 'platejs/react': path.resolve(
+ __dirname,
+ 'node_modules/platejs/dist/react'
+ ),
+ '@platejs/core/react': path.resolve(
+ __dirname,
+ 'node_modules/@platejs/core/dist/react'
+ ),
+ '@platejs/list/react': path.resolve(
+ __dirname,
+ 'node_modules/@platejs/list/dist/react'
+ ),
+
+ // 非 /react 基础别名:
+ 'platejs': path.resolve(
+ __dirname,
+ 'node_modules/platejs'
+ ),
+ '@platejs/core': path.resolve(
+ __dirname,
+ 'node_modules/@platejs/core'
+ ),
+ '@platejs/list': path.resolve(
+ __dirname,
+ 'node_modules/@platejs/list'
+ )
+ }
+ }
+});
+```
+
+**注意:**
+- 你需要为每一个使用的 `@platejs/*/react` 导入都进行此操作。
+- 对于测试/Jest,需要通过 `moduleNameMapper` 或类似方式复制这些别名。
+
+## 确保 Plate 版本一致
+
+假设你正在升级一个包到 `42.0.3`,请确保所有 `platejs*` 包都升级到 **最新版本(最高到 `42.0.3`)**(如果某个包没有 `42.0.3` 版本,可以保持在 `42.0.2`)。版本混用通常会导致不兼容问题。
+
+为了轻松管理和同步你的 `platejs*` 包版本,可以使用 `depset` CLI。例如,确保所有 `@udecode` 作用域的包都对齐到与 `42.x.y` 兼容的最新版本:
+
+```bash
+npx depset@latest @udecode 42
+```
+
+或者,指定特定版本如 `42.0.3`(如果可用,这将把所有包设置为 `42.0.3`,否则设置为之前的最新版本):
+
+```bash
+npx depset@latest @udecode 42.0.3
+```
+
+这有助于防止版本冲突,确保所有相关的 Plate 包都使用兼容的版本。
+
+## 常见问题
+
+> 我将 `moduleResolution` 改为 `bundler`,但旧的导入出错了。
+
+如果你的代码库有旧的 TypeScript 用法或依赖 `node` 解析,可以尝试路径别名方案,或者完全迁移到 TS 5+ / ESM 环境。
+
+> 我看到 `TS2305` 错误,提示缺少导出。这是解析错误还是真的缺少导出?
+
+可能是两者之一:
+- 如果整个包“找不到”,可能是解析问题。
+- 如果是“没有导出的成员”,请检查导入拼写是否正确(没有拼写错误),并确认你安装的版本确实包含该导出。
+
+> `moduleResolution: bundler` 需要的最低 TS 版本是多少?
+
+TypeScript 5.0 或更高版本。
+
+> 我们切换到 bundler 解析模式,但项目中的一些旧库出错了。
+
+如果你的旧库不支持 ESM,可以继续使用 `node` 解析模式并手动配置路径别名。一些大型代码库会逐步升级或为遗留代码创建单独的构建管道。
+
+> 在 Jest 中看到错误,但在 Vite 中没有。
+
+你需要在 Jest 中复制别名/解析配置。例如:
+
+```js
+// jest.config.js
+module.exports = {
+ // ...
+ moduleNameMapper: {
+ '^platejs/react$': '/node_modules/platejs/dist/react',
+ '^@platejs/core/react$': '/node_modules/@platejs/core/dist/react',
+ // ...
+ }
+};
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/typescript.mdx b/apps/www/content/docs/(guides)/typescript.mdx
new file mode 100644
index 0000000000..f23fe35674
--- /dev/null
+++ b/apps/www/content/docs/(guides)/typescript.mdx
@@ -0,0 +1,223 @@
+---
+title: TypeScript
+description: Configure TypeScript (tsconfig) for using Plate, including module resolution solutions.
+---
+
+Plate provides ESM packages, which require certain TypeScript (and bundler) configurations to ensure compatibility, especially when importing subpath modules like `platejs/react`. Below are several solutions and workarounds to make TypeScript happy.
+
+## Quick Summary
+
+1. **Recommended (Easiest):** Use TypeScript **5.0+** and set `"moduleResolution": "bundler"` in your `tsconfig.json`.
+2. **Alternate (Node resolution):** Keep `"moduleResolution": "node"` and map paths to `dist/react` (and potentially alias them in your bundler config).
+3. **Up-to-date Packages:** Use `depset` to upgrade Plate dependencies.
+
+
+## Recommended: `"moduleResolution": "bundler"`
+
+The simplest approach for modern bundlers (Vite, Next.js 14, etc.) is to enable the new TypeScript "bundler" resolution mode. Example:
+
+```jsonc
+// tsconfig.json
+{
+ "compilerOptions": {
+ // ...
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ // ...
+ }
+}
+```
+
+This aligns TypeScript's resolution logic more closely with modern bundlers and ESM packages. Below is a working excerpt from [Plate template](https://github.com/udecode/plate-template):
+
+```jsonc
+{
+ "compilerOptions": {
+ "strict": false,
+ "strictNullChecks": true,
+ "allowUnusedLabels": false,
+ "allowUnreachableCode": false,
+ "exactOptionalPropertyTypes": false,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitOverride": true,
+ "noImplicitReturns": false,
+ "noPropertyAccessFromIndexSignature": false,
+ "noUncheckedIndexedAccess": false,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+
+ "isolatedModules": true,
+
+ "allowJs": true,
+ "checkJs": false,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "jsx": "preserve",
+ "module": "esnext",
+ "target": "es2022",
+ "moduleResolution": "bundler",
+ "moduleDetection": "force",
+ "resolveJsonModule": true,
+ "noEmit": true,
+ "incremental": true,
+ "sourceMap": true,
+
+ "baseUrl": "src",
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ ".next/types/**/*.ts",
+ "src/**/*.ts",
+ "src/**/*.tsx"
+ ],
+ "exclude": ["node_modules"]
+}
+```
+
+- **`"moduleResolution": "bundler"`** was introduced in TypeScript 5.0.
+- If your TS version is older than 5.0, you **must** upgrade or stick to `"moduleResolution": "node"` plus manual path aliases.
+
+```jsonc
+// package.json
+{
+ "devDependencies": {
+ "typescript": "^5.0.0"
+ }
+}
+```
+
+If you see an error like `TS5023: Unknown compiler option 'moduleResolution'` (for `bundler`), it likely means your TypeScript version is below 5.0.
+
+## Workaround: `"moduleResolution": "node"` + Path Aliases
+
+If upgrading your entire project to TS 5.0 or changing the resolution mode is not possible:
+
+1. Keep `"moduleResolution": "node"`.
+2. Map each Plate subpath import to its `dist/react` types in `tsconfig.json` using `paths`.
+3. Alias these paths in your bundler config.
+
+### Example `tsconfig.json`
+
+```jsonc
+{
+ "compilerOptions": {
+ "moduleResolution": "node",
+ "paths": {
+ "platejs/react": [
+ "./node_modules/platejs/dist/react/index.d.ts"
+ ],
+ "@platejs/core/react": [
+ "./node_modules/@platejs/core/dist/react/index.d.ts"
+ ],
+ "@platejs/list/react": [
+ "./node_modules/@platejs/list/dist/react/index.d.ts"
+ ]
+ // ...repeat for all @platejs/*/react packages
+ }
+ }
+}
+```
+
+### Example `vite.config.ts`
+
+```ts
+import { defineConfig } from 'vite';
+import path from 'path';
+
+export default defineConfig({
+ resolve: {
+ alias: {
+ 'platejs/react': path.resolve(
+ __dirname,
+ 'node_modules/platejs/dist/react'
+ ),
+ '@platejs/core/react': path.resolve(
+ __dirname,
+ 'node_modules/@platejs/core/dist/react'
+ ),
+ '@platejs/list/react': path.resolve(
+ __dirname,
+ 'node_modules/@platejs/list/dist/react'
+ ),
+
+ // Non-/react base aliases:
+ 'platejs': path.resolve(
+ __dirname,
+ 'node_modules/platejs'
+ ),
+ '@platejs/core': path.resolve(
+ __dirname,
+ 'node_modules/@platejs/core'
+ ),
+ '@platejs/list': path.resolve(
+ __dirname,
+ 'node_modules/@platejs/list'
+ )
+ }
+ }
+});
+```
+
+**Note:**
+- You must do this for every `@platejs/*/react` import you use.
+- For testing/Jest, replicate these aliases via `moduleNameMapper` or similar.
+
+## Ensure Matching Plate Versions
+
+Say you're upgrading one package to `42.0.3`, double-check that all your `platejs*` packages are on the **latest version up to `42.0.3`** (one package could stay at `42.0.2` if it has no `42.0.3` release). Mixing versions often leads to mismatches.
+
+To easily manage and synchronize your `platejs*` package versions, you can use the `depset` CLI. For example, to ensure all your `@udecode` scope packages are aligned to the latest compatible with version `42.x.y`:
+
+```bash
+npx depset@latest @udecode 42
+```
+
+Or, for a specific version like `42.0.3` (this will set all packages in the scope to `42.0.3` if available, or the latest before it if not):
+
+```bash
+npx depset@latest @udecode 42.0.3
+```
+
+This helps prevent version conflicts by ensuring all related Plate packages are on compatible versions.
+
+## FAQ
+
+> I updated `moduleResolution` to `bundler` but it broke my older imports."
+
+If your codebase has older TS usage or relies on `node` resolution, try the path alias approach or fully migrate to a TS 5+ / ESM environment.
+
+> "I'm seeing `TS2305` about missing exports. Is that a resolution error or a real missing export?"
+
+It can be either:
+- If the entire package is "not found," it's likely a resolution issue.
+- If it's specifically "no exported member," double-check that you spelled the import correctly (no typos) and that your installed version actually has that export.
+
+> "Which minimum TS version do I need for `moduleResolution: bundler`?"
+
+TypeScript 5.0 or higher.
+
+> "We switched to bundler resolution, but some older libraries in our project break."
+
+If your older libraries aren't ESM-friendly, you might stick to `node` resolution and do manual path aliases. Some large codebases gradually upgrade or create separate build pipelines for legacy code.
+
+> "We see the error in Jest but not in Vite."
+
+You'll need to replicate your alias/resolution changes for Jest. For example:
+
+```js
+// jest.config.js
+module.exports = {
+ // ...
+ moduleNameMapper: {
+ '^platejs/react$': '/node_modules/platejs/dist/react',
+ '^@platejs/core/react$': '/node_modules/@platejs/core/dist/react',
+ // ...
+ }
+};
+```
diff --git a/apps/www/content/docs/(guides)/unit-testing.cn.mdx b/apps/www/content/docs/(guides)/unit-testing.cn.mdx
new file mode 100644
index 0000000000..9fa5ac7e32
--- /dev/null
+++ b/apps/www/content/docs/(guides)/unit-testing.cn.mdx
@@ -0,0 +1,290 @@
+---
+title: 单元测试 Plate
+description: 学习如何对 Plate 编辑器及插件进行单元测试。
+---
+
+本指南概述了使用 `@platejs/test-utils` 对 Plate 插件和组件进行单元测试的最佳实践。
+
+## 安装
+
+```bash
+npm install @platejs/test-utils
+```
+
+## 测试设置
+
+在测试文件顶部添加 JSX 编译指示:
+
+```typescript
+/** @jsx jsx */
+
+import { jsx } from '@platejs/test-utils';
+
+jsx; // 避免 ESLint 报错
+```
+
+这允许你使用 JSX 语法来创建编辑器值。
+
+## 创建测试用例
+
+### 编辑器状态表示
+
+使用 JSX 表示编辑器状态:
+
+```typescript
+const input = (
+
+
+ Hello world
+
+
+) as any as PlateEditor;
+```
+
+节点元素如 ` `、` `、` ` 表示不同类型的节点。
+
+特殊元素如 ` `、` ` 和 ` ` 表示选择状态。
+
+### 测试转换操作
+
+1. 创建输入状态
+2. 定义预期输出状态
+3. 使用 `createPlateEditor` 设置编辑器
+4. 直接应用转换操作
+5. 断言编辑器的新状态
+
+测试加粗格式化的示例:
+
+```typescript
+it('应应用加粗格式化', () => {
+ const input = (
+
+
+ Hello
+ world
+
+
+
+ ) as any as PlateEditor;
+
+ const output = (
+
+
+ Hello world
+
+
+ ) as any as PlateEditor;
+
+ const editor = createPlateEditor({
+ plugins: [BoldPlugin],
+ value: input.children,
+ selection: input.selection,
+ });
+
+ // 直接应用转换
+ editor.tf.toggleMark('bold');
+
+ expect(editor.children).toEqual(output.children);
+});
+```
+
+### 测试选择操作
+
+测试操作如何影响编辑器的选择:
+
+```typescript
+it('应在退格时折叠选择', () => {
+ const input = (
+
+
+ He llo wor ld
+
+
+ ) as any as PlateEditor;
+
+ const output = (
+
+
+ He ld
+
+
+ ) as any as PlateEditor;
+
+ const editor = createPlateEditor({
+ value: input.children,
+ selection: input.selection,
+ });
+
+ editor.tf.deleteBackward();
+
+ expect(editor.children).toEqual(output.children);
+ expect(editor.selection).toEqual(output.selection);
+});
+```
+
+## 测试键盘事件
+
+当需要直接测试键盘处理程序时:
+
+```typescript
+it('应调用 onKeyDown 处理程序', () => {
+ const input = (
+
+
+ Hello world
+
+
+ ) as any as PlateEditor;
+
+ // 创建模拟处理程序以验证调用
+ const onKeyDownMock = jest.fn();
+
+ const editor = createPlateEditor({
+ value: input.children,
+ selection: input.selection,
+ plugins: [
+ {
+ key: 'test',
+ handlers: {
+ onKeyDown: onKeyDownMock,
+ },
+ },
+ ],
+ });
+
+ // 创建键盘事件
+ const event = new KeyboardEvent('keydown', {
+ key: 'Enter',
+ }) as any;
+
+ // 直接调用处理程序
+ editor.plugins.test.handlers.onKeyDown({
+ ...getEditorPlugin(editor, { key: 'test' }),
+ event,
+ });
+
+ // 验证处理程序被调用
+ expect(onKeyDownMock).toHaveBeenCalled();
+});
+```
+
+## 测试复杂场景
+
+对于表格等复杂插件,通过直接应用转换来测试各种场景:
+
+```typescript
+describe('表格插件', () => {
+ it('应插入表格', () => {
+ const input = (
+
+
+ Test
+
+
+ ) as any as PlateEditor;
+
+ const output = (
+
+ Test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) as any as PlateEditor;
+
+ const editor = createPlateEditor({
+ value: input.children,
+ selection: input.selection,
+ plugins: [TablePlugin],
+ });
+
+ // 直接调用转换
+ editor.tf.insertTable({ rows: 2, columns: 2 });
+
+ expect(editor.children).toEqual(output.children);
+ expect(editor.selection).toEqual(output.selection);
+ });
+});
+```
+
+## 测试带选项的插件
+
+测试不同插件选项如何影响行为:
+
+```typescript
+describe('当启用撤销时', () => {
+ it('应在删除时撤销文本格式', () => {
+ const input = (
+
+
+ 1/
+
+
+ ) as any;
+
+ const output = (
+
+
+ 1/4
+
+
+ ) as any;
+
+ const editor = createPlateEditor({
+ plugins: [
+ AutoformatPlugin.configure({
+ options: {
+ enableUndoOnDelete: true,
+ rules: [
+ {
+ format: '¼',
+ match: '1/4',
+ mode: 'text',
+ },
+ ],
+ },
+ }),
+ ],
+ value: input,
+ });
+
+ // 触发自动格式化
+ editor.tf.insertText('4');
+
+ // 模拟退格键
+ const event = new KeyboardEvent('keydown', {
+ key: 'backspace',
+ }) as any;
+
+ // 调用处理程序
+ editor.getPlugin({key: KEYS.autoformat}).handlers.onKeyDown({
+ ...getEditorPlugin(editor, AutoformatPlugin),
+ event,
+ });
+
+ // 当 enableUndoOnDelete: true 时,按退格键应恢复原始文本
+ expect(input.children).toEqual(output.children);
+ });
+});
+```
+
+## 模拟与真实转换
+
+虽然模拟可用于隔离特定行为,但 Plate 测试通常会在转换后评估实际的编辑器子节点和选择。这种方法确保插件能与整个编辑器状态正确协同工作。
\ No newline at end of file
diff --git a/apps/www/content/docs/(guides)/unit-testing.mdx b/apps/www/content/docs/(guides)/unit-testing.mdx
new file mode 100644
index 0000000000..d75b9a4ae9
--- /dev/null
+++ b/apps/www/content/docs/(guides)/unit-testing.mdx
@@ -0,0 +1,291 @@
+---
+title: Unit Testing Plate
+description: Learn how to unit test Plate editor and plugins.
+---
+
+This guide outlines best practices for unit testing Plate plugins and components using `@platejs/test-utils`.
+
+## Installation
+
+```bash
+npm install @platejs/test-utils
+```
+
+## Setting Up Tests
+
+Add the JSX pragma at the top of your test file:
+
+```typescript
+/** @jsx jsx */
+
+import { jsx } from '@platejs/test-utils';
+
+jsx; // so ESLint doesn't complain
+```
+
+This allows you to use JSX syntax for creating editor values.
+
+## Creating Test Cases
+
+### Editor State Representation
+
+Use JSX to represent editor states:
+
+```typescript
+const input = (
+
+
+ Hello world
+
+
+) as any as PlateEditor;
+```
+
+Node elements like ` `, ` `, ` ` represent different types of nodes.
+
+Special elements like ` `, ` `, and ` ` represent selection states.
+
+### Testing Transforms
+
+1. Create an input state
+2. Define the expected output state
+3. Use `createPlateEditor` to set up the editor
+4. Apply the transform(s) directly
+5. Assert the editor's new state
+
+Example testing bold formatting:
+
+```typescript
+it('should apply bold formatting', () => {
+ const input = (
+
+
+ Hello
+ world
+
+
+
+ ) as any as PlateEditor;
+
+ const output = (
+
+
+ Hello world
+
+
+ ) as any as PlateEditor;
+
+ const editor = createPlateEditor({
+ plugins: [BoldPlugin],
+ value: input.children,
+ selection: input.selection,
+ });
+
+ // Apply transform directly
+ editor.tf.toggleMark('bold');
+
+ expect(editor.children).toEqual(output.children);
+});
+```
+
+### Testing Selection
+
+Test how operations affect the editor's selection:
+
+```typescript
+it('should collapse selection on backspace', () => {
+ const input = (
+
+
+ He llo wor ld
+
+
+ ) as any as PlateEditor;
+
+ const output = (
+
+
+ He ld
+
+
+ ) as any as PlateEditor;
+
+ const editor = createPlateEditor({
+ value: input.children,
+ selection: input.selection,
+ });
+
+ editor.tf.deleteBackward();
+
+ expect(editor.children).toEqual(output.children);
+ expect(editor.selection).toEqual(output.selection);
+});
+```
+
+## Testing Key Events
+
+When you need to test keyboard handlers directly:
+
+```typescript
+it('should call the onKeyDown handler', () => {
+ const input = (
+
+
+ Hello world
+
+
+ ) as any as PlateEditor;
+
+ // Create a mock handler to verify it's called
+ const onKeyDownMock = jest.fn();
+
+ const editor = createPlateEditor({
+ value: input.children,
+ selection: input.selection,
+ plugins: [
+ {
+ key: 'test',
+ handlers: {
+ onKeyDown: onKeyDownMock,
+ },
+ },
+ ],
+ });
+
+ // Create the keyboard event
+ const event = new KeyboardEvent('keydown', {
+ key: 'Enter',
+ }) as any;
+
+ // Call the handler directly
+ editor.plugins.test.handlers.onKeyDown({
+ ...getEditorPlugin(editor, { key: 'test' }),
+ event,
+ });
+
+ // Verify the handler was called
+ expect(onKeyDownMock).toHaveBeenCalled();
+});
+```
+
+## Testing Complex Scenarios
+
+For complex plugins like tables, test various scenarios by directly applying transforms:
+
+```typescript
+describe('Table plugin', () => {
+ it('should insert a table', () => {
+ const input = (
+
+
+ Test
+
+
+ ) as any as PlateEditor;
+
+ const output = (
+
+ Test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) as any as PlateEditor;
+
+ const editor = createPlateEditor({
+ value: input.children,
+ selection: input.selection,
+ plugins: [TablePlugin],
+ });
+
+ // Call transform directly
+ editor.tf.insertTable({ rows: 2, columns: 2 });
+
+ expect(editor.children).toEqual(output.children);
+ expect(editor.selection).toEqual(output.selection);
+ });
+});
+```
+
+## Testing Plugins with Options
+
+Test how different plugin options affect behavior:
+
+```typescript
+describe('when undo is enabled', () => {
+ it('should undo text format upon delete', () => {
+ const input = (
+
+
+ 1/
+
+
+ ) as any;
+
+ const output = (
+
+
+ 1/4
+
+
+ ) as any;
+
+ const editor = createPlateEditor({
+ plugins: [
+ AutoformatPlugin.configure({
+ options: {
+ enableUndoOnDelete: true,
+ rules: [
+ {
+ format: '¼',
+ match: '1/4',
+ mode: 'text',
+ },
+ ],
+ },
+ }),
+ ],
+ value: input,
+ });
+
+ // Trigger the autoformat
+ editor.tf.insertText('4');
+
+ // Simulate backspace key
+ const event = new KeyboardEvent('keydown', {
+ key: 'backspace',
+ }) as any;
+
+ // Call the handler
+ editor.getPlugin({key: KEYS.autoformat}).handlers.onKeyDown({
+ ...getEditorPlugin(editor, AutoformatPlugin),
+ event,
+ });
+
+ // With enableUndoOnDelete: true, pressing backspace should restore the original text
+ expect(input.children).toEqual(output.children);
+ });
+});
+```
+
+## Mocking vs. Real Transforms
+
+While mocking can be useful for isolating specific behaviors, Plate tests often assess actual editor children and selection after transforms. This approach ensures that plugins work correctly with the entire editor state.
+
diff --git a/apps/www/content/docs/(plugins)/(ai)/ai.cn.mdx b/apps/www/content/docs/(plugins)/(ai)/ai.cn.mdx
new file mode 100644
index 0000000000..7269538b9c
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(ai)/ai.cn.mdx
@@ -0,0 +1,1101 @@
+---
+title: AI
+description: AI 驱动的写作辅助。
+docs:
+ - route: https://pro.platejs.org/docs/examples/ai
+ title: Plus
+---
+
+
+
+
+
+## 功能特点
+
+- **智能命令菜单**: 带有预定义 AI 命令的组合框界面,用于生成和编辑
+- **多种触发模式**:
+ - **光标模式**: 在块末尾用空格触发
+ - **选择模式**: 用选中的文本触发
+ - **块选择模式**: 用选中的块触发
+- **响应模式**:
+ - **聊天模式**: 预览响应并提供接受/拒绝选项
+ - **插入模式**: 直接插入内容并支持 markdown 流式传输
+- **智能内容处理**: 针对表格、代码块和复杂结构优化的分块处理
+- **流式响应**: 实时 AI 内容生成
+- **Markdown 集成**: 完全支持 AI 响应中的 Markdown 语法
+- **可自定义提示**: 用户和系统提示的模板系统
+- **内置 Vercel AI SDK 支持**: 即用型聊天 API 集成
+
+
+
+## Kit 使用
+
+
+
+### 安装
+
+添加 AI 功能最快的方法是使用 `AIKit`,它包含预配置的 `AIPlugin` 和 `AIChatPlugin`,以及光标覆盖和 markdown 支持及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`AIMenu`](/docs/components/ai-menu): 渲染 AI 命令界面
+- [`AILoadingBar`](/docs/components/ai-loading-bar): 显示 AI 处理状态
+- [`AIAnchorElement`](/docs/components/ai-anchor-element): AI 菜单的锚点元素
+- [`AILeaf`](/docs/components/ai-leaf): 渲染 AI 生成的内容并带有视觉区分
+
+### 添加 Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { AIKit } from '@/components/editor/plugins/ai-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...AIKit,
+ ],
+});
+```
+
+### 添加 API 路由
+
+AI 功能需要服务器端 API 端点。添加预配置的 AI 命令路由:
+
+
+
+### 配置环境
+
+确保在环境变量中设置了 OpenAI API 密钥:
+
+```bash title=".env.local"
+OPENAI_API_KEY="your-api-key"
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/ai @platejs/selection @platejs/markdown @platejs/basic-nodes
+```
+
+### 添加插件
+
+```tsx
+import { AIPlugin, AIChatPlugin } from '@platejs/ai/react';
+import { createPlateEditor } from 'platejs/react';
+import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...MarkdownKit, // AI 内容处理必需
+ AIPlugin,
+ AIChatPlugin,
+ ],
+});
+```
+
+- [`MarkdownKit`](/docs/markdown): 处理带有 Markdown 语法和 MDX 支持的 AI 响应所必需。
+- `AIPlugin`: 用于 AI 内容管理和转换的核心插件。
+- `AIChatPlugin`: 处理 AI 聊天界面、流式传输和用户交互。
+
+### 配置插件
+
+创建带有基本配置的扩展 `aiChatPlugin`:
+
+```tsx
+import type { AIChatPluginConfig } from '@platejs/ai/react';
+import type { UseChatOptions } from 'ai/react';
+
+import { KEYS, PathApi } from 'platejs';
+import { streamInsertChunk, withAIBatch } from '@platejs/ai';
+import { AIChatPlugin, AIPlugin, useChatChunk } from '@platejs/ai/react';
+import { usePluginOption } from 'platejs/react';
+import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
+import { AILoadingBar, AIMenu } from '@/components/ui/ai-menu';
+import { AIAnchorElement, AILeaf } from '@/components/ui/ai-node';
+
+export const aiChatPlugin = AIChatPlugin.extend({
+ options: {
+ chatOptions: {
+ api: '/api/ai/command',
+ body: {},
+ } as UseChatOptions,
+ },
+ render: {
+ afterContainer: AILoadingBar,
+ afterEditable: AIMenu,
+ node: AIAnchorElement,
+ },
+ shortcuts: { show: { keys: 'mod+j' } },
+});
+
+const plugins = [
+ // ...其他插件,
+ ...MarkdownKit,
+ AIPlugin.withComponent(AILeaf),
+ aiChatPlugin,
+];
+```
+
+- `chatOptions`: Vercel AI SDK `useChat` 钩子的配置。
+- `render`: AI 界面的 UI 组件。
+- `shortcuts`: 键盘快捷键(`Cmd+J` 显示 AI 菜单)。
+
+### 使用 useHooks 添加流式传输
+
+`useChatChunk` 钩子实时处理流式 AI 响应,处理内容插入和块管理。它监控聊天状态并处理传入的文本块,在它们到达时将它们插入编辑器:
+
+```tsx
+export const aiChatPlugin = AIChatPlugin.extend({
+ // ... 之前的选项
+ useHooks: ({ editor, getOption }) => {
+ const mode = usePluginOption(
+ { key: KEYS.aiChat } as AIChatPluginConfig,
+ 'mode'
+ );
+
+ useChatChunk({
+ onChunk: ({ chunk, isFirst, nodes }) => {
+ if (isFirst && mode == 'insert') {
+ editor.tf.withoutSaving(() => {
+ editor.tf.insertNodes(
+ {
+ children: [{ text: '' }],
+ type: KEYS.aiChat,
+ },
+ {
+ at: PathApi.next(editor.selection!.focus.path.slice(0, 1)),
+ }
+ );
+ });
+ editor.setOption(AIChatPlugin, 'streaming', true);
+ }
+
+ if (mode === 'insert' && nodes.length > 0) {
+ withAIBatch(
+ editor,
+ () => {
+ if (!getOption('streaming')) return;
+ editor.tf.withScrolling(() => {
+ streamInsertChunk(editor, chunk, {
+ textProps: {
+ ai: true,
+ },
+ });
+ });
+ },
+ { split: isFirst }
+ );
+ }
+ },
+ onFinish: () => {
+ editor.setOption(AIChatPlugin, 'streaming', false);
+ editor.setOption(AIChatPlugin, '_blockChunks', '');
+ editor.setOption(AIChatPlugin, '_blockPath', null);
+ },
+ });
+ },
+});
+```
+
+- `onChunk`: 处理每个流式块,在第一个块创建 AI 节点并实时插入内容
+- `onFinish`: 响应完成时清理流式状态
+- 使用 `withAIBatch` 和 `streamInsertChunk` 进行优化的内容插入
+
+### 系统提示
+
+系统提示定义了 AI 的角色和行为。您可以在扩展插件中自定义 `systemTemplate`:
+
+```tsx
+export const customAIChatPlugin = AIChatPlugin.extend({
+ options: {
+ systemTemplate: ({ isBlockSelecting, isSelecting }) => {
+ const customSystem = `你是一个专门从事代码和 API 文档的技术文档助手。
+
+规则:
+- 提供准确、结构良好的技术内容
+- 使用适当的代码格式和语法高亮
+- 包含相关示例和最佳实践
+- 保持一致的文档风格
+- 重要:除非明确要求,否则不要删除或修改自定义 MDX 标签。
+- 重要:区分指令和问题。`;
+
+ return isBlockSelecting
+ ? `${customSystem}
+- 表示用户选择并想要修改或询问的完整文本块。
+- 你的响应应该是对整个 的直接替换。
+- 除非另有明确指示,否则保持所选块的整体结构和格式。
+
+{block}
+ `
+ : isSelecting
+ ? `${customSystem}
+- 是包含用户选择的文本块,提供上下文。
+- 是用户在块中选择并想要修改或询问的特定文本。
+- 考虑 提供的上下文,但只修改 。
+
+{block}
+
+
+{selection}
+ `
+ : `${customSystem}
+- 是用户当前正在处理的文本块。
+
+
+{block}
+ `;
+ },
+ // ...其他选项
+ },
+}),
+```
+
+### 用户提示
+
+自定义用户提示在扩展插件中的格式和上下文:
+
+```tsx
+export const customAIChatPlugin = AIChatPlugin.extend({
+ options: {
+ promptTemplate: ({ isBlockSelecting, isSelecting }) => {
+ return isBlockSelecting
+ ? `
+如果是问题,请提供关于 的有帮助且简洁的回答。
+如果是指令,请仅提供替换整个 的内容。不要解释。
+分析并改进以下内容块,保持结构和清晰度。
+永远不要写入 或 。
+
+{prompt} 关于 `
+ : isSelecting
+ ? `
+如果是问题,请提供关于 的有帮助且简洁的回答。
+如果是指令,请仅提供替换 的文本。不要解释。
+确保它无缝融入 。如果 为空,写一个随机句子。
+永远不要写入 或 。
+
+{prompt} 关于 `
+ : `
+重要:永远不要写入 。
+自然地继续或改进内容。
+
+{prompt}`;
+ },
+ // ...其他选项
+ },
+}),
+```
+
+### 添加 API 路由
+
+创建一个针对不同内容类型优化的流式 API 路由处理程序:
+
+```tsx title="app/api/ai/command/route.ts"
+import type { TextStreamPart, ToolSet } from 'ai';
+import type { NextRequest } from 'next/server';
+
+import { createOpenAI } from '@ai-sdk/openai';
+import { InvalidArgumentError } from '@ai-sdk/provider';
+import { delay as originalDelay } from '@ai-sdk/provider-utils';
+import { convertToCoreMessages, streamText } from 'ai';
+import { NextResponse } from 'next/server';
+
+const CHUNKING_REGEXPS = {
+ line: /\n+/m,
+ list: /.{8}/m,
+ word: /\S+\s+/m,
+};
+
+export async function POST(req: NextRequest) {
+ const { apiKey: key, messages, system } = await req.json();
+
+ const apiKey = key || process.env.OPENAI_API_KEY;
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: '缺少 OpenAI API 密钥。' },
+ { status: 401 }
+ );
+ }
+
+ const openai = createOpenAI({ apiKey });
+
+ let isInCodeBlock = false;
+ let isInTable = false;
+ let isInList = false;
+ let isInLink = false;
+
+ try {
+ const result = streamText({
+ experimental_transform: smoothStream({
+ chunking: (buffer) => {
+ // 检测内容类型以优化分块
+ if (/```[^\s]+/.test(buffer)) {
+ isInCodeBlock = true;
+ } else if (isInCodeBlock && buffer.includes('```')) {
+ isInCodeBlock = false;
+ }
+
+ if (buffer.includes('http')) {
+ isInLink = true;
+ } else if (buffer.includes('https')) {
+ isInLink = true;
+ } else if (buffer.includes('\n') && isInLink) {
+ isInLink = false;
+ }
+
+ if (buffer.includes('*') || buffer.includes('-')) {
+ isInList = true;
+ } else if (buffer.includes('\n') && isInList) {
+ isInList = false;
+ }
+
+ if (!isInTable && buffer.includes('|')) {
+ isInTable = true;
+ } else if (isInTable && buffer.includes('\n\n')) {
+ isInTable = false;
+ }
+
+ // 根据内容类型选择分块策略
+ let match;
+ if (isInCodeBlock || isInTable || isInLink) {
+ match = CHUNKING_REGEXPS.line.exec(buffer);
+ } else if (isInList) {
+ match = CHUNKING_REGEXPS.list.exec(buffer);
+ } else {
+ match = CHUNKING_REGEXPS.word.exec(buffer);
+ }
+
+ if (!match) return null;
+ return buffer.slice(0, match.index) + match?.[0];
+ },
+ delayInMs: () => (isInCodeBlock || isInTable ? 100 : 30),
+ }),
+ maxTokens: 2048,
+ messages: convertToCoreMessages(messages),
+ model: openai('gpt-4o'),
+ system: system,
+ });
+
+ return result.toDataStreamResponse();
+ } catch {
+ return NextResponse.json(
+ { error: '处理 AI 请求失败' },
+ { status: 500 }
+ );
+ }
+}
+
+// 用于优化分块的平滑流实现
+function smoothStream({
+ _internal: { delay = originalDelay } = {},
+ chunking = 'word',
+ delayInMs = 10,
+}: {
+ _internal?: {
+ delay?: (delayInMs: number | null) => Promise;
+ };
+ chunking?: ChunkDetector | RegExp | 'line' | 'word';
+ delayInMs?: delayer | number | null;
+} = {}): (options: {
+ tools: TOOLS;
+}) => TransformStream, TextStreamPart> {
+ let detectChunk: ChunkDetector;
+
+ if (typeof chunking === 'function') {
+ detectChunk = (buffer) => {
+ const match = chunking(buffer);
+ if (match == null) return null;
+ if (match.length === 0) {
+ throw new Error(`分块函数必须返回非空字符串。`);
+ }
+ if (!buffer.startsWith(match)) {
+ throw new Error(
+ `分块函数必须返回缓冲区前缀的匹配项。`
+ );
+ }
+ return match;
+ };
+ } else {
+ const chunkingRegex =
+ typeof chunking === 'string' ? CHUNKING_REGEXPS[chunking] : chunking;
+
+ if (chunkingRegex == null) {
+ throw new InvalidArgumentError({
+ argument: 'chunking',
+ message: `分块必须是 "word" 或 "line" 或 RegExp。收到: ${chunking}`,
+ });
+ }
+
+ detectChunk = (buffer) => {
+ const match = chunkingRegex.exec(buffer);
+ if (!match) return null;
+ return buffer.slice(0, match.index) + match?.[0];
+ };
+ }
+
+ return () => {
+ let buffer = '';
+
+ return new TransformStream, TextStreamPart>({
+ async transform(chunk, controller) {
+ if (chunk.type !== 'text-delta') {
+ if (buffer.length > 0) {
+ controller.enqueue({ textDelta: buffer, type: 'text-delta' });
+ buffer = '';
+ }
+ controller.enqueue(chunk);
+ return;
+ }
+
+ buffer += chunk.textDelta;
+ let match;
+
+ while ((match = detectChunk(buffer)) != null) {
+ controller.enqueue({ textDelta: match, type: 'text-delta' });
+ buffer = buffer.slice(match.length);
+
+ const _delayInMs =
+ typeof delayInMs === 'number'
+ ? delayInMs
+ : (delayInMs?.(buffer) ?? 10);
+
+ await delay(_delayInMs);
+ }
+ },
+ });
+ };
+}
+```
+
+然后在 `.env.local` 中设置你的 `OPENAI_API_KEY`。
+
+### 添加工具栏按钮
+
+你可以在[工具栏](/docs/toolbar)中添加 [`AIToolbarButton`](/docs/components/ai-toolbar-button) 来打开 AI 菜单。
+
+
+
+## 键盘快捷键
+
+
+
+ 在空块中打开 AI 菜单(光标模式)
+
+
+ 打开 AI 菜单(光标或选择模式)
+
+ 关闭 AI 菜单
+
+
+## Plate Plus
+
+
+
+## 自定义
+
+### 添加自定义 AI 命令
+
+
+
+你可以通过向 `aiChatItems` 对象添加新项目并更新菜单状态项目来扩展 AI 菜单。
+
+#### 简单自定义命令
+
+添加一个提交自定义提示的基本命令:
+
+```tsx
+// 添加到你的 ai-menu.tsx aiChatItems 对象
+summarizeInBullets: {
+ icon: ,
+ label: '以要点形式总结',
+ value: 'summarizeInBullets',
+ onSelect: ({ editor }) => {
+ void editor.getApi(AIChatPlugin).aiChat.submit({
+ prompt: '将此内容总结为要点',
+ });
+ },
+},
+```
+
+#### 复杂逻辑命令
+
+创建在提交前具有客户端逻辑的命令:
+
+```tsx
+generateTOC: {
+ icon: ,
+ label: '生成目录',
+ value: 'generateTOC',
+ onSelect: ({ editor }) => {
+ // 检查文档是否有标题
+ const headings = editor.api.nodes({
+ match: (n) => ['h1', 'h2', 'h3'].includes(n.type as string),
+ });
+
+ if (headings.length === 0) {
+ void editor.getApi(AIChatPlugin).aiChat.submit({
+ mode: 'insert',
+ prompt: '为此文档创建带有示例标题的目录',
+ });
+ } else {
+ void editor.getApi(AIChatPlugin).aiChat.submit({
+ mode: 'insert',
+ prompt: '根据现有标题生成目录',
+ });
+ }
+ },
+},
+```
+
+#### 理解菜单状态
+
+AI 菜单根据用户选择和 AI 响应状态适应不同的上下文:
+
+```tsx
+const menuState = React.useMemo(() => {
+ // 如果 AI 已经响应,显示建议操作
+ if (messages && messages.length > 0) {
+ return isSelecting ? 'selectionSuggestion' : 'cursorSuggestion';
+ }
+
+ // 如果还没有 AI 响应,显示命令操作
+ return isSelecting ? 'selectionCommand' : 'cursorCommand';
+}, [isSelecting, messages]);
+```
+
+**菜单状态:**
+- `cursorCommand`:无选择,无 AI 响应 → 显示生成命令(继续写作、总结等)
+- `selectionCommand`:文本已选择,无 AI 响应 → 显示编辑命令(改进写作、修正拼写等)
+- `cursorSuggestion`:无选择,AI 已响应 → 显示建议操作(接受、丢弃、重试)
+- `selectionSuggestion`:文本已选择,AI 已响应 → 显示替换操作(替换选择、在下方插入等)
+
+#### 更新菜单状态
+
+在 `menuStateItems` 中的适当菜单状态添加自定义命令:
+
+```tsx
+const menuStateItems: Record = {
+ cursorCommand: [
+ {
+ items: [
+ aiChatItems.generateTOC,
+ aiChatItems.summarizeInBullets,
+ // ... 现有项目
+ ],
+ },
+ ],
+ selectionCommand: [
+ {
+ items: [
+ aiChatItems.summarizeInBullets, // 也适用于选中的文本
+ // ... 现有项目
+ ],
+ },
+ ],
+ // ... 其他状态
+};
+```
+
+### 切换 AI 模型
+
+在 API 路由中配置不同的 AI 模型和提供商:
+
+```tsx title="app/api/ai/command/route.ts"
+import { createOpenAI } from '@ai-sdk/openai';
+import { createAnthropic } from '@ai-sdk/anthropic';
+
+export async function POST(req: NextRequest) {
+ const { model = 'gpt-4o', provider = 'openai', ...rest } = await req.json();
+
+ let aiProvider;
+
+ switch (provider) {
+ case 'anthropic':
+ aiProvider = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
+ break;
+ case 'openai':
+ default:
+ aiProvider = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
+ break;
+ }
+
+ const result = streamText({
+ model: aiProvider(model),
+ // ... 其他选项
+ });
+
+ return result.toDataStreamResponse();
+}
+```
+
+在 `aiChatPlugin` 中配置模型:
+
+```tsx
+export const aiChatPlugin = AIChatPlugin.extend({
+ options: {
+ chatOptions: {
+ api: '/api/ai/command',
+ body: {
+ model: 'gpt-4o-mini', // 或 'claude-4-sonnet'
+ provider: 'openai', // 或 'anthropic'
+ },
+ },
+ // ... 其他选项
+ },
+});
+```
+
+有关更多 AI 提供商和模型,请参阅 [Vercel AI SDK 文档](https://sdk.vercel.ai/providers/ai-sdk-providers)。
+
+### 自定义流式优化
+
+使用自定义分块策略优化特定内容类型的流式性能:
+
+```tsx title="app/api/ai/command/route.ts"
+const customChunking = (buffer: string) => {
+ // 检测 JSON 内容以进行较慢的分块
+ if (buffer.includes('{') && buffer.includes('}')) {
+ const jsonMatch = /\{[^}]*\}/g.exec(buffer);
+ if (jsonMatch) {
+ return buffer.slice(0, jsonMatch.index + jsonMatch[0].length);
+ }
+ }
+
+ // 检测代码块以进行基于行的分块
+ if (buffer.includes('```')) {
+ const lineMatch = /\n+/m.exec(buffer);
+ return lineMatch ? buffer.slice(0, lineMatch.index + lineMatch[0].length) : null;
+ }
+
+ // 默认单词分块
+ const wordMatch = /\S+\s+/m.exec(buffer);
+ return wordMatch ? buffer.slice(0, wordMatch.index + wordMatch[0].length) : null;
+};
+
+// 在 streamText 配置中使用
+const result = streamText({
+ experimental_transform: smoothStream({
+ chunking: customChunking,
+ delayInMs: (buffer) => {
+ // 复杂内容较慢,简单文本较快
+ return buffer.includes('```') || buffer.includes('{') ? 80 : 20;
+ },
+ }),
+ // ... 其他选项
+});
+```
+
+### 安全注意事项
+
+实现 AI 功能的安全最佳实践:
+
+```tsx title="app/api/ai/command/route.ts"
+export async function POST(req: NextRequest) {
+ const { messages, system } = await req.json();
+
+ // 验证请求结构
+ if (!messages || !Array.isArray(messages)) {
+ return NextResponse.json({ error: '无效的消息' }, { status: 400 });
+ }
+
+ // 内容长度验证
+ const totalContent = messages.map(m => m.content).join('');
+ if (totalContent.length > 50000) {
+ return NextResponse.json({ error: '内容过长' }, { status: 413 });
+ }
+
+ // 速率限制(使用您首选的解决方案实现)
+ // await rateLimit(req);
+
+ // 内容过滤(可选)
+ // const filteredMessages = await filterContent(messages);
+
+ // 处理 AI 请求...
+}
+```
+
+**安全指南:**
+- **验证输入**:始终验证和清理用户提示
+- **速率限制**:在 AI 端点上实现速率限制
+- **内容过滤**:考虑对响应进行内容过滤
+- **API 密钥安全**:切勿在客户端暴露 API 密钥
+- **用户隐私**:注意发送给 AI 模型的数据
+
+## 插件
+
+### `AIPlugin`
+
+核心插件,扩展编辑器以支持 AI 内容管理功能。
+
+
+
+
+ AI 叶子元素的节点配置。
+ - `isLeaf: true`:AI 内容被视为叶子节点
+ - `isDecoration: false`:不用于装饰
+
+
+
+
+### `AIChatPlugin`
+
+主要插件,支持 AI 聊天操作、流式传输和用户界面交互。
+
+
+
+
+ Vercel AI SDK `useChat` 钩子的配置选项。
+ - `api`:AI 请求的 API 端点
+ - `body`:额外的请求体参数
+
+
+ 指定如何处理助手消息:
+ - `'chat'`:显示带有接受/拒绝选项的预览
+ - `'insert'`:直接将内容插入编辑器
+ - **默认值:** `'chat'`
+
+
+ AI 聊天界面是否打开。
+ - **默认值:** `false`
+
+
+ AI 响应是否正在流式传输。
+ - **默认值:** `false`
+
+
+ 生成用户提示的模板。支持占位符:
+ - `{block}`:选择中块的 Markdown
+ - `{editor}`:整个编辑器内容的 Markdown
+ - `{selection}`:当前选择的 Markdown
+ - `{prompt}`:实际用户提示
+ - **默认值:** `'{prompt}'`
+
+
+ 系统消息的模板。支持与 `promptTemplate` 相同的占位符。
+
+
+ 用于生成 AI 响应的编辑器实例。
+ - **默认值:** `null`
+
+
+ `useChat` 钩子返回的聊天助手。
+
+
+
+
+## API
+
+### `api.aiChat.accept()`
+
+接受当前 AI 建议:
+- 从内容中移除 AI 标记
+- 隐藏 AI 聊天界面
+- 聚焦编辑器
+
+接受当前的 AI 建议:
+- 从内容中移除 AI 标记
+- 隐藏 AI 聊天界面
+- 聚焦编辑器
+
+### `api.aiChat.insertBelow()`
+
+在当前块下方插入 AI 生成的内容。
+
+处理块选择和普通选择两种模式:
+- 块选择模式:在最后一个选中块后插入,应用最后一个块的格式
+- 普通选择模式:在当前块后插入,应用当前块的格式
+
+
+
+
+ 包含要插入内容的编辑器。
+
+
+ 插入行为的选项。
+
+
+
+
+
+ 要应用的格式:
+ - `'all'`:对所有块应用格式
+ - `'none'`:插入时不应用格式
+ - `'single'`:仅对第一个块应用格式
+ - **默认值:** `'single'`
+
+
+
+
+### `api.aiChat.replaceSelection()`
+
+用 AI 生成的内容替换当前选择。
+
+处理不同的选择模式:
+- 单个块选择:替换选中的块,根据格式选项将选中块的格式应用到插入的内容
+- 多个块选择:替换所有选中的块
+ - 使用 `format: 'none'` 或 `'single'`:保留原始格式
+ - 使用 `format: 'all'`:将第一个块的格式应用到所有内容
+- 普通选择:替换当前选择,同时保持周围上下文
+
+
+
+
+ 包含替换内容的编辑器。
+
+
+ 替换行为的选项。
+
+
+
+
+
+ 要应用的格式:
+ - `'all'`:对所有块应用格式
+ - `'none'`:替换时不应用格式
+ - `'single'`:仅对第一个块应用格式
+ - **默认值:** `'single'`
+
+
+
+
+### `api.aiChat.reset()`
+
+重置聊天状态:
+- 停止任何正在进行的生成
+- 清除聊天消息
+- 从编辑器中移除所有 AI 节点
+
+### `api.aiChat.node()`
+
+获取 AI 聊天节点条目。
+
+
+
+
+ 查找节点的选项。
+
+
+
+
+
+ 为 true 时,查找类型与插件类型匹配的节点。
+ - **默认值:** `false`
+
+
+ 为 true 时,查找流式 AI 节点。
+ - **默认值:** `false`
+
+
+
+
+ 找到的节点条目,如果未找到则返回 undefined。
+
+
+
+### `api.aiChat.reload()`
+
+重新加载当前 AI 聊天:
+- 在插入模式:撤销之前的 AI 更改
+- 使用当前系统提示重新加载聊天
+
+### `api.aiChat.show()`
+
+显示 AI 聊天界面:
+- 重置聊天状态
+- 清除消息
+- 将打开状态设置为 true
+
+### `api.aiChat.hide()`
+
+隐藏 AI 聊天界面:
+- 重置聊天状态
+- 将打开状态设置为 false
+- 聚焦编辑器
+- 移除 AI 锚点
+
+### `api.aiChat.stop()`
+
+停止当前 AI 生成:
+- 将流式状态设置为 false
+- 调用聊天停止函数
+
+### `api.aiChat.submit()`
+
+提交提示以生成 AI 内容。
+
+
+
+
+ 提交的选项。
+
+
+
+
+
+ 使用的模式。在插入模式下,提交前撤销之前的 AI 更改。
+ - **默认值:** 选择时为 `'chat'`,否则为 `'insert'`
+
+
+ 自定义提示。
+ - **默认值:** 如果未提供则使用聊天输入
+
+
+ 此请求的自定义系统消息。
+
+
+
+
+## 转换
+
+### `tf.aiChat.removeAnchor()`
+
+从编辑器中移除 AI 聊天锚点节点。
+
+
+
+
+ 查找要移除节点的选项。
+
+
+
+
+### `tf.aiChat.accept()`
+
+接受当前 AI 建议并将其集成到编辑器内容中。
+
+### `tf.aiChat.insertBelow()`
+
+在当前块下方插入 AI 内容的转换。
+
+### `tf.aiChat.replaceSelection()`
+
+用 AI 内容替换当前选择的转换。
+
+### `tf.ai.insertNodes()`
+
+插入带有 AI 标记的 AI 生成节点。
+
+
+
+
+ 要插入的带 AI 标记的节点。
+
+
+ 插入节点的选项。
+
+
+
+
+
+ 插入的目标路径。
+ - **默认值:** 当前选择
+
+
+
+
+### `tf.ai.removeMarks()`
+
+从指定位置移除节点的 AI 标记。
+
+
+
+
+ 移除标记的选项。
+
+
+
+
+
+ 要移除标记的位置。
+ - **默认值:** 整个文档
+
+
+
+
+### `tf.ai.removeNodes()`
+
+移除带有 AI 标记的节点。
+
+
+
+
+ 移除节点的选项。
+
+
+
+
+
+ 要移除节点的路径。
+ - **默认值:** 整个文档
+
+
+
+
+### `tf.ai.undo()`
+
+AI 更改的特殊撤销操作:
+- 如果最后操作是 AI 生成的,则撤销该操作
+- 移除重做栈条目以防止重做 AI 操作
+
+## 钩子
+
+### `useAIChatEditor`
+
+一个在 AI 聊天插件中注册编辑器并使用块级记忆化反序列化 markdown 内容的钩子。
+
+
+
+
+ 要注册的编辑器实例。
+
+
+ 要反序列化到编辑器中的 markdown 内容。
+
+
+ 内容处理的选项。
+
+
+
+
+
+ 启用带有 `_memo` 属性的块级记忆化。
+ - **默认值:** `true`
+
+
+ markdown 标记解析器的选项。可以过滤特定的标记类型。
+
+
+ 自定义 markdown 处理器的函数。
+
+
+
+```tsx
+const AIChatEditor = ({ content }: { content: string }) => {
+ const aiEditor = usePlateEditor({
+ plugins: [
+ // 你的编辑器插件
+ MarkdownPlugin,
+ AIPlugin,
+ AIChatPlugin,
+ // 等等...
+ ],
+ });
+
+ useAIChatEditor(aiEditor, content, {
+ // 可选的 markdown 解析器选项
+ parser: {
+ exclude: ['space'],
+ },
+ });
+
+ return ;
+};
+```
+
diff --git a/apps/www/content/docs/(plugins)/(ai)/ai.mdx b/apps/www/content/docs/(plugins)/(ai)/ai.mdx
new file mode 100644
index 0000000000..731af58592
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(ai)/ai.mdx
@@ -0,0 +1,999 @@
+---
+title: AI
+description: AI-powered writing assistance.
+docs:
+ - route: https://pro.platejs.org/docs/examples/ai
+ title: Plus
+---
+
+
+
+
+
+## Features
+
+- **Intelligent Command Menu**: Combobox interface with predefined AI commands for generation and editing
+- **Multiple Trigger Modes**:
+ - **Cursor Mode**: Trigger at block end with space
+ - **Selection Mode**: Trigger with selected text
+ - **Block Selection Mode**: Trigger with selected blocks
+- **Response Modes**:
+ - **Chat Mode**: Preview responses with accept/reject options
+ - **Insert Mode**: Direct content insertion with markdown streaming
+- **Smart Content Processing**: Optimized chunking for tables, code blocks, and complex structures
+- **Streaming Responses**: Real-time AI content generation with support for:
+ - **Table Streaming**: Seamless streaming into table cells
+ - **Column Streaming**: Direct streaming into column layouts
+ - **MDX Tag Handling**: Proper preservation of custom MDX elements during streaming
+- **Markdown Integration**: Full support for Markdown syntax in AI responses
+- **Customizable Prompts**: Template system for user and system prompts
+- **Built-in Vercel AI SDK Support**: Ready-to-use chat API integration
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add AI functionality is with the `AIKit`, which includes pre-configured `AIPlugin` and `AIChatPlugin` along with cursor overlay and markdown support and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`AIMenu`](/docs/components/ai-menu): Renders the AI command interface
+- [`AILoadingBar`](/docs/components/ai-loading-bar): Shows AI processing status
+- [`AIAnchorElement`](/docs/components/ai-anchor-element): Anchor element for the AI Menu
+- [`AILeaf`](/docs/components/ai-leaf): Renders AI-generated content with visual distinction
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { AIKit } from '@/components/editor/plugins/ai-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...AIKit,
+ ],
+});
+```
+
+### Add API Route
+
+AI functionality requires a server-side API endpoint. Add the pre-configured AI command route:
+
+
+
+### Configure Environment
+
+Ensure your OpenAI API key is set in your environment variables:
+
+```bash title=".env.local"
+OPENAI_API_KEY="your-api-key"
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/ai @platejs/selection @platejs/markdown @platejs/basic-nodes
+```
+
+### Add Plugins
+
+```tsx
+import { AIPlugin, AIChatPlugin } from '@platejs/ai/react';
+import { createPlateEditor } from 'platejs/react';
+import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...MarkdownKit, // Required for AI content processing
+ AIPlugin,
+ AIChatPlugin,
+ ],
+});
+```
+
+- [`MarkdownKit`](/docs/markdown): Required for processing AI responses with Markdown syntax and MDX support.
+- `AIPlugin`: Core plugin for AI content management and transforms.
+- `AIChatPlugin`: Handles AI chat interface, streaming, and user interactions.
+
+### Configure Plugins
+
+Create the extended `aiChatPlugin` with basic configuration:
+
+```tsx
+import type { AIChatPluginConfig } from '@platejs/ai/react';
+import type { UseChatOptions } from 'ai/react';
+
+import { KEYS, PathApi } from 'platejs';
+import { streamInsertChunk, withAIBatch } from '@platejs/ai';
+import { AIChatPlugin, AIPlugin, useChatChunk } from '@platejs/ai/react';
+import { usePluginOption } from 'platejs/react';
+import { MarkdownKit } from '@/components/editor/plugins/markdown-kit';
+import { AILoadingBar, AIMenu } from '@/components/ui/ai-menu';
+import { AIAnchorElement, AILeaf } from '@/components/ui/ai-node';
+
+export const aiChatPlugin = AIChatPlugin.extend({
+ options: {
+ chatOptions: {
+ api: '/api/ai/command',
+ body: {},
+ } as UseChatOptions,
+ },
+ render: {
+ afterContainer: AILoadingBar,
+ afterEditable: AIMenu,
+ node: AIAnchorElement,
+ },
+ shortcuts: { show: { keys: 'mod+j' } },
+});
+
+const plugins = [
+ // ...otherPlugins,
+ ...MarkdownKit,
+ AIPlugin.withComponent(AILeaf),
+ aiChatPlugin,
+];
+```
+
+- `chatOptions`: Configuration for the Vercel AI SDK `useChat` hook.
+- `render`: UI components for the AI interface.
+- `shortcuts`: Keyboard shortcuts (`Cmd+J` to show AI menu).
+
+### Add Streaming with useHooks
+
+The `useChatChunk` hook processes streaming AI responses in real-time, handling content insertion and chunk management. It monitors the chat state and processes incoming text chunks, inserting them into the editor as they arrive:
+
+```tsx
+export const aiChatPlugin = AIChatPlugin.extend({
+ // ... previous options
+ useHooks: ({ editor, getOption }) => {
+ const mode = usePluginOption(
+ { key: KEYS.aiChat } as AIChatPluginConfig,
+ 'mode'
+ );
+
+ useChatChunk({
+ onChunk: ({ chunk, isFirst, nodes }) => {
+ if (isFirst && mode == 'insert') {
+ editor.tf.withoutSaving(() => {
+ editor.tf.insertNodes(
+ {
+ children: [{ text: '' }],
+ type: KEYS.aiChat,
+ },
+ {
+ at: PathApi.next(editor.selection!.focus.path.slice(0, 1)),
+ }
+ );
+ });
+ editor.setOption(AIChatPlugin, 'streaming', true);
+ }
+
+ if (mode === 'insert' && nodes.length > 0) {
+ withAIBatch(
+ editor,
+ () => {
+ if (!getOption('streaming')) return;
+ editor.tf.withScrolling(() => {
+ streamInsertChunk(editor, chunk, {
+ textProps: {
+ ai: true,
+ },
+ });
+ });
+ },
+ { split: isFirst }
+ );
+ }
+ },
+ onFinish: () => {
+ editor.setOption(AIChatPlugin, 'streaming', false);
+ editor.setOption(AIChatPlugin, '_blockChunks', '');
+ editor.setOption(AIChatPlugin, '_blockPath', null);
+ },
+ });
+ },
+});
+```
+
+- `onChunk`: Handles each streaming chunk, creating AI nodes on first chunk and inserting content in real-time
+- `onFinish`: Cleans up streaming state when the response completes
+- Uses `withAIBatch` and `streamInsertChunk` for optimized content insertion
+
+### System Prompt
+
+The system prompt defines the AI's role and behavior. You can customize the `systemTemplate` in your extended plugin:
+
+```tsx
+export const customAIChatPlugin = AIChatPlugin.extend({
+ options: {
+ systemTemplate: ({ isBlockSelecting, isSelecting }) => {
+ const customSystem = `You are a technical documentation assistant specialized in code and API documentation.
+
+Rules:
+- Provide accurate, well-structured technical content
+- Use appropriate code formatting and syntax highlighting
+- Include relevant examples and best practices
+- Maintain consistent documentation style
+- CRITICAL: DO NOT remove or modify custom MDX tags unless explicitly requested.
+- CRITICAL: Distinguish between INSTRUCTIONS and QUESTIONS.`;
+
+ return isBlockSelecting
+ ? `${customSystem}
+- represents the full blocks of text the user has selected and wants to modify or ask about.
+- Your response should be a direct replacement for the entire .
+- Maintain the overall structure and formatting of the selected blocks, unless explicitly instructed otherwise.
+
+{block}
+ `
+ : isSelecting
+ ? `${customSystem}
+- is the block of text containing the user's selection, providing context.
+- is the specific text the user has selected in the block and wants to modify or ask about.
+- Consider the context provided by , but only modify .
+
+{block}
+
+
+{selection}
+ `
+ : `${customSystem}
+- is the current block of text the user is working on.
+
+
+{block}
+ `;
+ },
+ // ...other options
+ },
+}),
+```
+
+### User Prompt
+
+Customize how user prompts are formatted and contextualized in your extended plugin:
+
+```tsx
+export const customAIChatPlugin = AIChatPlugin.extend({
+ options: {
+ promptTemplate: ({ isBlockSelecting, isSelecting }) => {
+ return isBlockSelecting
+ ? `
+If this is a question, provide a helpful and concise answer about .
+If this is an instruction, provide ONLY the content to replace the entire . No explanations.
+Analyze and improve the following content blocks maintaining structure and clarity.
+NEVER write or .
+
+{prompt} about `
+ : isSelecting
+ ? `
+If this is a question, provide a helpful and concise answer about .
+If this is an instruction, provide ONLY the text to replace . No explanations.
+Ensure it fits seamlessly within . If is empty, write ONE random sentence.
+NEVER write or .
+
+{prompt} about `
+ : `
+CRITICAL: NEVER write .
+Continue or improve the content naturally.
+
+{prompt}`;
+ },
+ // ...other options
+ },
+}),
+```
+
+### Add API Route
+
+Create an API route handler with optimized streaming for different content types:
+
+```tsx title="app/api/ai/command/route.ts"
+import type { NextRequest } from 'next/server';
+
+import { createOpenAI } from '@ai-sdk/openai';
+import { convertToCoreMessages, streamText } from 'ai';
+import { NextResponse } from 'next/server';
+
+import { markdownJoinerTransform } from '@/registry/lib/markdown-joiner-transform';
+
+export async function POST(req: NextRequest) {
+ const { apiKey: key, messages, system } = await req.json();
+
+ const apiKey = key || process.env.OPENAI_API_KEY;
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: 'Missing OpenAI API key.' },
+ { status: 401 }
+ );
+ }
+
+ const openai = createOpenAI({ apiKey });
+
+ try {
+ const result = streamText({
+ experimental_transform: markdownJoinerTransform(),
+ maxTokens: 2048,
+ messages: convertToCoreMessages(messages),
+ model: openai('gpt-4o'),
+ system: system,
+ });
+
+ return result.toDataStreamResponse();
+ } catch {
+ return NextResponse.json(
+ { error: 'Failed to process AI request' },
+ { status: 500 }
+ );
+ }
+}
+```
+
+Then, set your `OPENAI_API_KEY` in `.env.local`.
+
+### Add Toolbar Button
+
+You can add [`AIToolbarButton`](/docs/components/ai-toolbar-button) to your [Toolbar](/docs/toolbar) to open the AI menu.
+
+
+
+## Keyboard Shortcuts
+
+
+
+ Open AI menu in empty block (cursor mode)
+
+
+ Open AI menu (cursor or selection mode)
+
+ Close AI menu
+
+
+## Streaming Example
+
+
+
+
+## Plate Plus
+
+
+
+## Customization
+
+### Adding Custom AI Commands
+
+
+
+You can extend the AI menu with custom commands by adding new items to the `aiChatItems` object and updating the menu state items.
+
+#### Simple Custom Command
+
+Add a basic command that submits a custom prompt:
+
+```tsx
+// Add to your ai-menu.tsx aiChatItems object
+summarizeInBullets: {
+ icon: ,
+ label: 'Summarize in bullets',
+ value: 'summarizeInBullets',
+ onSelect: ({ editor }) => {
+ void editor.getApi(AIChatPlugin).aiChat.submit({
+ prompt: 'Summarize this content as bullet points',
+ });
+ },
+},
+```
+
+#### Command with Complex Logic
+
+Create commands with client-side logic before submission:
+
+```tsx
+generateTOC: {
+ icon: ,
+ label: 'Generate table of contents',
+ value: 'generateTOC',
+ onSelect: ({ editor }) => {
+ // Check if document has headings
+ const headings = editor.api.nodes({
+ match: (n) => ['h1', 'h2', 'h3'].includes(n.type as string),
+ });
+
+ if (headings.length === 0) {
+ void editor.getApi(AIChatPlugin).aiChat.submit({
+ mode: 'insert',
+ prompt: 'Create a table of contents with sample headings for this document',
+ });
+ } else {
+ void editor.getApi(AIChatPlugin).aiChat.submit({
+ mode: 'insert',
+ prompt: 'Generate a table of contents based on the existing headings',
+ });
+ }
+ },
+},
+```
+
+#### Understanding Menu States
+
+The AI menu adapts to different contexts based on user selection and AI response state:
+
+```tsx
+const menuState = React.useMemo(() => {
+ // If AI has already responded, show suggestion actions
+ if (messages && messages.length > 0) {
+ return isSelecting ? 'selectionSuggestion' : 'cursorSuggestion';
+ }
+
+ // If no AI response yet, show command actions
+ return isSelecting ? 'selectionCommand' : 'cursorCommand';
+}, [isSelecting, messages]);
+```
+
+**Menu States:**
+- `cursorCommand`: No selection, no AI response → Show generation commands (Continue writing, Summarize, etc.)
+- `selectionCommand`: Text selected, no AI response → Show editing commands (Improve writing, Fix spelling, etc.)
+- `cursorSuggestion`: No selection, AI responded → Show suggestion actions (Accept, Discard, Try again)
+- `selectionSuggestion`: Text selected, AI responded → Show replacement actions (Replace selection, Insert below, etc.)
+
+#### Update Menu States
+
+Add your custom commands to the appropriate menu states in `menuStateItems`:
+
+```tsx
+const menuStateItems: Record = {
+ cursorCommand: [
+ {
+ items: [
+ aiChatItems.generateTOC,
+ aiChatItems.summarizeInBullets,
+ // ... existing items
+ ],
+ },
+ ],
+ selectionCommand: [
+ {
+ items: [
+ aiChatItems.summarizeInBullets, // Works for selected text too
+ // ... existing items
+ ],
+ },
+ ],
+ // ... other states
+};
+```
+
+### Switching AI Models
+
+Configure different AI models and providers in your API route:
+
+```tsx title="app/api/ai/command/route.ts"
+import { createOpenAI } from '@ai-sdk/openai';
+import { createAnthropic } from '@ai-sdk/anthropic';
+
+export async function POST(req: NextRequest) {
+ const { model = 'gpt-4o', provider = 'openai', ...rest } = await req.json();
+
+ let aiProvider;
+
+ switch (provider) {
+ case 'anthropic':
+ aiProvider = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
+ break;
+ case 'openai':
+ default:
+ aiProvider = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
+ break;
+ }
+
+ const result = streamText({
+ model: aiProvider(model),
+ // ... other options
+ });
+
+ return result.toDataStreamResponse();
+}
+```
+
+Configure the model in your `aiChatPlugin`:
+
+```tsx
+export const aiChatPlugin = AIChatPlugin.extend({
+ options: {
+ chatOptions: {
+ api: '/api/ai/command',
+ body: {
+ model: 'gpt-4o-mini', // or 'claude-4-sonnet'
+ provider: 'openai', // or 'anthropic'
+ },
+ },
+ // ... other options
+ },
+});
+```
+
+For more AI providers and models, see the [Vercel AI SDK documentation](https://sdk.vercel.ai/providers/ai-sdk-providers).
+
+### Custom Streaming Optimization
+
+Optimize streaming performance for specific content types with custom chunking strategies:
+
+```tsx title="app/api/ai/command/route.ts"
+const customChunking = (buffer: string) => {
+ // Detect JSON content for slower chunking
+ if (buffer.includes('{') && buffer.includes('}')) {
+ const jsonMatch = /\{[^}]*\}/g.exec(buffer);
+ if (jsonMatch) {
+ return buffer.slice(0, jsonMatch.index + jsonMatch[0].length);
+ }
+ }
+
+ // Detect code blocks for line-based chunking
+ if (buffer.includes('```')) {
+ const lineMatch = /\n+/m.exec(buffer);
+ return lineMatch ? buffer.slice(0, lineMatch.index + lineMatch[0].length) : null;
+ }
+
+ // Default word chunking
+ const wordMatch = /\S+\s+/m.exec(buffer);
+ return wordMatch ? buffer.slice(0, wordMatch.index + wordMatch[0].length) : null;
+};
+
+// Use in your streamText configuration
+const result = streamText({
+ experimental_transform: smoothStream({
+ chunking: customChunking,
+ delayInMs: (buffer) => {
+ // Slower for complex content, faster for simple text
+ return buffer.includes('```') || buffer.includes('{') ? 80 : 20;
+ },
+ }),
+ // ... other options
+});
+```
+
+### Security Considerations
+
+Implement security best practices for AI functionality:
+
+```tsx title="app/api/ai/command/route.ts"
+export async function POST(req: NextRequest) {
+ const { messages, system } = await req.json();
+
+ // Validate request structure
+ if (!messages || !Array.isArray(messages)) {
+ return NextResponse.json({ error: 'Invalid messages' }, { status: 400 });
+ }
+
+ // Content length validation
+ const totalContent = messages.map(m => m.content).join('');
+ if (totalContent.length > 50000) {
+ return NextResponse.json({ error: 'Content too long' }, { status: 413 });
+ }
+
+ // Rate limiting (implement with your preferred solution)
+ // await rateLimit(req);
+
+ // Content filtering (optional)
+ // const filteredMessages = await filterContent(messages);
+
+ // Process AI request...
+}
+```
+
+**Security Guidelines:**
+- **Validate Input**: Always validate and sanitize user prompts
+- **Rate Limiting**: Implement rate limiting on AI endpoints
+- **Content Filtering**: Consider content filtering for responses
+- **API Key Security**: Never expose API keys client-side
+- **User Privacy**: Be mindful of data sent to AI models
+
+## Plugins
+
+### `AIPlugin`
+
+Core plugin that extends the editor with AI content management capabilities.
+
+
+
+
+ Node configuration for AI leaf elements.
+ - `isLeaf: true`: AI content is treated as leaf nodes
+ - `isDecoration: false`: Not used for decorations
+
+
+
+
+### `AIChatPlugin`
+
+Main plugin that enables AI chat operations, streaming, and user interface interactions.
+
+
+
+
+ Configuration options for the Vercel AI SDK `useChat` hook.
+ - `api`: API endpoint for AI requests
+ - `body`: Additional request body parameters
+
+
+ Specifies how assistant messages are handled:
+ - `'chat'`: Shows preview with accept/reject options
+ - `'insert'`: Directly inserts content into editor
+ - **Default:** `'chat'`
+
+
+ Whether the AI chat interface is open.
+ - **Default:** `false`
+
+
+ Whether AI response is currently streaming.
+ - **Default:** `false`
+
+
+ Template for generating user prompts. Supports placeholders:
+ - `{block}`: Markdown of blocks in selection
+ - `{editor}`: Markdown of entire editor content
+ - `{selection}`: Markdown of current selection
+ - `{prompt}`: Actual user prompt
+ - **Default:** `'{prompt}'`
+
+
+ Template for system messages. Supports same placeholders as `promptTemplate`.
+
+
+ The editor instance used to generate AI responses.
+ - **Default:** `null`
+
+
+ Chat helpers returned by `useChat` hook.
+
+
+
+
+## API
+
+### `api.aiChat.accept()`
+
+Accepts the current AI suggestion:
+- Removes AI marks from the content
+- Hides the AI chat interface
+- Focuses the editor
+
+### `api.aiChat.insertBelow()`
+
+Inserts AI-generated content below the current block.
+
+Handles both block selection and normal selection modes:
+- In block selection: Inserts after the last selected block, applying formatting from the last block
+- In normal selection: Inserts after the current block, applying formatting from the current block
+
+
+
+
+ Editor containing the content to insert.
+
+
+ Options for insertion behavior.
+
+
+
+
+
+ Format to apply:
+ - `'all'`: Apply formatting to all blocks
+ - `'none'`: Insert without formatting
+ - `'single'`: Apply formatting only to first block
+ - **Default:** `'single'`
+
+
+
+
+### `api.aiChat.replaceSelection()`
+
+Replaces the current selection with AI-generated content.
+
+Handles different selection modes:
+- Single block selection: Replaces the selected block, applying its formatting to inserted content based on format option
+- Multiple block selection: Replaces all selected blocks
+ - With `format: 'none'` or `'single'`: Preserves original formatting
+ - With `format: 'all'`: Applies first block's formatting to all content
+- Normal selection: Replaces the current selection while maintaining surrounding context
+
+
+
+
+ Editor containing the replacement content.
+
+
+ Options for replacement behavior.
+
+
+
+
+
+ Format to apply:
+ - `'all'`: Apply formatting to all blocks
+ - `'none'`: Replace without formatting
+ - `'single'`: Apply formatting only to first block
+ - **Default:** `'single'`
+
+
+
+
+### `api.aiChat.reset()`
+
+Resets the chat state:
+- Stops any ongoing generation
+- Clears chat messages
+- Removes all AI nodes from the editor
+
+### `api.aiChat.node()`
+
+Gets the AI chat node entry.
+
+
+
+
+ Options for finding the node.
+
+
+
+
+
+ When true, finds nodes with type matching the plugin type.
+ - **Default:** `false`
+
+
+ When true, finds streaming AI nodes.
+ - **Default:** `false`
+
+
+
+
+ The found node entry or undefined if not found.
+
+
+
+### `api.aiChat.reload()`
+
+Reloads the current AI chat:
+- In insert mode: Undoes previous AI changes
+- Reloads the chat with the current system prompt
+
+### `api.aiChat.show()`
+
+Shows the AI chat interface:
+- Resets the chat state
+- Clears messages
+- Sets the open state to true
+
+### `api.aiChat.hide()`
+
+Hides the AI chat interface:
+- Resets the chat state
+- Sets the open state to false
+- Focuses the editor
+- Removes the AI anchor
+
+### `api.aiChat.stop()`
+
+Stops the current AI generation:
+- Sets streaming state to false
+- Calls the chat stop function
+
+### `api.aiChat.submit()`
+
+Submits a prompt to generate AI content.
+
+
+
+
+ Options for the submission.
+
+
+
+
+
+ Mode to use. In insert mode, undoes previous AI changes before submitting.
+ - **Default:** `'chat'` for selection, `'insert'` otherwise
+
+
+ Custom prompt to submit.
+ - **Default:** Uses chat input if not provided
+
+
+ Custom system message for this request.
+
+
+
+
+## Transforms
+
+### `tf.aiChat.removeAnchor()`
+
+Removes the AI chat anchor node from the editor.
+
+
+
+
+ Options for finding nodes to remove.
+
+
+
+
+### `tf.aiChat.accept()`
+
+Accepts the current AI suggestion and integrates it into the editor content.
+
+### `tf.aiChat.insertBelow()`
+
+Transform that inserts AI content below the current block.
+
+### `tf.aiChat.replaceSelection()`
+
+Transform that replaces the current selection with AI content.
+
+### `tf.ai.insertNodes()`
+
+Inserts AI-generated nodes with the AI mark.
+
+
+
+
+ Nodes to insert with AI mark.
+
+
+ Options for inserting nodes.
+
+
+
+
+
+ Target path for insertion.
+ - **Default:** Current selection
+
+
+
+
+### `tf.ai.removeMarks()`
+
+Removes AI marks from nodes in the specified location.
+
+
+
+
+ Options for removing marks.
+
+
+
+
+
+ Location to remove marks from.
+ - **Default:** Entire document
+
+
+
+
+### `tf.ai.removeNodes()`
+
+Removes nodes that have the AI mark.
+
+
+
+
+ Options for removing nodes.
+
+
+
+
+
+ Path to remove nodes from.
+ - **Default:** Entire document
+
+
+
+
+### `tf.ai.undo()`
+
+Special undo operation for AI changes:
+- Undoes the last operation if it was AI-generated
+- Removes the redo stack entry to prevent redoing AI operations
+
+## Streaming Behavior
+
+### Enhanced Empty Paragraph Handling
+
+The AI streaming system intelligently handles empty paragraphs:
+- Only removes truly empty paragraphs when starting to stream
+- Preserves paragraphs containing only whitespace or formatting marks
+- Prevents accidental content loss during streaming initialization
+
+### Table and Column Support
+
+AI streaming seamlessly works within complex structures:
+
+**Tables:**
+- Streams directly into table cells without disrupting table structure
+- Maintains table formatting during streaming
+- Properly handles cell boundaries
+
+**Columns:**
+- Supports streaming into column layouts
+- Preserves column width and structure
+- Enables AI content generation within multi-column documents
+
+### MDX Tag Preservation
+
+During streaming, the system:
+- Detects and preserves custom MDX tags
+- Prevents MDX content from being incorrectly parsed as Markdown
+- Maintains proper nesting of MDX elements
+- Supports streaming of content containing MDX components
+
+## Hooks
+
+### `useAIChatEditor`
+
+A hook that registers an editor in the AI chat plugin and deserializes markdown content with block-level memoization.
+
+
+
+
+ The editor instance to register.
+
+
+ The markdown content to deserialize into the editor.
+
+
+ Options for content processing.
+
+
+
+
+
+ Enable block-level memoization with `_memo` property.
+ - **Default:** `true`
+
+
+ Options for the markdown token parser. Can filter out specific token types.
+
+
+ Function to customize the markdown processor.
+
+
+
+```tsx
+const AIChatEditor = ({ content }: { content: string }) => {
+ const aiEditor = usePlateEditor({
+ plugins: [
+ // Your editor plugins
+ MarkdownPlugin,
+ AIPlugin,
+ AIChatPlugin,
+ // etc...
+ ],
+ });
+
+ useAIChatEditor(aiEditor, content, {
+ // Optional markdown parser options
+ parser: {
+ exclude: ['space'],
+ },
+ });
+
+ return ;
+};
+```
+
diff --git a/apps/www/content/docs/(plugins)/(ai)/copilot.cn.mdx b/apps/www/content/docs/(plugins)/(ai)/copilot.cn.mdx
new file mode 100644
index 0000000000..58adb10e64
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(ai)/copilot.cn.mdx
@@ -0,0 +1,501 @@
+---
+title: Copilot
+description: AI 驱动的文本补全建议。
+docs:
+ - route: /docs/components/ghost-text
+ title: 幽灵文本
+ - route: https://pro.platejs.org/docs/components/ghost-text
+ title: 幽灵文本
+---
+
+
+
+
+
+## 功能特性
+
+- 在输入时渲染幽灵文本建议
+- 两种触发模式:
+ - 快捷键(如 `Ctrl+Space`)。再次按下可获取替代建议。
+ - 防抖模式:在段落末尾空格后自动触发
+- 使用 Tab 接受建议或使用 `Cmd+→` 逐词接受
+- 内置支持 Vercel AI SDK 补全 API
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+添加 Copilot 功能最快的方式是使用 `CopilotKit`,它包含预配置的 `CopilotPlugin` 以及 `MarkdownKit` 和它们的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`GhostText`](/docs/components/ghost-text): 渲染幽灵文本建议。
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CopilotKit } from '@/components/editor/plugins/copilot-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...CopilotKit,
+ // 将使用 Tab 键的插件放在 CopilotKit 之后以避免冲突
+ // IndentPlugin,
+ // TabbablePlugin,
+ ],
+});
+```
+
+**Tab 键处理**: Copilot 插件使用 Tab 键来接受建议。为避免与其他使用 Tab 的插件(如 `IndentPlugin` 或 `TabbablePlugin`)冲突,请确保 `CopilotKit` 在插件配置中位于它们之前。
+
+### 添加 API 路由
+
+Copilot 需要一个服务器端 API 端点来与 AI 模型通信。添加预配置的 Copilot API 路由:
+
+
+
+### 配置环境
+
+确保您的 OpenAI API 密钥已设置在环境变量中:
+
+```bash title=".env.local"
+OPENAI_API_KEY="您的-api-密钥"
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/ai @platejs/markdown
+```
+
+### 添加插件
+
+```tsx
+import { CopilotPlugin } from '@platejs/ai/react';
+import { MarkdownPlugin } from '@platejs/markdown';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ MarkdownPlugin,
+ CopilotPlugin,
+ // 将使用 Tab 键的插件放在 CopilotPlugin 之后以避免冲突
+ // IndentPlugin,
+ // TabbablePlugin,
+ ],
+});
+```
+
+- `MarkdownPlugin`: 用于将编辑器内容序列化为提示词发送。
+- `CopilotPlugin`: 启用 AI 驱动的文本补全。
+
+**Tab 键处理**: Copilot 插件使用 Tab 键来接受建议。为避免与其他使用 Tab 的插件(如 `IndentPlugin` 或 `TabbablePlugin`)冲突,请确保 `CopilotPlugin` 在插件配置中位于它们之前。
+
+### 配置插件
+
+```tsx
+import { CopilotPlugin } from '@platejs/ai/react';
+import { serializeMd, stripMarkdown } from '@platejs/markdown';
+import { GhostText } from '@/components/ui/ghost-text';
+
+const plugins = [
+ // ...其他插件,
+ MarkdownPlugin.configure({
+ options: {
+ remarkPlugins: [remarkMath, remarkGfm, remarkMdx],
+ },
+ }),
+ CopilotPlugin.configure(({ api }) => ({
+ options: {
+ completeOptions: {
+ api: '/api/ai/copilot',
+ onError: () => {
+ // 模拟 API 响应。在实现路由 /api/ai/copilot 后移除
+ api.copilot.setBlockSuggestion({
+ text: stripMarkdown('这是一个模拟建议。'),
+ });
+ },
+ onFinish: (_, completion) => {
+ if (completion === '0') return;
+
+ api.copilot.setBlockSuggestion({
+ text: stripMarkdown(completion),
+ });
+ },
+ },
+ debounceDelay: 500,
+ renderGhostText: GhostText,
+ },
+ shortcuts: {
+ accept: { keys: 'tab' },
+ acceptNextWord: { keys: 'mod+right' },
+ reject: { keys: 'escape' },
+ triggerSuggestion: { keys: 'ctrl+space' },
+ },
+ })),
+];
+```
+
+- `completeOptions`: 配置 Vercel AI SDK `useCompletion` 钩子。
+ - `api`: AI 补全路由的端点。
+ - `onError`: 处理错误的回调(用于开发期间的模拟)。
+ - `onFinish`: 处理完成建议的回调。此处将建议设置到编辑器中。
+- `debounceDelay`: 用户停止输入后自动触发建议的延迟时间(毫秒)。
+- `renderGhostText`: 用于内联显示建议的 React 组件。
+- `shortcuts`: 定义与 Copilot 建议交互的键盘快捷键。
+
+### 添加 API 路由
+
+在 `app/api/ai/copilot/route.ts` 创建 API 路由处理程序来处理 AI 请求。此端点将接收来自编辑器的提示词并调用 AI 模型。
+
+```tsx title="app/api/ai/copilot/route.ts"
+import type { NextRequest } from 'next/server';
+
+import { createOpenAI } from '@ai-sdk/openai';
+import { generateText } from 'ai';
+import { NextResponse } from 'next/server';
+
+export async function POST(req: NextRequest) {
+ const {
+ apiKey: key,
+ model = 'gpt-4o-mini',
+ prompt,
+ system,
+ } = await req.json();
+
+ const apiKey = key || process.env.OPENAI_API_KEY;
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: '缺少 OpenAI API 密钥。' },
+ { status: 401 }
+ );
+ }
+
+ const openai = createOpenAI({ apiKey });
+
+ try {
+ const result = await generateText({
+ abortSignal: req.signal,
+ maxTokens: 50,
+ model: openai(model),
+ prompt: prompt,
+ system,
+ temperature: 0.7,
+ });
+
+ return NextResponse.json(result);
+ } catch (error) {
+ if (error instanceof Error && error.name === 'AbortError') {
+ return NextResponse.json(null, { status: 408 });
+ }
+
+ return NextResponse.json(
+ { error: '处理 AI 请求失败' },
+ { status: 500 }
+ );
+ }
+}
+```
+
+然后,在 `.env.local` 中设置您的 `OPENAI_API_KEY`。
+
+### 系统提示词
+
+系统提示词定义了 AI 的角色和行为。修改 `completeOptions` 中的 `body.system` 属性:
+
+```tsx
+CopilotPlugin.configure(({ api }) => ({
+ options: {
+ completeOptions: {
+ api: '/api/ai/copilot',
+ body: {
+ system: {
+ system: `您是一个高级 AI 写作助手,类似于 VSCode Copilot,但适用于通用文本。您的任务是根据给定上下文预测并生成文本的下一部分。
+
+规则:
+- 自然地继续文本直到下一个标点符号(., ,, ;, :, ? 或 !)。
+- 保持风格和语气。不要重复给定文本。
+- 对于不明确的上下文,提供最可能的延续。
+- 如果需要,处理代码片段、列表或结构化文本。
+- 不要在响应中包含 """。
+- 关键:始终以标点符号结尾。
+- 关键:避免开始新块。不要使用块格式化如 >, #, 1., 2., - 等。建议应继续在与上下文相同的块中。
+- 如果未提供上下文或无法生成延续,返回 "0" 而不解释。`,
+ },
+ },
+ // ... 其他选项
+ },
+ // ... 其他插件选项
+ },
+})),
+```
+
+### 用户提示词
+
+用户提示词(通过 `getPrompt`)决定发送给 AI 的上下文内容。您可以自定义它以包含更多上下文或以不同方式格式化:
+
+```tsx
+CopilotPlugin.configure(({ api }) => ({
+ options: {
+ getPrompt: ({ editor }) => {
+ const contextEntry = editor.api.block({ highest: true });
+
+ if (!contextEntry) return '';
+
+ const prompt = serializeMd(editor, {
+ value: [contextEntry[0] as TElement],
+ });
+
+ return `继续文本直到下一个标点符号:
+"""
+${prompt}
+"""`;
+ },
+ // ... 其他选项
+ },
+})),
+```
+
+
+
+## Plate Plus
+
+
+
+## 自定义
+
+### 切换 AI 模型
+
+在 API 路由中配置不同的 AI 模型和 provider:
+
+```tsx title="app/api/ai/copilot/route.ts"
+import { createOpenAI } from '@ai-sdk/openai';
+import { createAnthropic } from '@ai-sdk/anthropic';
+
+export async function POST(req: NextRequest) {
+ const {
+ model = 'gpt-4o-mini',
+ provider = 'openai',
+ prompt,
+ system
+ } = await req.json();
+
+ let aiProvider;
+
+ switch (provider) {
+ case 'anthropic':
+ aiProvider = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
+ break;
+ case 'openai':
+ default:
+ aiProvider = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
+ break;
+ }
+
+ const result = await generateText({
+ model: aiProvider(model),
+ prompt,
+ system,
+ maxTokens: 50,
+ temperature: 0.7,
+ });
+
+ return NextResponse.json(result);
+}
+```
+
+在 `CopilotPlugin` 中配置模型:
+
+```tsx
+CopilotPlugin.configure(({ api }) => ({
+ options: {
+ completeOptions: {
+ api: '/api/ai/copilot',
+ body: {
+ model: 'claude-3-haiku-20240307', // 用于补全的快速模型
+ provider: 'anthropic',
+ system: '您的系统提示词...',
+ },
+ },
+ // ... 其他选项
+ },
+})),
+```
+
+更多 AI provider 和模型,请参阅 [Vercel AI SDK 文档](https://sdk.vercel.ai/providers/ai-sdk-providers)。
+
+### 自定义触发条件
+
+控制何时自动触发建议:
+
+```tsx
+CopilotPlugin.configure(({ api }) => ({
+ options: {
+ triggerQuery: ({ editor }) => {
+ // 仅在段落块中触发
+ const block = editor.api.block();
+ if (!block || block[0].type !== 'p') return false;
+
+ // 标准检查
+ return editor.selection &&
+ !editor.api.isExpanded() &&
+ editor.api.isAtEnd();
+ },
+ autoTriggerQuery: ({ editor }) => {
+ // 自动触发的自定义条件
+ const block = editor.api.block();
+ if (!block) return false;
+
+ const text = editor.api.string(block[0]);
+
+ // 在疑问词后触发
+ return /\b(what|how|why|when|where)\s*$/i.test(text);
+ },
+ // ... 其他选项
+ },
+})),
+```
+
+### 安全考虑
+
+为 Copilot API 实施安全最佳实践:
+
+```tsx title="app/api/ai/copilot/route.ts"
+export async function POST(req: NextRequest) {
+ const { prompt, system } = await req.json();
+
+ // 验证提示词长度
+ if (!prompt || prompt.length > 1000) {
+ return NextResponse.json({ error: '无效提示词' }, { status: 400 });
+ }
+
+ // 速率限制(使用您偏好的解决方案实现)
+ // await rateLimit(req);
+
+ // 敏感内容过滤
+ if (containsSensitiveContent(prompt)) {
+ return NextResponse.json({ error: '内容被过滤' }, { status: 400 });
+ }
+
+ // 处理 AI 请求...
+}
+```
+
+**安全指南:**
+- **输入验证**: 限制提示词长度并验证内容
+- **速率限制**: 通过请求限制防止滥用
+- **内容过滤**: 过滤敏感或不适当内容
+- **API 密钥安全**: 切勿在客户端暴露 API 密钥
+- **超时处理**: 优雅处理请求超时
+
+## 插件
+
+### `CopilotPlugin`
+
+用于 AI 驱动的文本补全建议的插件。
+
+
+
+
+ 自动触发 copilot 的附加条件。
+ - **默认:** 检查:
+ - 上方块不为空
+ - 上方块以空格结尾
+ - 无现有建议
+
+
+ AI 补全配置选项。参见 [AI SDK useCompletion 参数](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-completion#parameters)。
+
+
+ 自动触发建议的防抖延迟。
+ - **默认:** `0`
+
+
+ 从建议文本中提取下一个单词的函数。
+
+
+ 生成 AI 补全提示词的函数。
+ - **默认:** 使用祖先节点的 markdown 序列化
+
+
+ 渲染幽灵文本建议的组件。
+
+
+ 触发 copilot 的条件。
+ - **默认:** 检查:
+ - 选择未展开
+ - 选择在块末尾
+
+
+
+
+## 转换
+
+### `tf.copilot.accept()`
+
+接受当前建议并将其应用到编辑器内容中。
+
+默认快捷键: `Tab`
+
+### `tf.copilot.acceptNextWord()`
+
+仅接受当前建议的下一个单词,允许逐步接受建议。
+
+示例快捷键: `Cmd + →`
+
+## API
+
+### `api.copilot.reject()`
+
+将插件状态重置为初始条件:
+默认快捷键: `Escape`
+
+### `api.copilot.triggerSuggestion()`
+
+触发新的建议请求。请求可能会根据插件配置进行防抖。
+
+示例快捷键: `Ctrl + Space`
+
+### `api.copilot.setBlockSuggestion()`
+
+为块设置建议文本。
+
+
+
+
+ 设置块建议的选项。
+
+
+
+
+
+ 要设置的建议文本。
+
+
+ 目标块 ID。
+ - **默认:** 当前块
+
+
+
+
+### `api.copilot.stop()`
+
+停止正在进行的建议请求并清理:
+
+- 取消防抖的触发调用
+- 中止当前 API 请求
+- 重置中止控制器
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(ai)/copilot.mdx b/apps/www/content/docs/(plugins)/(ai)/copilot.mdx
new file mode 100644
index 0000000000..c0e64c2d4f
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(ai)/copilot.mdx
@@ -0,0 +1,503 @@
+---
+title: Copilot
+description: AI-powered text completion suggestions.
+docs:
+ - route: https://pro.platejs.org/docs/examples/copilot
+ title: Plus
+ - route: /docs/components/ghost-text
+ title: Ghost Text
+---
+
+
+
+
+
+## Features
+
+- Renders ghost text suggestions as you type
+- Two trigger modes:
+ - Shortcut (e.g. `Ctrl+Space`). Press again for alternative suggestions.
+ - Debounce mode: automatically triggers after a space at paragraph ends
+- Accept suggestions with Tab or word-by-word with `Cmd+→`
+- Built-in support for Vercel AI SDK completion API
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add Copilot functionality is with the `CopilotKit`, which includes pre-configured `CopilotPlugin` along with `MarkdownKit` and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`GhostText`](/docs/components/ghost-text): Renders the ghost text suggestions.
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CopilotKit } from '@/components/editor/plugins/copilot-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...CopilotKit,
+ // Place tab-using plugins after CopilotKit to avoid conflicts
+ // IndentPlugin,
+ // TabbablePlugin,
+ ],
+});
+```
+
+**Tab Key Handling**: The Copilot plugin uses the Tab key to accept suggestions. To avoid conflicts with other plugins that use Tab (like `IndentPlugin` or `TabbablePlugin`), ensure `CopilotKit` is placed before them in your plugin configuration.
+
+### Add API Route
+
+Copilot requires a server-side API endpoint to communicate with the AI model. Add the pre-configured Copilot API route:
+
+
+
+### Configure Environment
+
+Ensure your OpenAI API key is set in your environment variables:
+
+```bash title=".env.local"
+OPENAI_API_KEY="your-api-key"
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/ai @platejs/markdown
+```
+
+### Add Plugins
+
+```tsx
+import { CopilotPlugin } from '@platejs/ai/react';
+import { MarkdownPlugin } from '@platejs/markdown';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ MarkdownPlugin,
+ CopilotPlugin,
+ // Place tab-using plugins after CopilotPlugin to avoid conflicts
+ // IndentPlugin,
+ // TabbablePlugin,
+ ],
+});
+```
+
+- `MarkdownPlugin`: Required for serializing editor content to send as a prompt.
+- `CopilotPlugin`: Enables AI-powered text completion.
+
+**Tab Key Handling**: The Copilot plugin uses the Tab key to accept suggestions. To avoid conflicts with other plugins that use Tab (like `IndentPlugin` or `TabbablePlugin`), ensure `CopilotPlugin` is placed before them in your plugin configuration.
+
+### Configure Plugins
+
+```tsx
+import { CopilotPlugin } from '@platejs/ai/react';
+import { serializeMd, stripMarkdown } from '@platejs/markdown';
+import { GhostText } from '@/components/ui/ghost-text';
+
+const plugins = [
+ // ...otherPlugins,
+ MarkdownPlugin.configure({
+ options: {
+ remarkPlugins: [remarkMath, remarkGfm, remarkMdx],
+ },
+ }),
+ CopilotPlugin.configure(({ api }) => ({
+ options: {
+ completeOptions: {
+ api: '/api/ai/copilot',
+ onError: () => {
+ // Mock the API response. Remove when you implement the route /api/ai/copilot
+ api.copilot.setBlockSuggestion({
+ text: stripMarkdown('This is a mock suggestion.'),
+ });
+ },
+ onFinish: (_, completion) => {
+ if (completion === '0') return;
+
+ api.copilot.setBlockSuggestion({
+ text: stripMarkdown(completion),
+ });
+ },
+ },
+ debounceDelay: 500,
+ renderGhostText: GhostText,
+ },
+ shortcuts: {
+ accept: { keys: 'tab' },
+ acceptNextWord: { keys: 'mod+right' },
+ reject: { keys: 'escape' },
+ triggerSuggestion: { keys: 'ctrl+space' },
+ },
+ })),
+];
+```
+
+- `completeOptions`: Configures the Vercel AI SDK `useCompletion` hook.
+ - `api`: The endpoint for your AI completion route.
+ - `onError`: A callback for handling errors (used for mocking during development).
+ - `onFinish`: A callback to handle the completed suggestion. Here, it sets the suggestion in the editor.
+- `debounceDelay`: The delay in milliseconds for auto-triggering suggestions after the user stops typing.
+- `renderGhostText`: The React component used to display the suggestion inline.
+- `shortcuts`: Defines keyboard shortcuts for interacting with Copilot suggestions.
+
+### Add API Route
+
+Create an API route handler at `app/api/ai/copilot/route.ts` to process AI requests. This endpoint will receive the prompt from the editor and call the AI model.
+
+```tsx title="app/api/ai/copilot/route.ts"
+import type { NextRequest } from 'next/server';
+
+import { createOpenAI } from '@ai-sdk/openai';
+import { generateText } from 'ai';
+import { NextResponse } from 'next/server';
+
+export async function POST(req: NextRequest) {
+ const {
+ apiKey: key,
+ model = 'gpt-4o-mini',
+ prompt,
+ system,
+ } = await req.json();
+
+ const apiKey = key || process.env.OPENAI_API_KEY;
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: 'Missing OpenAI API key.' },
+ { status: 401 }
+ );
+ }
+
+ const openai = createOpenAI({ apiKey });
+
+ try {
+ const result = await generateText({
+ abortSignal: req.signal,
+ maxTokens: 50,
+ model: openai(model),
+ prompt: prompt,
+ system,
+ temperature: 0.7,
+ });
+
+ return NextResponse.json(result);
+ } catch (error) {
+ if (error instanceof Error && error.name === 'AbortError') {
+ return NextResponse.json(null, { status: 408 });
+ }
+
+ return NextResponse.json(
+ { error: 'Failed to process AI request' },
+ { status: 500 }
+ );
+ }
+}
+```
+
+Then, set your `OPENAI_API_KEY` in `.env.local`.
+
+### System Prompt
+
+The system prompt defines the AI's role and behavior. Modify the `body.system` property in `completeOptions`:
+
+```tsx
+CopilotPlugin.configure(({ api }) => ({
+ options: {
+ completeOptions: {
+ api: '/api/ai/copilot',
+ body: {
+ system: {
+ system: `You are an advanced AI writing assistant, similar to VSCode Copilot but for general text. Your task is to predict and generate the next part of the text based on the given context.
+
+Rules:
+- Continue the text naturally up to the next punctuation mark (., ,, ;, :, ?, or !).
+- Maintain style and tone. Don't repeat given text.
+- For unclear context, provide the most likely continuation.
+- Handle code snippets, lists, or structured text if needed.
+- Don't include """ in your response.
+- CRITICAL: Always end with a punctuation mark.
+- CRITICAL: Avoid starting a new block. Do not use block formatting like >, #, 1., 2., -, etc. The suggestion should continue in the same block as the context.
+- If no context is provided or you can't generate a continuation, return "0" without explanation.`,
+ },
+ },
+ // ... other options
+ },
+ // ... other plugin options
+ },
+})),
+```
+
+### User Prompt
+
+The user prompt (via `getPrompt`) determines what context is sent to the AI. You can customize it to include more context or format it differently:
+
+```tsx
+CopilotPlugin.configure(({ api }) => ({
+ options: {
+ getPrompt: ({ editor }) => {
+ const contextEntry = editor.api.block({ highest: true });
+
+ if (!contextEntry) return '';
+
+ const prompt = serializeMd(editor, {
+ value: [contextEntry[0] as TElement],
+ });
+
+ return `Continue the text up to the next punctuation mark:
+"""
+${prompt}
+"""`;
+ },
+ // ... other options
+ },
+})),
+```
+
+
+
+
+
+## Plate Plus
+
+
+
+## Customization
+
+### Switching AI Models
+
+Configure different AI models and providers in your API route:
+
+```tsx title="app/api/ai/copilot/route.ts"
+import { createOpenAI } from '@ai-sdk/openai';
+import { createAnthropic } from '@ai-sdk/anthropic';
+
+export async function POST(req: NextRequest) {
+ const {
+ model = 'gpt-4o-mini',
+ provider = 'openai',
+ prompt,
+ system
+ } = await req.json();
+
+ let aiProvider;
+
+ switch (provider) {
+ case 'anthropic':
+ aiProvider = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
+ break;
+ case 'openai':
+ default:
+ aiProvider = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
+ break;
+ }
+
+ const result = await generateText({
+ model: aiProvider(model),
+ prompt,
+ system,
+ maxTokens: 50,
+ temperature: 0.7,
+ });
+
+ return NextResponse.json(result);
+}
+```
+
+Configure the model in your `CopilotPlugin`:
+
+```tsx
+CopilotPlugin.configure(({ api }) => ({
+ options: {
+ completeOptions: {
+ api: '/api/ai/copilot',
+ body: {
+ model: 'claude-3-haiku-20240307', // Fast model for completions
+ provider: 'anthropic',
+ system: 'Your system prompt here...',
+ },
+ },
+ // ... other options
+ },
+})),
+```
+
+For more AI providers and models, see the [Vercel AI SDK documentation](https://sdk.vercel.ai/providers/ai-sdk-providers).
+
+### Custom Trigger Conditions
+
+Control when suggestions are automatically triggered:
+
+```tsx
+CopilotPlugin.configure(({ api }) => ({
+ options: {
+ triggerQuery: ({ editor }) => {
+ // Only trigger in paragraph blocks
+ const block = editor.api.block();
+ if (!block || block[0].type !== 'p') return false;
+
+ // Standard checks
+ return editor.selection &&
+ !editor.api.isExpanded() &&
+ editor.api.isAtEnd();
+ },
+ autoTriggerQuery: ({ editor }) => {
+ // Custom conditions for auto-triggering
+ const block = editor.api.block();
+ if (!block) return false;
+
+ const text = editor.api.string(block[0]);
+
+ // Trigger after question words
+ return /\b(what|how|why|when|where)\s*$/i.test(text);
+ },
+ // ... other options
+ },
+})),
+```
+
+### Security Considerations
+
+Implement security best practices for Copilot API:
+
+```tsx title="app/api/ai/copilot/route.ts"
+export async function POST(req: NextRequest) {
+ const { prompt, system } = await req.json();
+
+ // Validate prompt length
+ if (!prompt || prompt.length > 1000) {
+ return NextResponse.json({ error: 'Invalid prompt' }, { status: 400 });
+ }
+
+ // Rate limiting (implement with your preferred solution)
+ // await rateLimit(req);
+
+ // Content filtering for sensitive content
+ if (containsSensitiveContent(prompt)) {
+ return NextResponse.json({ error: 'Content filtered' }, { status: 400 });
+ }
+
+ // Process AI request...
+}
+```
+
+**Security Guidelines:**
+- **Input Validation**: Limit prompt length and validate content
+- **Rate Limiting**: Prevent abuse with request limits
+- **Content Filtering**: Filter sensitive or inappropriate content
+- **API Key Security**: Never expose API keys client-side
+- **Timeout Handling**: Handle request timeouts gracefully
+
+## Plugins
+
+### `CopilotPlugin`
+
+Plugin for AI-powered text completion suggestions.
+
+
+
+
+ Additional conditions to auto trigger copilot.
+ - **Default:** Checks:
+ - Block above is not empty
+ - Block above ends with a space
+ - No existing suggestion
+
+
+ AI completion configuration options. See [AI SDK useCompletion Parameters](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-completion#parameters).
+
+
+ Delay for debouncing auto-triggered suggestions.
+ - **Default:** `0`
+
+
+ Function to extract the next word from suggestion text.
+
+
+ Function to generate the prompt for AI completion.
+ - **Default:** Uses markdown serialization of ancestor node
+
+
+ Component to render ghost text suggestions.
+
+
+ Conditions to trigger copilot.
+ - **Default:** Checks:
+ - Selection is not expanded
+ - Selection is at block end
+
+
+
+
+## Transforms
+
+### `tf.copilot.accept()`
+
+Accepts the current suggestion and applies it to the editor content.
+
+Default Shortcut: `Tab`
+
+### `tf.copilot.acceptNextWord()`
+
+Accepts only the next word of the current suggestion, allowing for granular acceptance of suggestions.
+
+Example Shortcut: `Cmd + →`
+
+## API
+
+### `api.copilot.reject()`
+
+Resets the plugin state to its initial condition:
+Default Shortcut: `Escape`
+
+### `api.copilot.triggerSuggestion()`
+
+Triggers a new suggestion request. The request may be debounced based on the plugin configuration.
+
+Example Shortcut: `Ctrl + Space`
+
+### `api.copilot.setBlockSuggestion()`
+
+Sets suggestion text for a block.
+
+
+
+
+ Options for setting the block suggestion.
+
+
+
+
+
+ The suggestion text to set.
+
+
+ Target block ID.
+ - **Default:** Current block
+
+
+
+
+### `api.copilot.stop()`
+
+Stops ongoing suggestion requests and cleans up:
+
+- Cancels debounced trigger calls
+- Aborts current API request
+- Resets abort controller
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/comment.cn.mdx b/apps/www/content/docs/(plugins)/(collaboration)/comment.cn.mdx
new file mode 100644
index 0000000000..f0e604f379
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/comment.cn.mdx
@@ -0,0 +1,486 @@
+---
+title: 评论功能
+docs:
+ - route: /docs/components/comment-node
+ title: 评论叶子节点
+ - route: /docs/components/comment-toolbar-button
+ title: 评论工具栏按钮
+ - route: /docs/components/block-discussion
+ title: 区块讨论
+---
+
+
+
+
+
+## 功能特性
+
+- **文本评论**: 以内联标注形式添加文本评论标记
+- **重叠评论**: 支持同一段文本上的多个评论
+- **草稿评论**: 在最终确定前创建评论草稿
+- **状态追踪**: 跟踪评论状态和用户交互
+- **讨论集成**: 与讨论插件配合实现完整协作功能
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的添加评论功能方式是使用 `CommentKit`,它包含预配置的 `commentPlugin` 和相关组件以及它们的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`CommentLeaf`](/docs/components/comment-node): 渲染评论文本标记
+- [`BlockDiscussion`](/docs/components/block-discussion): 渲染集成评论功能的讨论界面
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CommentKit } from '@/components/editor/plugins/comment-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...CommentKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/comment
+```
+
+### 扩展评论插件
+
+创建带有状态管理扩展配置的评论插件:
+
+```tsx
+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;
+ }
+>;
+
+export const commentPlugin = toTPlatePlugin(
+ BaseCommentPlugin,
+ ({ editor }) => ({
+ options: {
+ activeId: null,
+ commentingBlock: null,
+ hoverId: null,
+ uniquePathMap: new Map(),
+ },
+ render: {
+ node: CommentLeaf,
+ },
+ })
+);
+```
+
+- `options.activeId`: 当前激活评论ID,用于视觉高亮
+- `options.commentingBlock`: 当前被评论区块的路径
+- `options.hoverId`: 当前悬停评论ID,用于悬停效果
+- `options.uniquePathMap`: 追踪评论解析唯一路径的映射表
+- `render.node`: 指定 [`CommentLeaf`](/docs/components/comment-node) 来渲染评论文本标记
+
+### 添加点击处理
+
+添加点击处理来管理激活评论状态:
+
+```tsx
+export const commentPlugin = toTPlatePlugin(
+ BaseCommentPlugin,
+ ({ editor }) => ({
+ handlers: {
+ // 点击评论标记时设置激活评论
+ 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();
+ },
+ },
+ // ... 之前的options和render配置
+ })
+);
+```
+
+点击处理器追踪当前激活的评论:
+- **检测评论点击**: 遍历DOM查找评论元素
+- **设置激活状态**: 点击评论时更新`activeId`
+- **清除状态**: 点击评论外部时取消`activeId`
+- **视觉反馈**: 在评论组件中启用悬停/激活样式
+
+### 扩展转换
+
+扩展`setDraft`转换以增强功能:
+
+```tsx
+export const commentPlugin = toTPlatePlugin(
+ BaseCommentPlugin,
+ ({ editor }) => ({
+ // ... 之前的配置
+ })
+)
+ .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' },
+ },
+ });
+```
+
+### 添加工具栏按钮
+
+您可以在[工具栏](/docs/toolbar)中添加[`CommentToolbarButton`](/docs/components/comment-toolbar-button)来为选中文本添加评论。
+
+### 添加插件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ commentPlugin,
+ ],
+});
+```
+
+### 讨论集成
+
+评论插件可与[讨论插件](/docs/discussion)配合实现完整协作:
+
+```tsx
+import { discussionPlugin } from '@/components/editor/plugins/discussion-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ discussionPlugin,
+ commentPlugin,
+ ],
+});
+```
+
+
+
+## 键盘快捷键
+
+
+
+ 在选中文本上添加评论
+
+
+
+## Plate Plus
+
+
+
+## 插件
+
+### `CommentPlugin`
+
+用于创建和管理文本评论的插件,具有状态追踪和讨论集成功能。
+
+
+
+
+ 当前激活评论ID,用于视觉高亮。内部用于状态追踪。
+
+
+ 当前被评论区块的路径。
+
+
+ 当前悬停评论ID,用于悬停效果。
+
+
+ 追踪评论解析唯一路径的映射表。
+
+
+
+
+## API
+
+### `api.comment.has`
+
+检查编辑器中是否存在指定ID的评论。
+
+
+
+
+ 包含要检查评论ID的选项。
+
+
+
+ 评论是否存在。
+
+
+
+### `api.comment.node`
+
+获取评论节点entry。
+
+
+
+ 查找节点的选项。
+
+
+ 找到的评论节点entry。
+
+
+
+### `api.comment.nodeId`
+
+从叶子节点获取评论ID。
+
+
+
+
+ 评论叶子节点。
+
+
+
+ 找到的评论ID。
+
+
+
+### `api.comment.nodes`
+
+获取所有匹配选项的评论节点entry。
+
+
+
+ 查找节点的选项。
+
+
+ 评论节点entry数组。
+
+
+
+## 转换
+
+### `tf.comment.removeMark`
+
+从当前选区或指定位置移除评论标记。
+
+
+
+### `tf.comment.setDraft`
+
+在当前选区设置草稿评论标记。
+
+
+
+ 设置草稿评论的选项。
+
+
+
+### `tf.comment.unsetMark`
+
+从编辑器中取消设置指定ID的评论节点。
+
+
+
+
+ 包含要取消评论ID的选项。
+
+
+
+
+## 工具函数
+
+### `getCommentCount`
+
+获取评论节点中非草稿评论的数量。
+
+
+
+
+ 评论节点。
+
+
+
+ 评论数量。
+
+
+
+### `getCommentKey`
+
+基于提供的ID生成评论key。
+
+
+
+
+ 评论ID。
+
+
+
+ 生成的评论key。
+
+
+
+### `getCommentKeyId`
+
+从评论key中提取评论ID。
+
+
+
+
+ 评论key。
+
+
+
+ 提取的评论ID。
+
+
+
+### `getCommentKeys`
+
+返回给定节点中存在的评论key数组。
+
+
+
+
+ 要检查评论key的节点。
+
+
+
+ 评论key数组。
+
+
+
+### `getDraftCommentKey`
+
+获取草稿评论使用的key。
+
+
+
+ 草稿评论key。
+
+
+
+### `isCommentKey`
+
+检查给定key是否为评论key。
+
+
+
+
+ 要检查的key。
+
+
+
+ 是否为评论key。
+
+
+
+### `isCommentNodeById`
+
+检查给定节点是否为指定ID的评论。
+
+
+
+
+ 要检查的节点。
+
+
+ 评论ID。
+
+
+
+ 节点是否为指定ID的评论。
+
+
+
+## 类型
+
+### `TCommentText`
+
+可包含评论的文本节点。
+
+
+
+
+ 该文本节点是否包含评论。
+
+
+ 按评论ID索引的评论数据。一个文本节点可包含多个评论。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/comment.mdx b/apps/www/content/docs/(plugins)/(collaboration)/comment.mdx
new file mode 100644
index 0000000000..4ba7b5d91a
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/comment.mdx
@@ -0,0 +1,488 @@
+---
+title: Comment
+docs:
+ - route: https://pro.platejs.org/docs/examples/discussion
+ title: Plus
+ - route: /docs/components/comment-node
+ title: Comment Leaf
+ - route: /docs/components/comment-toolbar-button
+ title: Comment Toolbar Button
+ - route: /docs/components/block-discussion
+ title: Block Discussion
+---
+
+
+
+
+
+## Features
+
+- **Text Comments:** Add comments as text marks with inline annotations
+- **Overlapping Comments:** Support multiple comments on the same text
+- **Draft Comments:** Create draft comments before finalizing
+- **State Tracking:** Track comment state and user interactions
+- **Discussion Integration:** Works with discussion plugin for complete collaboration
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add comment functionality is with the `CommentKit`, which includes pre-configured `commentPlugin` and related components along with their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`CommentLeaf`](/docs/components/comment-node): Renders comment text marks
+- [`BlockDiscussion`](/docs/components/block-discussion): Renders discussion UI with comments integration
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CommentKit } from '@/components/editor/plugins/comment-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...CommentKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/comment
+```
+
+### Extend Comment Plugin
+
+Create the comment plugin with extended configuration for state management:
+
+```tsx
+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;
+ }
+>;
+
+export const commentPlugin = toTPlatePlugin(
+ BaseCommentPlugin,
+ ({ editor }) => ({
+ options: {
+ activeId: null,
+ commentingBlock: null,
+ hoverId: null,
+ uniquePathMap: new Map(),
+ },
+ render: {
+ node: CommentLeaf,
+ },
+ })
+);
+```
+
+- `options.activeId`: Currently active comment ID for visual highlighting
+- `options.commentingBlock`: Path of the block currently being commented
+- `options.hoverId`: Currently hovered comment ID for hover effects
+- `options.uniquePathMap`: Map tracking unique paths for comment resolution
+- `render.node`: Assigns [`CommentLeaf`](/docs/components/comment-node) to render comment text marks
+
+### Add Click Handler
+
+Add click handling to manage active comment state:
+
+```tsx
+export const commentPlugin = toTPlatePlugin(
+ 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:
+- **Detects comment clicks**: Traverses DOM to find comment elements
+- **Sets active state**: Updates `activeId` when clicking on comments
+- **Clears state**: Unsets `activeId` when clicking outside comments
+- **Visual feedback**: Enables hover/active styling in comment components
+
+### Extend Transforms
+
+Extend the `setDraft` transform for enhanced functionality:
+
+```tsx
+export const commentPlugin = toTPlatePlugin(
+ 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' },
+ },
+ });
+```
+
+### Add Toolbar Button
+
+You can add [`CommentToolbarButton`](/docs/components/comment-toolbar-button) to your [Toolbar](/docs/toolbar) to add comments on selected text.
+
+### Add Plugins
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ commentPlugin,
+ ],
+});
+```
+
+### Discussion Integration
+
+The comment plugin works with the [discussion plugin](/docs/discussion) for complete collaboration:
+
+```tsx
+import { discussionPlugin } from '@/components/editor/plugins/discussion-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ discussionPlugin,
+ commentPlugin,
+ ],
+});
+```
+
+
+
+## Keyboard Shortcuts
+
+
+
+ Add a comment on the selected text.
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `CommentPlugin`
+
+Plugin for creating and managing text comments with state tracking and discussion integration.
+
+
+
+
+ Currently active comment ID for visual highlighting. Used internally to track state.
+
+
+ Path of the block currently being commented on.
+
+
+ Currently hovered comment ID for hover effects.
+
+
+ Map tracking unique paths for comment resolution.
+
+
+
+
+## API
+
+### `api.comment.has`
+
+Checks if a comment with the given ID exists in the editor.
+
+
+
+
+ Options containing the comment ID to check.
+
+
+
+ Whether the comment exists.
+
+
+
+### `api.comment.node`
+
+Gets a comment node entry.
+
+
+
+ Options for finding the node.
+
+
+ The comment node entry if found.
+
+
+
+### `api.comment.nodeId`
+
+Gets the ID of a comment from a leaf node.
+
+
+
+
+ The comment leaf node.
+
+
+
+ The comment ID if found.
+
+
+
+### `api.comment.nodes`
+
+Gets all comment node entries matching the options.
+
+
+
+ Options for finding the nodes.
+
+
+ Array of comment node entries.
+
+
+
+## Transforms
+
+### `tf.comment.removeMark`
+
+Removes the comment mark from the current selection or a specified location.
+
+
+
+### `tf.comment.setDraft`
+
+Sets a draft comment mark at the current selection.
+
+
+
+ Options for setting the draft comment.
+
+
+
+### `tf.comment.unsetMark`
+
+Unsets comment nodes with the specified ID from the editor.
+
+
+
+
+ Options containing the comment ID to unset.
+
+
+
+
+## Utilities
+
+### `getCommentCount`
+
+Gets the count of non-draft comments in a comment node.
+
+
+
+
+ The comment node.
+
+
+
+ The count of comments.
+
+
+
+### `getCommentKey`
+
+Generates a comment key based on the provided ID.
+
+
+
+
+ The ID of the comment.
+
+
+
+ The generated comment key.
+
+
+
+### `getCommentKeyId`
+
+Extracts the comment ID from a comment key.
+
+
+
+
+ The comment key.
+
+
+
+ The extracted comment ID.
+
+
+
+### `getCommentKeys`
+
+Returns an array of comment keys present in the given node.
+
+
+
+
+ The node to check for comment keys.
+
+
+
+ Array of comment keys.
+
+
+
+### `getDraftCommentKey`
+
+Gets the key used for draft comments.
+
+
+
+ The draft comment key.
+
+
+
+### `isCommentKey`
+
+Checks if a given key is a comment key.
+
+
+
+
+ The key to check.
+
+
+
+ Whether the key is a comment key.
+
+
+
+### `isCommentNodeById`
+
+Checks if a given node is a comment with the specified ID.
+
+
+
+
+ The node to check.
+
+
+ The ID of the comment.
+
+
+
+ Whether the node is a comment with the specified ID.
+
+
+
+## Types
+
+### `TCommentText`
+
+Text nodes that can contain comments.
+
+
+
+
+ Whether this text node contains comments.
+
+
+ Comment data keyed by comment ID. Multiple comments can exist in one text node.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/comments.cn.mdx b/apps/www/content/docs/(plugins)/(collaboration)/comments.cn.mdx
new file mode 100644
index 0000000000..ef5fef9a03
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/comments.cn.mdx
@@ -0,0 +1,248 @@
+---
+title: 评论功能
+docs:
+ - route: /docs/components/comment-leaf
+ title: 评论标记组件
+ - route: /docs/components/comment-toolbar-button
+ title: 评论工具栏按钮
+ - route: /docs/components/block-discussion
+ title: 区块讨论
+---
+
+
+
+
+
+## 功能特性
+
+- 以文本标记形式添加评论
+- 支持重叠评论
+- 支持撤销/恢复解决和删除操作
+
+
+
+## 安装
+```bash
+npm install @platejs/comment
+```
+
+## 使用方法
+
+```tsx
+import { commentPlugin } from '@/components/editor/plugins/comment-plugin';
+import { discussionPlugin } from '@/components/editor/plugins/discussion-plugin';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ discussionPlugin,
+ commentPlugin,
+ ],
+});
+```
+
+评论插件需与讨论插件配合使用以提供完整的讨论系统。讨论插件负责管理讨论界面和用户交互层。示例中提供了两个插件的完整源码。
+
+## 快捷键
+
+
+
+ 为选中文本添加评论
+
+
+
+## 示例
+
+### Plate UI
+
+参考上方预览组件。
+
+### Plate Plus
+
+
+
+## 插件
+
+### `CommentPlugin`
+
+## API接口
+
+### `tf.comment.removeMark`
+
+从编辑器中移除评论标记。
+
+### `tf.comment.setDraft`
+
+在当前选区设置草稿评论标记。
+
+### `tf.comment.unsetMark`
+
+从编辑器中移除指定ID的评论节点。
+
+
+
+
+ 要移除的评论节点ID
+
+
+
+
+### `api.comment.has`
+
+检查指定ID的评论是否存在。
+
+
+
+
+ 要检查的评论ID
+
+
+
+ 评论是否存在
+
+
+
+### `api.comment.node`
+
+获取评论节点entry。
+
+
+
+ 查找节点的配置选项
+
+
+ 找到的评论节点entry(如存在)
+
+
+
+### `api.comment.nodeId`
+
+从leaf节点获取评论ID。
+
+
+
+
+ 评论leaf节点
+
+
+
+ 找到的评论ID(如存在)
+
+
+
+### `api.comment.nodes`
+
+获取所有匹配条件的评论节点entry。
+
+
+
+ 查找节点的配置选项
+
+
+ 评论节点entry数组
+
+
+
+### `getCommentCount`
+
+获取评论节点中的非草稿评论数量。
+
+
+
+
+ 评论节点
+
+
+
+
+### `getCommentKey`
+
+根据ID生成评论key。
+
+
+
+
+ 评论ID
+
+
+
+
+### `getCommentKeyId`
+
+从评论key中提取评论ID。
+
+
+
+
+ 评论key
+
+
+
+
+### `getCommentKeys`
+
+返回节点中存在的所有评论key数组。
+
+
+
+
+ 要检查的节点
+
+
+
+
+### `getDraftCommentKey`
+
+获取草稿评论使用的key。
+
+### `isCommentKey`
+
+检查给定key是否为评论key。
+
+
+
+
+ 要检查的key
+
+
+
+
+ 是否为评论key
+
+
+
+### `isCommentNodeById`
+
+检查给定节点是否为指定ID的评论。
+
+
+
+
+ 要检查的节点
+
+
+ 评论ID
+
+
+
+
+ 是否为指定ID的评论节点
+
+
+
+## 类型定义
+
+### `TCommentText`
+
+可包含评论的文本节点接口。
+
+
+
+
+ 是否为评论节点
+
+
+ 评论ID标识。单个文本节点可包含多个评论。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/discussion.cn.mdx b/apps/www/content/docs/(plugins)/(collaboration)/discussion.cn.mdx
new file mode 100644
index 0000000000..a5094850b9
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/discussion.cn.mdx
@@ -0,0 +1,222 @@
+---
+title: 讨论
+docs:
+ - route: /docs/components/block-discussion
+ title: 块讨论
+---
+
+
+
+
+
+## 功能特点
+
+- **用户管理**: 存储和管理带有头像和名称的用户数据
+- **讨论线程**: 管理带有评论的讨论数据结构
+- **当前用户跟踪**: 跟踪当前活跃用户以进行协作
+- **数据存储**: 用于存储协作状态的纯 UI 插件
+- **选择器 API**: 通过插件选择器轻松访问用户数据
+
+
+
+## Kit 使用
+
+
+
+### 安装
+
+添加讨论功能最快的方法是使用 `DiscussionKit`,它包含预配置的 `discussionPlugin` 及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`BlockDiscussion`](/docs/components/block-discussion): 在节点上方渲染讨论 UI
+
+### 添加 Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { DiscussionKit } from '@/components/editor/plugins/discussion-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...DiscussionKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/comment @platejs/suggestion
+```
+
+### 创建插件
+
+```tsx
+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
+- `options.discussions`: 讨论数据结构数组
+- `options.users`: 将用户 ID 映射到用户数据的对象
+- `render.aboveNodes`: 在节点上方渲染 [`BlockDiscussion`](/docs/components/block-discussion) 用于讨论 UI
+- `selectors.currentUser`: 获取当前用户数据
+- `selectors.user`: 通过 ID 获取用户数据
+
+### 添加插件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ discussionPlugin,
+ ],
+});
+```
+
+
+
+## 插件
+
+### `discussionPlugin`
+
+用于管理包括用户和讨论数据在内的协作状态的纯 UI 插件。
+
+
+
+
+ 协作会话中当前活跃用户的 ID。
+
+
+ 包含评论和元数据的讨论对象数组。
+
+
+ 将用户 ID 映射到包括名称和头像在内的用户信息的对象。
+
+
+
+
+## 选择器
+
+### `currentUser`
+
+获取当前用户数据。
+
+
+
+ 当前用户的数据,包括 id、name 和 avatarUrl。
+
+
+
+### `user`
+
+通过 ID 获取用户数据。
+
+
+
+
+ 要查找的用户 ID。
+
+
+
+ 如果找到则返回用户数据,否则返回 undefined。
+
+
+
+## 类型
+
+### `TDiscussion`
+
+包含评论和元数据的讨论数据结构。
+
+
+
+
+ 讨论的唯一标识符。
+
+
+ 讨论线程中的评论数组。
+
+
+ 讨论创建的时间。
+
+
+ 讨论是否已解决。
+
+
+ 创建讨论的用户 ID。
+
+
+ 与此讨论相关的文档内容。
+
+
+
+
+### `UserData`
+
+用于协作的用户信息结构。
+
+
+
+
+ 用户的唯一标识符。
+
+
+ 用户的显示名称。
+
+
+ 用户头像图片的 URL。
+
+
+ 用于用户识别的可选颜色色调。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/discussion.mdx b/apps/www/content/docs/(plugins)/(collaboration)/discussion.mdx
new file mode 100644
index 0000000000..e20e1000bd
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/discussion.mdx
@@ -0,0 +1,228 @@
+---
+title: Discussion
+docs:
+ - route: https://pro.platejs.org/docs/examples/discussion
+ title: Plus
+ - route: /docs/components/block-discussion
+ title: Block Discussion
+---
+
+
+
+
+
+## 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](/docs/installation/plate-ui) components.
+
+
+
+- [`BlockDiscussion`](/docs/components/block-discussion): Renders discussion UI above nodes
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { DiscussionKit } from '@/components/editor/plugins/discussion-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...DiscussionKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/comment @platejs/suggestion
+```
+
+### Create Plugin
+
+```tsx
+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`](/docs/components/block-discussion) above nodes for discussion UI
+- `selectors.currentUser`: Gets the current user data
+- `selectors.user`: Gets user data by ID
+
+### Add Plugin
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ discussionPlugin,
+ ],
+});
+```
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `discussionPlugin`
+
+Pure UI plugin for managing collaboration state including users and discussion data.
+
+
+
+
+ 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.
+
+
+
+ The current user's data including id, name, and avatarUrl.
+
+
+
+### `user`
+
+Gets user data by ID.
+
+
+
+
+ The user ID to look up.
+
+
+
+ The user data if found, undefined otherwise.
+
+
+
+## Types
+
+### `TDiscussion`
+
+Discussion data structure containing comments and metadata.
+
+
+
+
+ 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.
+
+
+
+
+ Unique identifier for the user.
+
+
+ Display name of the user.
+
+
+ URL for the user's avatar image.
+
+
+ Optional color hue for user identification.
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/suggestion.cn.mdx b/apps/www/content/docs/(plugins)/(collaboration)/suggestion.cn.mdx
new file mode 100644
index 0000000000..a37f39381c
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/suggestion.cn.mdx
@@ -0,0 +1,461 @@
+---
+title: 建议功能
+docs:
+ - route: /docs/components/suggestion-node
+ title: 建议文本节点
+ - route: /docs/components/suggestion-toolbar-button
+ title: 建议工具栏按钮
+ - route: /docs/components/block-suggestion
+ title: 区块建议
+ - route: /docs/components/block-discussion
+ title: 区块讨论
+---
+
+
+
+
+
+## 功能特性
+
+- **文本建议**:以内联标注形式添加文本标记建议
+- **区块建议**:为整个内容区块创建建议
+- **状态追踪**:追踪建议状态和用户交互
+- **撤销/重做支持**:完整支持建议变更的撤销/重做
+- **讨论集成**:与讨论插件协同工作实现完整协作
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的添加建议功能方式是使用 `SuggestionKit`,它包含预配置的 `SuggestionPlugin` 及相关组件,以及它们的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`SuggestionLeaf`](/docs/components/suggestion-node):渲染建议文本标记
+- [`BlockSuggestion`](/docs/components/block-suggestion):渲染区块级建议
+- [`SuggestionLineBreak`](/docs/components/suggestion-node):处理建议中的换行符
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...SuggestionKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/suggestion
+```
+
+### 扩展建议插件
+
+创建带有状态管理扩展配置的建议插件:
+
+```tsx
+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;
+ }
+>;
+
+export const suggestionPlugin = toTPlatePlugin(
+ 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 ;
+ },
+ },
+ })
+);
+```
+
+- `options.activeId`:当前活跃建议ID,用于视觉高亮
+- `options.currentUserId`:创建建议的当前用户ID
+- `options.hoverId`:当前悬停建议ID,用于悬停效果
+- `options.uniquePathMap`:追踪建议解析唯一路径的映射表
+- `render.node`:指定 [`SuggestionLeaf`](/docs/components/suggestion-node) 渲染建议文本标记
+- `render.belowRootNodes`:为区块级建议渲染 [`BlockSuggestion`](/docs/components/block-suggestion)
+
+### 添加点击处理器
+
+添加点击处理以管理活跃建议状态:
+
+```tsx
+export const suggestionPlugin = toTPlatePlugin(
+ 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`
+- **视觉反馈**:在建议组件中启用悬停/活跃样式
+
+### 添加插件
+
+```tsx
+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`](/docs/components/suggestion-node) 处理建议中的换行符
+
+### 启用建议模式
+
+使用插件API控制建议模式:
+
+```tsx
+import { useEditorRef, usePluginOption } from 'platejs/react';
+
+function SuggestionToolbar() {
+ const editor = useEditorRef();
+ const isSuggesting = usePluginOption(suggestionPlugin, 'isSuggesting');
+
+ const toggleSuggesting = () => {
+ editor.setOption(suggestionPlugin, 'isSuggesting', !isSuggesting);
+ };
+
+ return (
+
+ {isSuggesting ? '停止建议' : '开始建议'}
+
+ );
+}
+```
+
+### 添加工具栏按钮
+
+您可以在[工具栏](/docs/toolbar)中添加 [`SuggestionToolbarButton`](/docs/components/suggestion-toolbar-button) 来切换编辑器的建议模式。
+
+### 讨论集成
+
+建议插件与[讨论插件](/docs/discussion)协同工作实现完整协作:
+
+```tsx
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ discussionPlugin,
+ suggestionPlugin.configure({
+ options: {
+ currentUserId: 'alice',
+ },
+ }),
+ suggestionLineBreakPlugin,
+ ],
+});
+```
+
+
+
+## 键盘快捷键
+
+
+
+ 在选中文本上添加建议
+
+
+
+## Plate Plus
+
+
+
+## 插件
+
+### `SuggestionPlugin`
+
+用于创建和管理文本及区块建议的插件,具有状态追踪和讨论集成功能。
+
+
+
+
+ 创建建议的当前用户ID。正确归属建议所必需。
+
+
+ 编辑器当前是否处于建议模式。内部用于追踪状态。
+
+
+
+
+## API
+
+### `api.suggestion.dataList`
+
+从文本节点获取建议数据。
+
+
+
+
+ 建议文本节点。
+
+
+
+ 建议数据数组。
+
+
+
+### `api.suggestion.isBlockSuggestion`
+
+检查节点是否为区块建议元素。
+
+
+
+
+ 要检查的节点。
+
+
+
+ 是否为区块建议。
+
+
+
+### `api.suggestion.node`
+
+获取建议节点条目。
+
+
+
+ 查找节点的选项。
+
+
+ 找到的建议节点条目。
+
+
+
+### `api.suggestion.nodeId`
+
+从节点获取建议ID。
+
+
+
+
+ 要获取ID的节点。
+
+
+
+ 找到的建议ID。
+
+
+
+### `api.suggestion.nodes`
+
+获取所有匹配选项的建议节点条目。
+
+
+
+ 查找节点的选项。
+
+
+ 建议节点条目数组。
+
+
+
+### `api.suggestion.suggestionData`
+
+从节点获取建议数据。
+
+
+
+
+ 要获取建议数据的节点。
+
+
+
+ 找到的建议数据。
+
+
+
+### `api.suggestion.withoutSuggestions`
+
+在执行函数时临时禁用建议。
+
+
+
+
+ 要执行的函数。
+
+
+
+
+## 类型
+
+### `TSuggestionText`
+
+可包含建议的文本节点。
+
+
+
+
+ 是否为建议。
+
+
+ 建议数据。单个文本节点可包含多个建议。
+
+
+
+
+### `TSuggestionElement`
+
+包含建议元数据的区块元素。
+
+
+
+
+ 区块级建议数据,包括类型、用户和时间信息。
+
+
+
+
+### `TInlineSuggestionData`
+
+内联文本建议的数据结构。
+
+
+
+
+ 建议的唯一标识符。
+
+
+ 创建建议的用户ID。
+
+
+ 建议创建的时间戳。
+
+
+ 建议操作类型。
+
+
+ 对于更新建议,建议的新标记属性。
+
+
+ 对于更新建议,先前的标记属性。
+
+
+
+
+### `TSuggestionData`
+
+区块级建议的数据结构。
+
+
+
+
+ 建议的唯一标识符。
+
+
+ 创建建议的用户ID。
+
+
+ 建议创建的时间戳。
+
+
+ 区块建议操作类型。
+
+
+ 该建议是否代表换行符插入。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/suggestion.mdx b/apps/www/content/docs/(plugins)/(collaboration)/suggestion.mdx
new file mode 100644
index 0000000000..a3cb5e9027
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/suggestion.mdx
@@ -0,0 +1,463 @@
+---
+title: Suggestion
+docs:
+ - route: https://pro.platejs.org/docs/examples/discussion
+ title: Plus
+ - route: /docs/components/suggestion-node
+ title: Suggestion Leaf
+ - route: /docs/components/suggestion-toolbar-button
+ title: Suggestion Toolbar Button
+ - route: /docs/components/block-suggestion
+ title: Block suggestion
+ - route: /docs/components/block-discussion
+ title: Block discussion
+---
+
+
+
+
+
+## Features
+
+- **Text Suggestions:** Add suggestions as text marks with inline annotations
+- **Block Suggestions:** Create suggestions for entire blocks of content
+- **State Tracking:** Track suggestion state and user interactions
+- **Undo/Redo Support:** Full undo/redo support for suggestion changes
+- **Discussion Integration:** Works with discussion plugin for complete collaboration
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add suggestion functionality is with the `SuggestionKit`, which includes pre-configured `SuggestionPlugin` and related components along with their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`SuggestionLeaf`](/docs/components/suggestion-node): Renders suggestion text marks
+- [`BlockSuggestion`](/docs/components/block-suggestion): Renders block-level suggestions
+- [`SuggestionLineBreak`](/docs/components/suggestion-node): Handles line breaks in suggestions
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { SuggestionKit } from '@/components/editor/plugins/suggestion-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...SuggestionKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/suggestion
+```
+
+### Extend Suggestion Plugin
+
+Create the suggestion plugin with extended configuration for state management:
+
+```tsx
+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;
+ }
+>;
+
+export const suggestionPlugin = toTPlatePlugin(
+ 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 ;
+ },
+ },
+ })
+);
+```
+
+- `options.activeId`: Currently active suggestion ID for visual highlighting
+- `options.currentUserId`: ID of the current user creating suggestions
+- `options.hoverId`: Currently hovered suggestion ID for hover effects
+- `options.uniquePathMap`: Map tracking unique paths for suggestion resolution
+- `render.node`: Assigns [`SuggestionLeaf`](/docs/components/suggestion-node) to render suggestion text marks
+- `render.belowRootNodes`: Renders [`BlockSuggestion`](/docs/components/block-suggestion) for block-level suggestions
+
+### Add Click Handler
+
+Add click handling to manage active suggestion state:
+
+```tsx
+export const suggestionPlugin = toTPlatePlugin(
+ 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:
+- **Detects suggestion clicks**: Traverses DOM to find suggestion elements
+- **Sets active state**: Updates `activeId` when clicking on suggestions
+- **Clears state**: Unsets `activeId` when clicking outside suggestions
+- **Visual feedback**: Enables hover/active styling in suggestion components
+
+### Add Plugins
+
+```tsx
+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: [
+ // ...otherPlugins,
+ suggestionPlugin,
+ suggestionLineBreakPlugin,
+ ],
+});
+```
+
+- `render.belowNodes`: Renders [`SuggestionLineBreak`](/docs/components/suggestion-node) below nodes to handle line break suggestions
+
+### Enable Suggestion Mode
+
+Use the plugin's API to control suggestion mode:
+
+```tsx
+import { useEditorRef, usePluginOption } from 'platejs/react';
+
+function SuggestionToolbar() {
+ const editor = useEditorRef();
+ const isSuggesting = usePluginOption(suggestionPlugin, 'isSuggesting');
+
+ const toggleSuggesting = () => {
+ editor.setOption(suggestionPlugin, 'isSuggesting', !isSuggesting);
+ };
+
+ return (
+
+ {isSuggesting ? 'Stop Suggesting' : 'Start Suggesting'}
+
+ );
+}
+```
+
+### Add Toolbar Button
+
+You can add [`SuggestionToolbarButton`](/docs/components/suggestion-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle suggestion mode in the editor.
+
+### Discussion Integration
+
+The suggestion plugin works with the [discussion plugin](/docs/discussion) for complete collaboration:
+
+```tsx
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ discussionPlugin,
+ suggestionPlugin.configure({
+ options: {
+ currentUserId: 'alice',
+ },
+ }),
+ suggestionLineBreakPlugin,
+ ],
+});
+```
+
+
+
+## Keyboard Shortcuts
+
+
+
+ Add a suggestion on the selected text.
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `SuggestionPlugin`
+
+Plugin for creating and managing text and block suggestions with state tracking and discussion integration.
+
+
+
+
+ ID of the current user creating suggestions. Required for proper suggestion attribution.
+
+
+ Whether the editor is currently in suggestion mode. Used internally to track state.
+
+
+
+
+## API
+
+### `api.suggestion.dataList`
+
+Gets suggestion data from a text node.
+
+
+
+
+ The suggestion text node.
+
+
+
+ Array of suggestion data.
+
+
+
+### `api.suggestion.isBlockSuggestion`
+
+Checks if a node is a block suggestion element.
+
+
+
+
+ The node to check.
+
+
+
+ Whether the node is a block suggestion.
+
+
+
+### `api.suggestion.node`
+
+Gets a suggestion node entry.
+
+
+
+ Options for finding the node.
+
+
+ The suggestion node entry if found.
+
+
+
+### `api.suggestion.nodeId`
+
+Gets the ID of a suggestion from a node.
+
+
+
+
+ The node to get ID from.
+
+
+
+ The suggestion ID if found.
+
+
+
+### `api.suggestion.nodes`
+
+Gets all suggestion node entries matching the options.
+
+
+
+ Options for finding the nodes.
+
+
+ Array of suggestion node entries.
+
+
+
+### `api.suggestion.suggestionData`
+
+Gets suggestion data from a node.
+
+
+
+
+ The node to get suggestion data from.
+
+
+
+ The suggestion data if found.
+
+
+
+### `api.suggestion.withoutSuggestions`
+
+Temporarily disables suggestions while executing a function.
+
+
+
+
+ The function to execute.
+
+
+
+
+## Types
+
+### `TSuggestionText`
+
+Text nodes that can contain suggestions.
+
+
+
+
+ Whether this is a suggestion.
+
+
+ Suggestion data. Multiple suggestions can exist in one text node.
+
+
+
+
+### `TSuggestionElement`
+
+Block elements that contain suggestion metadata.
+
+
+
+
+ Block-level suggestion data including type, user, and timing information.
+
+
+
+
+### `TInlineSuggestionData`
+
+Data structure for inline text suggestions.
+
+
+
+
+ Unique identifier for the suggestion.
+
+
+ ID of the user who created the suggestion.
+
+
+ Timestamp when the suggestion was created.
+
+
+ Type of suggestion operation.
+
+
+ For update suggestions, the new mark properties being suggested.
+
+
+ For update suggestions, the previous mark properties.
+
+
+
+
+### `TSuggestionData`
+
+Data structure for block-level suggestions.
+
+
+
+
+ Unique identifier for the suggestion.
+
+
+ ID of the user who created the suggestion.
+
+
+ Timestamp when the suggestion was created.
+
+
+ Type of block suggestion operation.
+
+
+ Whether this suggestion represents a line break insertion.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/yjs.cn.mdx b/apps/www/content/docs/(plugins)/(collaboration)/yjs.cn.mdx
new file mode 100644
index 0000000000..d292a035bb
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/yjs.cn.mdx
@@ -0,0 +1,557 @@
+---
+title: 协作编辑
+description: 使用 Yjs 实现实时协作
+toc: true
+---
+
+
+
+
+
+## 核心特性
+
+- **多提供者支持**:通过 [Yjs](https://github.com/yjs/yjs) 和 [slate-yjs](https://docs.slate-yjs.dev/) 实现实时协作。支持多个同步提供者(如 Hocuspocus + WebRTC)同时操作共享的 `Y.Doc`。
+- **内置提供者**:开箱即用支持 [Hocuspocus](https://tiptap.dev/hocuspocus)(服务端方案)和 [WebRTC](https://github.com/yjs/y-webrtc)(点对点方案)。
+- **自定义提供者**:通过实现 `UnifiedProvider` 接口可扩展自定义提供者(如 IndexedDB 离线存储)。
+- **状态感知与光标**:集成 Yjs Awareness 协议共享光标位置等临时状态,包含 [`RemoteCursorOverlay`](/docs/components/remote-cursor-overlay) 组件渲染远程光标。
+- **可定制光标**:通过 `cursors` 配置光标外观(名称、颜色)。
+- **手动生命周期**:提供明确的 `init` 和 `destroy` 方法管理 Yjs 连接。
+
+
+
+## 使用指南
+
+
+
+### 安装
+
+安装核心 Yjs 插件和所需提供者包:
+
+```bash
+npm install @platejs/yjs
+```
+
+Hocuspocus 服务端方案:
+
+```bash
+npm install @hocuspocus/provider
+```
+
+WebRTC 点对点方案:
+
+```bash
+npm install y-webrtc
+```
+
+### 添加插件
+
+```tsx
+import { YjsPlugin } from '@platejs/yjs/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ YjsPlugin,
+ ],
+ // 重要:使用 Yjs 时需跳过 Plate 的默认初始化
+ skipInitialization: true,
+});
+```
+
+
+ 创建编辑器时必须设置 `skipInitialization: true`。Yjs 负责管理初始文档状态,跳过 Plate 的默认值初始化可避免冲突。
+
+
+### 配置 YjsPlugin
+
+配置插件提供者和光标设置:
+
+```tsx
+import { YjsPlugin } from '@platejs/yjs/react';
+import { createPlateEditor } from 'platejs/react';
+import { RemoteCursorOverlay } from '@/components/ui/remote-cursor-overlay';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ YjsPlugin.configure({
+ render: {
+ afterEditable: RemoteCursorOverlay,
+ },
+ options: {
+ // 配置本地用户光标外观
+ cursors: {
+ data: {
+ name: '用户名', // 替换为动态用户名
+ color: '#aabbcc', // 替换为动态用户颜色
+ },
+ },
+ // 配置提供者(所有提供者共享同一个 Y.Doc 和 Awareness 实例)
+ providers: [
+ // Hocuspocus 提供者示例
+ {
+ type: 'hocuspocus',
+ options: {
+ name: '我的文档ID', // 文档唯一标识
+ url: 'ws://localhost:8888', // Hocuspocus 服务地址
+ },
+ },
+ // WebRTC 提供者示例(可与 Hocuspocus 同时使用)
+ {
+ type: 'webrtc',
+ options: {
+ roomName: '我的文档ID', // 需与文档标识一致
+ signaling: ['ws://localhost:4444'], // 可选:信令服务器地址
+ },
+ },
+ ],
+ },
+ }),
+ ],
+ skipInitialization: true,
+});
+```
+
+- `render.afterEditable`:指定 [`RemoteCursorOverlay`](/docs/components/remote-cursor-overlay) 渲染远程用户光标。
+- `cursors.data`:配置本地用户光标显示名称和颜色。
+- `providers`:协作提供者数组(Hocuspocus、WebRTC 或自定义提供者)。
+
+### 添加编辑器容器
+
+`RemoteCursorOverlay` 需要定位容器包裹编辑器内容,使用 [`EditorContainer`](/docs/components/editor) 或 `platejs/react` 的 `PlateContainer`:
+
+```tsx
+import { Plate } from 'platejs/react';
+import { EditorContainer } from '@/components/ui/editor';
+
+return (
+
+
+
+
+
+);
+```
+
+### 初始化 Yjs 连接
+
+Yjs 连接和状态需手动初始化(通常在 `useEffect` 中处理):
+
+```tsx
+import React, { useEffect } from 'react';
+import { YjsPlugin } from '@platejs/yjs/react';
+import { useMounted } from '@/hooks/use-mounted'; // 或自定义挂载检查
+
+const MyEditorComponent = ({ documentId, initialValue }) => {
+ const editor = usePlateEditor(/** 前文配置 **/);
+ const mounted = useMounted();
+
+ useEffect(() => {
+ if (!mounted) return;
+
+ // 初始化 Yjs 连接并设置初始状态
+ editor.getApi(YjsPlugin).yjs.init({
+ id: documentId, // Yjs 文档唯一标识
+ value: initialValue, // Y.Doc 为空时的初始内容
+ });
+
+ // 清理:组件卸载时销毁连接
+ return () => {
+ editor.getApi(YjsPlugin).yjs.destroy();
+ };
+ }, [editor, mounted]);
+
+ return (
+
+
+
+
+
+ );
+};
+```
+
+
+ **初始值**:`init` 的 `value` 仅在后台/对等网络中文档完全空时生效。若文档已存在,将同步现有内容并忽略该值。
+
+ **生命周期管理**:必须调用 `editor.api.yjs.init()` 建立连接,并在组件卸载时调用 `editor.api.yjs.destroy()` 清理资源。
+
+
+### 监控连接状态(可选)
+
+访问提供者状态并添加事件监听:
+
+```tsx
+import React from 'react';
+import { YjsPlugin } from '@platejs/yjs/react';
+import { usePluginOption } from 'platejs/react';
+
+function EditorStatus() {
+ // 直接访问提供者状态(只读)
+ const providers = usePluginOption(YjsPlugin, '_providers');
+ const isConnected = usePluginOption(YjsPlugin, '_isConnected');
+
+ return (
+
+ {providers.map((provider) => (
+
+ {provider.type}: {provider.isConnected ? '已连接' : '未连接'} ({provider.isSynced ? '已同步' : '同步中'})
+
+ ))}
+
+ );
+}
+
+// 添加连接事件处理器:
+YjsPlugin.configure({
+ options: {
+ // ... 其他配置
+ onConnect: ({ type }) => console.debug(`${type} 提供者已连接!`),
+ onDisconnect: ({ type }) => console.debug(`${type} 提供者已断开`),
+ onSyncChange: ({ type, isSynced }) => console.debug(`${type} 提供者同步状态: ${isSynced}`),
+ onError: ({ type, error }) => console.error(`${type} 提供者错误:`, error),
+ },
+});
+```
+
+
+
+## 提供者类型
+
+### Hocuspocus 提供者
+
+基于 [Hocuspocus](https://tiptap.dev/hocuspocus) 的服务端方案,需运行 Hocuspocus 服务。
+
+```tsx
+type HocuspocusProviderConfig = {
+ type: 'hocuspocus',
+ options: {
+ name: string; // 文档标识
+ url: string; // WebSocket 服务地址
+ token?: string; // 认证令牌
+ }
+}
+```
+
+### WebRTC 提供者
+
+基于 [y-webrtc](https://github.com/yjs/y-webrtc) 的点对点方案。
+
+```tsx
+type WebRTCProviderConfig = {
+ type: 'webrtc',
+ options: {
+ roomName: string; // 协作房间名
+ signaling?: string[]; // 信令服务器地址
+ password?: string; // 房间密码
+ maxConns?: number; // 最大连接数
+ peerOpts?: object; // WebRTC 对等选项
+ }
+}
+```
+
+### 自定义提供者
+
+通过实现 `UnifiedProvider` 接口创建自定义提供者:
+
+```typescript
+interface UnifiedProvider {
+ awareness: Awareness;
+ document: Y.Doc;
+ type: string;
+ connect: () => void;
+ destroy: () => void;
+ disconnect: () => void;
+ isConnected: boolean;
+ isSynced: boolean;
+}
+```
+
+直接在提供者数组中使用:
+
+```tsx
+const customProvider = new MyCustomProvider({ doc: ydoc, awareness });
+
+YjsPlugin.configure({
+ options: {
+ providers: [customProvider],
+ },
+});
+```
+
+## 后端配置
+
+### Hocuspocus 服务
+
+搭建 [Hocuspocus 服务](https://tiptap.dev/hocuspocus/getting-started),确保提供者配置中的 `url` 和 `name` 与服务端匹配。
+
+### WebRTC 配置
+
+#### 信令服务器
+
+WebRTC 需信令服务器进行节点发现。测试可使用公共服务器,生产环境建议自建:
+
+```bash
+npm install y-webrtc
+PORT=4444 node ./node_modules/y-webrtc/bin/server.js
+```
+
+客户端配置自定义信令:
+
+```tsx
+{
+ type: 'webrtc',
+ options: {
+ roomName: '文档-1',
+ signaling: ['ws://您的信令服务器:4444'],
+ },
+}
+```
+
+#### TURN 服务器
+
+
+ WebRTC 连接可能因防火墙失败。生产环境建议使用 TURN 服务器或结合 Hocuspocus。
+
+
+配置 TURN 服务器提升连接可靠性:
+
+```tsx
+{
+ type: 'webrtc',
+ options: {
+ roomName: '文档-1',
+ signaling: ['ws://您的信令服务器:4444'],
+ peerOpts: {
+ config: {
+ iceServers: [
+ { urls: 'stun:stun.l.google.com:19302' },
+ {
+ urls: 'turn:您的TURN服务器:3478',
+ username: '用户名',
+ credential: '密码'
+ }
+ ]
+ }
+ }
+ }
+}
+```
+
+## 安全实践
+
+**认证与授权:**
+- 使用 Hocuspocus 的 `onAuthenticate` 钩子验证用户
+- 后端实现文档级访问控制
+- 通过 `token` 选项传递认证令牌
+
+**传输安全:**
+- 生产环境使用 `wss://` 加密通信
+- 配置 `turns://` 协议的 TURN 服务器
+
+**WebRTC 安全:**
+- 使用 `password` 选项控制房间访问
+- 配置安全信令服务器
+
+安全配置示例:
+
+```tsx
+YjsPlugin.configure({
+ options: {
+ providers: [
+ {
+ type: 'hocuspocus',
+ options: {
+ name: '安全文档ID',
+ url: 'wss://您的Hocuspocus服务',
+ token: '用户认证令牌',
+ },
+ },
+ {
+ type: 'webrtc',
+ options: {
+ roomName: '安全文档ID',
+ password: '高强度房间密码',
+ signaling: ['wss://您的安全信令服务'],
+ peerOpts: {
+ config: {
+ iceServers: [
+ {
+ urls: 'turns:您的TURN服务器:443?transport=tcp',
+ username: '用户',
+ credential: '密码'
+ }
+ ]
+ }
+ }
+ },
+ },
+ ],
+ },
+});
+```
+
+## 问题排查
+
+### 连接问题
+
+**检查地址与名称:**
+- 确认 Hocuspocus 的 `url` 和 WebRTC 的 `signaling` 地址正确
+- 确保所有协作者的 `name` 或 `roomName` 完全一致
+- 开发环境使用 `ws://`,生产环境使用 `wss://`
+
+**服务状态:**
+- 确认 Hocuspocus 和信令服务正常运行
+- 检查服务端日志错误
+- WebRTC 需测试 TURN 服务器连通性
+
+**网络问题:**
+- 防火墙可能阻止 WebSocket/WebRTC 流量
+- 配置 TCP 443 端口的 TURN 服务器提升穿透能力
+- 浏览器控制台查看提供者错误
+
+### 多文档处理
+
+**独立实例:**
+- 每个文档创建独立的 `Y.Doc` 实例
+- 使用唯一的文档标识作为 `name`/`roomName`
+- 为每个编辑器传递独立的 `ydoc` 和 `awareness` 实例
+
+### 同步问题
+
+**编辑器初始化:**
+- 创建编辑器时始终设置 `skipInitialization: true`
+- 使用 `editor.api.yjs.init({ value })` 设置初始内容
+- 确保所有提供者使用完全相同的文档标识
+
+**内容冲突:**
+- 避免手动操作共享的 `Y.Doc`
+- 所有文档操作通过编辑器由 Yjs 处理
+
+### 光标问题
+
+**悬浮层配置:**
+- 插件配置中包含 [`RemoteCursorOverlay`](/docs/components/remote-cursor-overlay)
+- 使用定位容器(`EditorContainer` 或 `PlateContainer`)
+- 确认本地用户的 `cursors.data`(名称、颜色)配置正确
+
+## 相关资源
+
+- [Yjs](https://github.com/yjs/yjs) - 协作 CRDT 框架
+- [slate-yjs](https://docs.slate-yjs.dev/) - Slate 的 Yjs 绑定
+- [Hocuspocus](https://tiptap.dev/hocuspocus) - Yjs 后端服务
+- [y-webrtc](https://github.com/yjs/y-webrtc) - WebRTC 提供者
+- [RemoteCursorOverlay](/docs/components/remote-cursor-overlay) - 远程光标组件
+- [EditorContainer](/docs/components/editor) - 编辑器容器组件
+
+## 插件
+
+### `YjsPlugin`
+
+通过 Yjs 实现实时协作,支持多提供者和远程光标。
+
+
+
+
+ 提供者配置数组或已实例化的提供者。插件会根据配置创建实例或直接使用现有实例。所有提供者共享同一个 Y.Doc 和 Awareness。每个配置对象需指定提供者 `type`(如 `'hocuspocus'`、`'webrtc'`)及其专属 `options`。自定义提供者实例需符合 `UnifiedProvider` 接口。
+
+
+ 远程光标配置。设为 `null` 显式禁用光标。未指定时,若配置了提供者则默认启用。参数传递给 `withTCursors`,详见 [WithCursorsOptions API](https://docs.slate-yjs.dev/api/slate-yjs-core/cursor-plugin#withcursors)。包含本地用户信息的 `data` 和默认 `true` 的 `autoSend`。
+
+
+ 可选共享 Y.Doc 实例。未提供时插件会内部创建。需与其他 Yjs 工具集成或管理多文档时建议自行提供。
+
+
+ 可选共享 Awareness 实例。未提供时插件会内部创建。
+
+
+ 任一提供者成功连接时的回调。
+
+
+ 任一提供者断开连接时的回调。
+
+
+ 任一提供者发生错误(如连接失败)时的回调。
+
+
+ 任一提供者同步状态 (`provider.isSynced`) 变化时的回调。
+
+
+
+ {/* 内部状态,通常使用 options 或事件处理器替代 */}
+
+ 内部状态:至少一个提供者已连接时为 true。
+
+
+ 内部状态:反映整体同步状态。
+
+
+ 内部状态:所有活跃提供者实例数组。
+
+
+
+
+## API
+
+### `api.yjs.init`
+
+初始化 Yjs 连接,将其绑定到编辑器,根据插件配置设置提供者,可能填充 Y.Doc 的初始内容,并连接提供者。**必须在编辑器挂载后调用。**
+
+
+
+
+ 初始化配置对象。
+
+
+
+
+
+ Yjs 文档的唯一标识符(如房间名、文档 ID)。未提供时使用 `editor.id`。确保协作者连接到同一文档状态的关键。
+
+
+ 编辑器的初始内容。**仅当共享状态(后端/对等端)中与 `id` 关联的 Y.Doc 完全为空时应用。**如果文档已存在,将同步其内容并忽略此值。可以是 Plate JSON(`Value`)、HTML 字符串或返回/解析为 `Value` 的函数。如果省略或为空,且 Y.Doc 为新文档,则使用默认空段落初始化。
+
+
+ 是否在初始化期间自动调用所有配置提供者的 `provider.connect()`。默认:`true`。如果要使用 `editor.api.yjs.connect()` 手动管理连接,请设置为 `false`。
+
+
+ 如果设置,在初始化和同步后自动聚焦编辑器并将光标放置在文档的 'start' 或 'end' 位置。
+
+
+ 初始化后设置选择的具体 Plate `Location`,覆盖 `autoSelect`。
+
+
+
+
+ 初始设置(包括潜在的异步 `value` 解析和 YjsEditor 绑定)完成时解析。注意提供者连接和同步是异步进行的。
+
+
+
+### `api.yjs.destroy`
+
+断开所有提供者连接,清理 Yjs 绑定(将编辑器从 Y.Doc 分离),并销毁 awareness 实例。**必须在编辑器组件卸载时调用**以防止内存泄漏和过时连接。
+
+### `api.yjs.connect`
+
+手动连接到提供者。在 `init` 期间使用 `autoConnect: false` 时很有用。
+
+
+
+
+ 如果提供,仅连接到指定类型的提供者。如果省略,连接到所有尚未连接的已配置提供者。
+
+
+
+
+### `api.yjs.disconnect`
+
+手动断开与提供者的连接。
+
+
+
+
+ 如果提供,仅断开与指定类型提供者的连接。如果省略,断开与所有当前已连接提供者的连接。
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(collaboration)/yjs.mdx b/apps/www/content/docs/(plugins)/(collaboration)/yjs.mdx
new file mode 100644
index 0000000000..fc202da7bc
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(collaboration)/yjs.mdx
@@ -0,0 +1,560 @@
+---
+title: Collaboration
+description: Real-time collaboration with Yjs
+toc: true
+---
+
+
+
+
+
+## Features
+
+- **Multi-Provider Support:** Enables real-time collaboration using [Yjs](https://github.com/yjs/yjs) and [slate-yjs](https://docs.slate-yjs.dev/). Supports multiple synchronization providers simultaneously (e.g., Hocuspocus + WebRTC) working on a shared `Y.Doc`.
+- **Built-in Providers:** Includes support for [Hocuspocus](https://tiptap.dev/hocuspocus) (server-based) and [WebRTC](https://github.com/yjs/y-webrtc) (peer-to-peer) providers out-of-the-box.
+- **Custom Providers:** Extensible architecture allows adding custom providers (e.g., for offline storage like IndexedDB) by implementing the `UnifiedProvider` interface.
+- **Awareness & Cursors:** Integrates Yjs Awareness protocol for sharing cursor locations and other ephemeral state between users. Includes [`RemoteCursorOverlay`](/docs/components/remote-cursor-overlay) for rendering remote cursors.
+- **Customizable Cursors:** Cursor appearance (name, color) can be customized via `cursors`.
+- **Manual Lifecycle:** Provides explicit `init` and `destroy` methods for managing the Yjs connection lifecycle.
+
+
+
+## Usage
+
+
+
+### Installation
+
+Install the core Yjs plugin and the specific provider packages you intend to use:
+
+```bash
+npm install @platejs/yjs
+```
+
+For Hocuspocus server-based collaboration:
+
+```bash
+npm install @hocuspocus/provider
+```
+
+For WebRTC peer-to-peer collaboration:
+
+```bash
+npm install y-webrtc
+```
+
+### Add Plugin
+
+```tsx
+import { YjsPlugin } from '@platejs/yjs/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ YjsPlugin,
+ ],
+ // Important: Skip Plate's default initialization when using Yjs
+ skipInitialization: true,
+});
+```
+
+
+ It's crucial to set `skipInitialization: true` when creating the editor. Yjs manages the initial document state, so Plate's default value initialization should be skipped to avoid conflicts.
+
+
+### Configure YjsPlugin
+
+Configure the plugin with providers and cursor settings:
+
+```tsx
+import { YjsPlugin } from '@platejs/yjs/react';
+import { createPlateEditor } from 'platejs/react';
+import { RemoteCursorOverlay } from '@/components/ui/remote-cursor-overlay';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ YjsPlugin.configure({
+ render: {
+ afterEditable: RemoteCursorOverlay,
+ },
+ options: {
+ // Configure local user cursor appearance
+ cursors: {
+ data: {
+ name: 'User Name', // Replace with dynamic user name
+ color: '#aabbcc', // Replace with dynamic user color
+ },
+ },
+ // Configure providers. All providers share the same Y.Doc and Awareness instance.
+ providers: [
+ // Example: Hocuspocus provider
+ {
+ type: 'hocuspocus',
+ options: {
+ name: 'my-document-id', // Unique identifier for the document
+ url: 'ws://localhost:8888', // Your Hocuspocus server URL
+ },
+ },
+ // Example: WebRTC provider (can be used alongside Hocuspocus)
+ {
+ type: 'webrtc',
+ options: {
+ roomName: 'my-document-id', // Must match the document identifier
+ signaling: ['ws://localhost:4444'], // Optional: Your signaling server URLs
+ },
+ },
+ ],
+ },
+ }),
+ ],
+ skipInitialization: true,
+});
+```
+
+- `render.afterEditable`: Assigns [`RemoteCursorOverlay`](/docs/components/remote-cursor-overlay) to render remote user cursors.
+- `cursors.data`: Configures the local user's cursor appearance with name and color.
+- `providers`: Array of collaboration providers to use (Hocuspocus, WebRTC, or custom providers).
+
+### Add Editor Container
+
+The `RemoteCursorOverlay` requires a positioned container around the editor content. Use [`EditorContainer`](/docs/components/editor) component or `PlateContainer` from `platejs/react`:
+
+```tsx
+import { Plate } from 'platejs/react';
+import { EditorContainer } from '@/components/ui/editor';
+
+return (
+
+
+
+
+
+);
+```
+
+### Initialize Yjs Connection
+
+Yjs connection and state initialization are handled manually, typically within a `useEffect` hook:
+
+```tsx
+import React, { useEffect } from 'react';
+import { YjsPlugin } from '@platejs/yjs/react';
+import { useMounted } from '@/hooks/use-mounted'; // Or your own mounted check
+
+const MyEditorComponent = ({ documentId, initialValue }) => {
+ const editor = usePlateEditor(/** editor config from previous steps **/);
+ const mounted = useMounted();
+
+ useEffect(() => {
+ // Ensure component is mounted and editor is ready
+ if (!mounted) return;
+
+ // Initialize Yjs connection, sync document, and set initial editor state
+ editor.getApi(YjsPlugin).yjs.init({
+ id: documentId, // Unique identifier for the Yjs document
+ value: initialValue, // Initial content if the Y.Doc is empty
+ });
+
+ // Clean up: Destroy connection when component unmounts
+ return () => {
+ editor.getApi(YjsPlugin).yjs.destroy();
+ };
+ }, [editor, mounted]);
+
+ return (
+
+
+
+
+
+ );
+};
+```
+
+
+ **Initial Value**: The `value` passed to `init` is only used to populate the Y.Doc if it's completely empty on the backend/peer network. If the document already exists, its content will be synced, and this initial value will be ignored.
+
+ **Lifecycle Management**: You **must** call `editor.api.yjs.init()` to establish the connection and `editor.api.yjs.destroy()` on component unmount to clean up resources.
+
+
+### Monitor Connection Status (Optional)
+
+Access provider states and add event handlers for connection monitoring:
+
+```tsx
+import React from 'react';
+import { YjsPlugin } from '@platejs/yjs/react';
+import { usePluginOption } from 'platejs/react';
+
+function EditorStatus() {
+ // Access provider states directly (read-only)
+ const providers = usePluginOption(YjsPlugin, '_providers');
+ const isConnected = usePluginOption(YjsPlugin, '_isConnected');
+
+ return (
+
+ {providers.map((provider) => (
+
+ {provider.type}: {provider.isConnected ? 'Connected' : 'Disconnected'} ({provider.isSynced ? 'Synced' : 'Syncing'})
+
+ ))}
+
+ );
+}
+
+// Add event handlers for connection events:
+YjsPlugin.configure({
+ options: {
+ // ... other options
+ onConnect: ({ type }) => console.debug(`Provider ${type} connected!`),
+ onDisconnect: ({ type }) => console.debug(`Provider ${type} disconnected.`),
+ onSyncChange: ({ type, isSynced }) => console.debug(`Provider ${type} sync status: ${isSynced}`),
+ onError: ({ type, error }) => console.error(`Error in provider ${type}:`, error),
+ },
+});
+```
+
+
+
+## Provider Types
+
+### Hocuspocus Provider
+
+Server-based collaboration using [Hocuspocus](https://tiptap.dev/hocuspocus). Requires a running Hocuspocus server.
+
+```tsx
+type HocuspocusProviderConfig = {
+ type: 'hocuspocus',
+ options: {
+ name: string; // Document identifier
+ url: string; // WebSocket server URL
+ token?: string; // Authentication token
+ }
+}
+```
+
+### WebRTC Provider
+
+Peer-to-peer collaboration using [y-webrtc](https://github.com/yjs/y-webrtc).
+
+```tsx
+type WebRTCProviderConfig = {
+ type: 'webrtc',
+ options: {
+ roomName: string; // Room name for collaboration
+ signaling?: string[]; // Signaling server URLs
+ password?: string; // Room password
+ maxConns?: number; // Max connections
+ peerOpts?: object; // WebRTC peer options
+ }
+}
+```
+
+### Custom Provider
+
+Create custom providers by implementing the `UnifiedProvider` interface:
+
+```typescript
+interface UnifiedProvider {
+ awareness: Awareness;
+ document: Y.Doc;
+ type: string;
+ connect: () => void;
+ destroy: () => void;
+ disconnect: () => void;
+ isConnected: boolean;
+ isSynced: boolean;
+}
+```
+
+Use custom providers directly in the providers array:
+
+```tsx
+const customProvider = new MyCustomProvider({ doc: ydoc, awareness });
+
+YjsPlugin.configure({
+ options: {
+ providers: [customProvider],
+ },
+});
+```
+
+## Backend Setup
+
+### Hocuspocus Server
+
+Set up a [Hocuspocus server](https://tiptap.dev/hocuspocus/getting-started) for server-based collaboration. Ensure the `url` and `name` in your provider options match your server configuration.
+
+### WebRTC Setup
+
+#### Signaling Server
+
+WebRTC requires signaling servers for peer discovery. Public servers work for testing but use your own for production:
+
+```bash
+npm install y-webrtc
+PORT=4444 node ./node_modules/y-webrtc/bin/server.js
+```
+
+Configure your client to use custom signaling:
+
+```tsx
+{
+ type: 'webrtc',
+ options: {
+ roomName: 'document-1',
+ signaling: ['ws://your-signaling-server.com:4444'],
+ },
+}
+```
+
+#### TURN Servers
+
+
+ WebRTC connections can fail due to firewalls. Use TURN servers or combine with Hocuspocus for production reliability.
+
+
+Configure TURN servers for reliable connections:
+
+```tsx
+{
+ type: 'webrtc',
+ options: {
+ roomName: 'document-1',
+ signaling: ['ws://your-signaling-server.com:4444'],
+ peerOpts: {
+ config: {
+ iceServers: [
+ { urls: 'stun:stun.l.google.com:19302' },
+ {
+ urls: 'turn:your-turn-server.com:3478',
+ username: 'username',
+ credential: 'password'
+ }
+ ]
+ }
+ }
+ }
+}
+```
+
+## Security
+
+**Authentication & Authorization:**
+- Use Hocuspocus's `onAuthenticate` hook to validate users
+- Implement document-level access control on your backend
+- Pass authentication tokens via the `token` option
+
+**Transport Security:**
+- Use `wss://` URLs in production for encrypted communication
+- Configure secure TURN servers with the `turns://` protocol
+
+**WebRTC Security:**
+- Use the `password` option for basic room access control
+- Configure secure signaling servers
+
+Example secure configuration:
+
+```tsx
+YjsPlugin.configure({
+ options: {
+ providers: [
+ {
+ type: 'hocuspocus',
+ options: {
+ name: 'secure-document-id',
+ url: 'wss://your-hocuspocus-server.com',
+ token: 'user-auth-token',
+ },
+ },
+ {
+ type: 'webrtc',
+ options: {
+ roomName: 'secure-document-id',
+ password: 'strong-room-password',
+ signaling: ['wss://your-secure-signaling.com'],
+ peerOpts: {
+ config: {
+ iceServers: [
+ {
+ urls: 'turns:your-turn-server.com:443?transport=tcp',
+ username: 'user',
+ credential: 'pass'
+ }
+ ]
+ }
+ }
+ },
+ },
+ ],
+ },
+});
+```
+
+## Troubleshooting
+
+### Connection Issues
+
+**Check URLs and Names:**
+- Verify `url` (Hocuspocus) and `signaling` URLs (WebRTC) are correct
+- Ensure `name` or `roomName` matches exactly across all collaborators
+- Use `ws://` for local development, `wss://` for production
+
+**Server Status:**
+- Verify Hocuspocus and signaling servers are running
+- Check server logs for errors
+- Test TURN server connectivity if using WebRTC
+
+**Network Issues:**
+- Firewalls may block WebSocket or WebRTC traffic
+- Use TURN servers configured for TCP (port 443) for better traversal
+- Check browser console for provider errors
+
+### Multiple Documents
+
+**Separate Instances:**
+- Create separate `Y.Doc` instances for each document
+- Use unique document identifiers for `name`/`roomName`
+- Pass unique `ydoc` and `awareness` instances to each editor
+
+### Sync Issues
+
+**Editor Initialization:**
+- Always set `skipInitialization: true` when creating the editor
+- Use `editor.api.yjs.init({ value })` for initial content
+- Ensure all providers use the exact same document identifier
+
+**Content Conflicts:**
+- Avoid manually manipulating the shared `Y.Doc`
+- Let Yjs handle all document operations through the editor
+
+### Cursor Issues
+
+**Overlay Setup:**
+- Include [`RemoteCursorOverlay`](/docs/components/remote-cursor-overlay) in plugin render config
+- Use positioned container (`EditorContainer` or `PlateContainer`)
+- Verify `cursors.data` (name, color) is set correctly for local user
+
+## Related
+
+- [Yjs](https://github.com/yjs/yjs) - CRDT framework for collaboration
+- [slate-yjs](https://docs.slate-yjs.dev/) - Yjs bindings for Slate
+- [Hocuspocus](https://tiptap.dev/hocuspocus) - Backend server for Yjs
+- [y-webrtc](https://github.com/yjs/y-webrtc) - WebRTC provider
+- [RemoteCursorOverlay](/docs/components/remote-cursor-overlay) - Remote cursor component
+- [EditorContainer](/docs/components/editor) - Editor container component
+
+## Plugins
+
+### `YjsPlugin`
+
+Enables real-time collaboration using Yjs with support for multiple providers and remote cursors.
+
+
+
+
+ Array of provider configurations or pre-instantiated provider instances. The plugin will create instances from configurations and use existing instances directly. All providers will share the same Y.Doc and Awareness. Each configuration object specifies a provider `type` (e.g., `'hocuspocus'`,
+ `'webrtc'`) and its specific `options`. Custom provider instances must conform to the
+ `UnifiedProvider` interface.
+
+
+ Configuration for remote cursors. Set to `null` to explicitly disable cursors. If omitted, cursors are enabled by default if providers are specified. Passed to `withTCursors`. See [WithCursorsOptions API](https://docs.slate-yjs.dev/api/slate-yjs-core/cursor-plugin#withcursors). Includes `data` for local user info and `autoSend` (default `true`).
+
+
+ Optional shared Y.Doc instance. If not provided, a new one will be created internally by the plugin. Provide your own if integrating with other Yjs tools or managing multiple documents.
+
+
+ Optional shared Awareness instance. If not provided, a new one will be created.
+
+
+ Callback fired when any provider successfully connects.
+
+
+ Callback fired when any provider disconnects.
+
+
+ Callback fired when any provider encounters an error (e.g., connection failure).
+
+
+ Callback fired when the sync status (`provider.isSynced`) of any individual provider changes.
+
+
+
+ {/* Attributes are internal state, generally use options or event handlers instead */}
+
+ Internal state: Whether at least one provider is currently connected.
+
+
+ Internal state: Reflects overall sync status.
+
+
+ Internal state: Array of all active, instantiated provider instances.
+
+
+
+
+## API
+
+### `api.yjs.init`
+
+Initializes the Yjs connection, binds it to the editor, sets up providers based on plugin configuration, potentially populates the Y.Doc with initial content, and connects providers. **Must be called after the editor is mounted.**
+
+
+
+
+ Configuration object for initialization.
+
+
+
+
+
+ A unique identifier for the Yjs document (e.g., room name, document ID). If not provided, `editor.id` is used. Essential for ensuring collaborators connect to the same document state.
+
+
+ The initial content for the editor. **This is only applied if the Y.Doc associated with the `id` is completely empty in the shared state (backend/peers).** If the document already exists, its content will be synced, ignoring this value. Can be Plate JSON (`Value`), an HTML string, or a function returning/resolving to `Value`. If omitted or empty, a default empty paragraph is used for initialization if the Y.Doc is new.
+
+
+ Whether to automatically call `provider.connect()` for all configured providers during initialization. Default: `true`. Set to `false` if you want to manage connections manually using `editor.api.yjs.connect()`.
+
+
+ If set, automatically focuses the editor and places the cursor at the 'start' or 'end' of the document after initialization and sync.
+
+
+ Specific Plate `Location` to set the selection to after initialization, overriding `autoSelect`.
+
+
+
+
+ Resolves when the initial setup (including potential async `value` resolution and YjsEditor binding) is complete. Note that provider connection and synchronization happen asynchronously.
+
+
+
+### `api.yjs.destroy`
+
+Disconnects all providers, cleans up Yjs bindings (detaches editor from Y.Doc), and destroys the awareness instance. **Must be called when the editor component unmounts** to prevent memory leaks and stale connections.
+
+### `api.yjs.connect`
+
+Manually connects to providers. Useful if `autoConnect: false` was used during `init`.
+
+
+
+
+ If provided, only connects to providers of the specified type(s). If omitted, connects to all configured providers that are not already connected.
+
+
+
+
+### `api.yjs.disconnect`
+
+Manually disconnects from providers.
+
+
+
+
+ If provided, only disconnects from providers of the specified type(s). If omitted, disconnects from all currently connected providers.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/basic-blocks.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/basic-blocks.cn.mdx
new file mode 100644
index 0000000000..4fd08f2e51
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/basic-blocks.cn.mdx
@@ -0,0 +1,65 @@
+---
+title: 基础元素
+description: 用于构建内容的常用块级元素。
+---
+
+
+
+
+创建不同层级的标题来组织内容结构。
+
+
+
+通过样式化引用来强调重要信息。
+
+
+
+插入水平线来分隔内容。
+
+
+
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+`BasicBlocksKit` 捆绑了段落、标题(H1、H2、H3)、引用块和水平分割线的插件,以及它们对应的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`ParagraphElement`](/docs/components/paragraph-node): 渲染段落元素。
+- [`H1Element`](/docs/components/heading-node): 渲染 H1 元素。
+- [`H2Element`](/docs/components/heading-node): 渲染 H2 元素。
+- [`H3Element`](/docs/components/heading-node): 渲染 H3 元素。
+- [`BlockquoteElement`](/docs/components/blockquote-node): 渲染引用块元素。
+- [`HrElement`](/docs/components/hr-node): 渲染水平分割线元素。
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+
+
+## 手动安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+如需单独配置插件,请查看上方链接对应的具体插件文档页面。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/basic-blocks.mdx b/apps/www/content/docs/(plugins)/(elements)/basic-blocks.mdx
new file mode 100644
index 0000000000..f41d70638a
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/basic-blocks.mdx
@@ -0,0 +1,65 @@
+---
+title: Basic Elements
+description: Commonly used block elements for structuring content.
+---
+
+
+
+
+Create headings of various levels to structure content.
+
+
+
+Emphasize important information with styled quotes.
+
+
+
+Insert horizontal lines to separate content.
+
+
+
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The `BasicBlocksKit` bundles plugins for Paragraphs, Headings (H1, H2, H3), Blockquotes, and Horizontal Rules, along with their respective UI components from [Plate UI](/docs/installation/plate-ui).
+
+
+
+- [`ParagraphElement`](/docs/components/paragraph-node): Renders paragraph elements.
+- [`H1Element`](/docs/components/heading-node): Renders H1 elements.
+- [`H2Element`](/docs/components/heading-node): Renders H2 elements.
+- [`H3Element`](/docs/components/heading-node): Renders H3 elements.
+- [`BlockquoteElement`](/docs/components/blockquote-node): Renders blockquote elements.
+- [`HrElement`](/docs/components/hr-node): Renders horizontal rule elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+For individual plugin setup and configuration, see the specific plugin documentation pages linked above.
diff --git a/apps/www/content/docs/(plugins)/(elements)/blockquote.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/blockquote.cn.mdx
new file mode 100644
index 0000000000..42ed06dad9
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/blockquote.cn.mdx
@@ -0,0 +1,134 @@
+---
+title: 引用块
+docs:
+ - route: /docs/components/blockquote-node
+ title: 引用块元素
+---
+
+
+
+
+
+## 功能特点
+
+- 创建引用块以强调重要信息或突出显示来自外部来源的引用。
+- 默认渲染为 `` HTML 元素。
+
+
+
+## Kit 使用
+
+
+
+### 安装
+
+添加引用块插件最快的方法是使用 `BasicBlocksKit`,它包含预配置的 `BlockquotePlugin` 以及其他基本块元素及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`BlockquoteElement`](/docs/components/blockquote-node): 渲染引用块元素。
+
+### 添加 Kit
+
+将 kit 添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+在创建编辑器时,将 `BlockquotePlugin` 包含在你的 Plate 插件数组中。
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ BlockquotePlugin,
+ ],
+});
+```
+
+### 配置插件
+
+你可以使用特定渲染组件或自定义键盘快捷键等选项来配置 `BlockquotePlugin`。
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { BlockquoteElement } from '@/components/ui/blockquote-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ BlockquotePlugin.configure({
+ node: { component: BlockquoteElement },
+ shortcuts: { toggle: 'mod+shift+.' },
+ }),
+ ],
+});
+```
+
+- `node.component`: 分配 [`BlockquoteElement`](/docs/components/blockquote-node) 来渲染引用块元素。
+- `shortcuts.toggle`: 定义用于切换引用块格式的键盘[快捷键](/docs/plugin-shortcuts)。
+
+### 转换为工具栏按钮
+
+你可以将引用块添加到[转换为工具栏按钮](/docs/toolbar#turn-into-toolbar-button)以切换引用块:
+
+```tsx
+{
+ icon: ,
+ label: '引用',
+ value: KEYS.blockquote,
+}
+```
+
+### 插入工具栏按钮
+
+你可以将引用块添加到[插入工具栏按钮](/docs/toolbar#insert-toolbar-button)以插入引用块:
+
+```tsx
+{
+ icon: ,
+ label: '引用',
+ value: KEYS.blockquote,
+}
+```
+
+
+
+## 插件
+
+### `BlockquotePlugin`
+
+用于引用块元素的插件。默认渲染为 `` HTML 元素。
+
+## 转换
+
+### `tf.blockquote.toggle`
+
+在当前块和段落之间切换引用块。如果该块已经是引用块,则恢复为段落。如果是段落或其他块类型,则转换为引用块。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/blockquote.mdx b/apps/www/content/docs/(plugins)/(elements)/blockquote.mdx
new file mode 100644
index 0000000000..0877fb5ea1
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/blockquote.mdx
@@ -0,0 +1,134 @@
+---
+title: Blockquote
+docs:
+ - route: /docs/components/blockquote-node
+ title: Blockquote Element
+---
+
+
+
+
+
+## Features
+
+- Create blockquotes to emphasize important information or highlight quotes from external sources.
+- Renders as `` HTML element by default.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the blockquote plugin is with the `BasicBlocksKit`, which includes pre-configured `BlockquotePlugin` along with other basic block elements and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`BlockquoteElement`](/docs/components/blockquote-node): Renders blockquote elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `BlockquotePlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ BlockquotePlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `BlockquotePlugin` with options such as a specific rendering component or custom keyboard shortcuts.
+
+```tsx
+import { BlockquotePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { BlockquoteElement } from '@/components/ui/blockquote-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ BlockquotePlugin.configure({
+ node: { component: BlockquoteElement },
+ shortcuts: { toggle: 'mod+shift+.' },
+ }),
+ ],
+});
+```
+
+- `node.component`: Assigns [`BlockquoteElement`](/docs/components/blockquote-node) to render blockquote elements.
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle blockquote formatting.
+
+### Turn Into Toolbar Button
+
+You can add blockquote to the [Turn Into Toolbar Button](/docs/toolbar#turn-into-toolbar-button) to toggle blockquotes:
+
+```tsx
+{
+ icon: ,
+ label: 'Quote',
+ value: KEYS.blockquote,
+}
+```
+
+### Insert Toolbar Button
+
+You can add blockquote to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert blockquotes:
+
+```tsx
+{
+ icon: ,
+ label: 'Quote',
+ value: KEYS.blockquote,
+}
+```
+
+
+
+## Plugins
+
+### `BlockquotePlugin`
+
+Plugin for blockquote elements. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.blockquote.toggle`
+
+Toggles the current block between blockquote and paragraph. If the block is already a blockquote, it reverts to a paragraph. If it's a paragraph or another block type, it converts to a blockquote.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/callout.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/callout.cn.mdx
new file mode 100644
index 0000000000..1b50589bb5
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/callout.cn.mdx
@@ -0,0 +1,191 @@
+---
+title: 提示框
+docs:
+ - route: /docs/components/callout
+ title: 提示框元素
+ - route: https://pro.platejs.org/docs/components/callout-node
+ title: 提示框元素
+---
+
+
+
+
+
+## 功能特性
+
+- 可自定义的提示框区块,用于突出显示重要信息
+- 支持不同类型的提示框变体(如信息、警告、错误)
+- 可为提示框设置自定义图标或表情符号
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快添加提示框插件的方式是使用 `CalloutKit`,它包含预配置的 `CalloutPlugin` 和 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`CalloutElement`](/docs/components/callout-node): 渲染提示框元素
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CalloutKit } from '@/components/editor/plugins/callout-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...CalloutKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/callout
+```
+
+### 添加插件
+
+在创建编辑器时,将 `CalloutPlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { CalloutPlugin } from '@platejs/callout/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ CalloutPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+你可以通过自定义组件来配置 `CalloutPlugin` 以渲染提示框元素。
+
+```tsx
+import { CalloutPlugin } from '@platejs/callout/react';
+import { createPlateEditor } from 'platejs/react';
+import { CalloutElement } from '@/components/ui/callout-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ CalloutPlugin.withComponent(CalloutElement),
+ ],
+});
+```
+
+- `withComponent`: 指定 [`CalloutElement`](/docs/components/callout-node) 来渲染提示框元素
+
+
+
+## Plate Plus
+
+
+
+## 插件
+
+### `CalloutPlugin`
+
+提示框元素插件。
+
+## 转换器
+
+### `tf.insert.callout`
+
+向编辑器中插入提示框元素。
+
+
+
+
+ 要插入的提示框变体类型
+
+
+ 来自 `InsertNodesOptions` 的其他选项
+
+
+
+
+## 钩子
+
+### `useCalloutEmojiPicker`
+
+管理提示框的表情符号选择器功能。
+
+
+
+
+ 表情符号选择器是否打开
+
+
+ 设置表情符号选择器打开状态的函数
+
+
+
+
+
+ 表情符号工具栏下拉框的属性
+
+
+ 表情符号选择器是否打开
+
+
+ 设置表情符号选择器打开状态的函数,会考虑只读模式
+
+
+
+
+ 表情符号选择器组件的属性
+
+
+ 表情符号选择器是否打开
+
+
+ 设置表情符号选择器打开状态的函数
+
+
+ 当选择表情符号时调用的函数。它会更新提示框的图标并关闭选择器
+
+
+
+
+
+
+## 类型
+
+### `TCalloutElement`
+
+```typescript
+interface TCalloutElement extends TElement {
+ variant?: string;
+ icon?: string;
+}
+```
+
+
+
+
+ 提示框的变体类型
+
+
+ 要显示的图标或表情符号
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/callout.mdx b/apps/www/content/docs/(plugins)/(elements)/callout.mdx
new file mode 100644
index 0000000000..baf8d723c8
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/callout.mdx
@@ -0,0 +1,192 @@
+---
+title: Callout
+docs:
+ - route: https://pro.platejs.org/docs/components/callout-node
+ title: Plus
+ - route: /docs/components/callout
+ title: Callout Element
+---
+
+
+
+
+
+## Features
+
+- Customizable callout blocks for highlighting important information
+- Support for different callout variants (e.g., info, warning, error)
+- Ability to set custom icons or emojis for callouts
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the callout plugin is with the `CalloutKit`, which includes pre-configured `CalloutPlugin` with the [Plate UI](/docs/installation/plate-ui) component.
+
+
+
+- [`CalloutElement`](/docs/components/callout-node): Renders callout elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CalloutKit } from '@/components/editor/plugins/callout-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...CalloutKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/callout
+```
+
+### Add Plugin
+
+Include `CalloutPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { CalloutPlugin } from '@platejs/callout/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CalloutPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `CalloutPlugin` with a custom component to render callout elements.
+
+```tsx
+import { CalloutPlugin } from '@platejs/callout/react';
+import { createPlateEditor } from 'platejs/react';
+import { CalloutElement } from '@/components/ui/callout-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CalloutPlugin.withComponent(CalloutElement),
+ ],
+});
+```
+
+- `withComponent`: Assigns [`CalloutElement`](/docs/components/callout-node) to render callout elements.
+
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `CalloutPlugin`
+
+Callout element plugin.
+
+## Transforms
+
+### `tf.insert.callout`
+
+Insert a callout element into the editor.
+
+
+
+
+ The variant of the callout to insert.
+
+
+ Other options from `InsertNodesOptions`.
+
+
+
+
+## Hooks
+
+### `useCalloutEmojiPicker`
+
+Manage the emoji picker functionality for callouts.
+
+
+
+
+ Whether the emoji picker is open.
+
+
+ Function to set the open state of the emoji picker.
+
+
+
+
+
+ Props for the emoji toolbar dropdown.
+
+
+ Whether the emoji picker is open.
+
+
+ Function to set the open state of the emoji picker, respecting read-only mode.
+
+
+
+
+ Props for the emoji picker component.
+
+
+ Whether the emoji picker is open.
+
+
+ Function to set the open state of the emoji picker.
+
+
+ Function called when an emoji is selected. It updates the callout's icon and closes the picker.
+
+
+
+
+
+
+## Types
+
+### `TCalloutElement`
+
+```typescript
+interface TCalloutElement extends TElement {
+ variant?: string;
+ icon?: string;
+}
+```
+
+
+
+
+ The variant of the callout.
+
+
+ The icon or emoji to display.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/code-block.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/code-block.cn.mdx
new file mode 100644
index 0000000000..3019acc8f1
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/code-block.cn.mdx
@@ -0,0 +1,274 @@
+---
+title: 代码块
+docs:
+ - route: /docs/components/code-block-node
+ title: 代码块元素
+---
+
+
+
+
+
+## 功能特性
+
+- 代码块的语法高亮显示
+- 支持多种编程语言
+- 可自定义语言选择
+- 正确处理缩进
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷添加代码块功能的方式是使用 `CodeBlockKit`,它包含预配置的 `CodeBlockPlugin`、`CodeLinePlugin` 和 `CodeSyntaxPlugin`,提供语法高亮和 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`CodeBlockElement`](/docs/components/code-block-node): 渲染代码块容器
+- [`CodeLineElement`](/docs/components/code-block-node): 渲染单行代码
+- [`CodeSyntaxLeaf`](/docs/components/code-block-node): 渲染语法高亮文本
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CodeBlockKit } from '@/components/editor/plugins/code-block-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...CodeBlockKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/code-block lowlight
+```
+
+### 添加插件
+
+在创建编辑器时,将代码块插件包含到 Plate 插件数组中。
+
+```tsx
+import { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from '@platejs/code-block/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ CodeBlockPlugin,
+ CodeLinePlugin,
+ CodeSyntaxPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+配置带有语法高亮和自定义组件的插件。
+
+**包含所有语言的基础设置:**
+
+```tsx
+import { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from '@platejs/code-block/react';
+import { all, createLowlight } from 'lowlight';
+import { createPlateEditor } from 'platejs/react';
+import { CodeBlockElement, CodeLineElement, CodeSyntaxLeaf } from '@/components/ui/code-block-node';
+
+// 创建包含所有语言的 lowlight 实例
+const lowlight = createLowlight(all);
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ CodeBlockPlugin.configure({
+ node: { component: CodeBlockElement },
+ options: { lowlight },
+ shortcuts: { toggle: { keys: 'mod+alt+8' } },
+ }),
+ CodeLinePlugin.withComponent(CodeLineElement),
+ CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf),
+ ],
+});
+```
+
+**自定义语言设置(优化包体积):**
+
+为了优化包体积,你可以只注册特定语言:
+
+```tsx
+import { createLowlight } from 'lowlight';
+import css from 'highlight.js/lib/languages/css';
+import js from 'highlight.js/lib/languages/javascript';
+import ts from 'highlight.js/lib/languages/typescript';
+import html from 'highlight.js/lib/languages/xml';
+
+// 创建 lowlight 实例
+const lowlight = createLowlight();
+
+// 只注册需要的语言
+lowlight.register('html', html);
+lowlight.register('css', css);
+lowlight.register('js', js);
+lowlight.register('ts', ts);
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ CodeBlockPlugin.configure({
+ node: { component: CodeBlockElement },
+ options: {
+ lowlight,
+ defaultLanguage: 'js', // 设置默认语言(可选)
+ },
+ shortcuts: { toggle: { keys: 'mod+alt+8' } },
+ }),
+ CodeLinePlugin.withComponent(CodeLineElement),
+ CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf),
+ ],
+});
+```
+
+- `node.component`: 指定 [`CodeBlockElement`](/docs/components/code-block-node) 来渲染代码块容器
+- `options.lowlight`: 用于语法高亮的 lowlight 实例
+- `options.defaultLanguage`: 未指定语言时使用的默认语言
+- `shortcuts.toggle`: 定义切换代码块的键盘[快捷键](/docs/plugin-shortcuts)
+- `withComponent`: 为代码行和语法高亮指定组件
+
+### 转换为工具栏按钮
+
+你可以将此项目添加到[转换为工具栏按钮](/docs/toolbar#turn-into-toolbar-button)中,将块转换为代码块:
+
+```tsx
+{
+ icon: ,
+ label: '代码',
+ value: KEYS.codeBlock,
+}
+```
+
+### 插入工具栏按钮
+
+你可以将此项目添加到[插入工具栏按钮](/docs/toolbar#insert-toolbar-button)中,插入代码块元素:
+
+```tsx
+{
+ icon: ,
+ label: '代码',
+ value: KEYS.codeBlock,
+}
+```
+
+
+
+## 插件
+
+### `CodeBlockPlugin`
+
+
+
+
+ 未指定语言时使用的默认语言。设为 null 可默认禁用语法高亮。
+
+
+ 用于高亮的 lowlight 实例。如果未提供,将禁用语法高亮。
+
+
+
+
+## API
+
+### `isCodeBlockEmpty`
+
+
+
+ 判断选区是否在空代码块中。
+
+
+
+### `isSelectionAtCodeBlockStart`
+
+
+
+ 判断选区是否位于代码块第一行开头。
+
+
+
+### `indentCodeLine`
+
+如果选区已展开或光标左侧无非空白字符,则缩进代码行。默认缩进为 2 个空格。
+
+
+
+
+ 要缩进的代码行。
+
+
+ 代码行的缩进大小。
+ - **默认值:** `2`
+
+
+
+
+### `insertCodeBlock`
+
+通过将节点设为代码行并用代码块包裹来插入代码块。如果光标不在块开头,则在代码块前插入换行。
+
+
+
+
+ 插入节点的选项。
+
+
+
+
+### `insertCodeLine`
+
+插入以指定缩进深度开头的代码行。
+
+
+
+
+ 代码行的缩进深度。
+ - **默认值:** `0`
+
+
+
+
+### `outdentCodeLine`
+
+减少代码行的缩进,如果存在则移除两个空白字符。
+
+
+
+
+ 要减少缩进的代码行。
+
+
+ 包含要减少缩进代码行的代码块。
+
+
+
+
+### `toggleCodeBlock`
+
+切换编辑器中的代码块。
+
+### `unwrapCodeBlock`
+
+解除编辑器中的代码块包裹。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/code-block.mdx b/apps/www/content/docs/(plugins)/(elements)/code-block.mdx
new file mode 100644
index 0000000000..13e5d088ee
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/code-block.mdx
@@ -0,0 +1,276 @@
+---
+title: Code Block
+docs:
+ - route: https://pro.platejs.org/docs/components/code-block-node
+ title: Plus
+ - route: /docs/components/code-block-node
+ title: Code Block Element
+---
+
+
+
+
+
+## Features
+
+- Syntax highlighting for code blocks
+- Support for multiple programming languages
+- Customizable language selection
+- Proper indentation handling
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add code block functionality is with the `CodeBlockKit`, which includes pre-configured `CodeBlockPlugin`, `CodeLinePlugin`, and `CodeSyntaxPlugin` with syntax highlighting and [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`CodeBlockElement`](/docs/components/code-block-node): Renders code block containers.
+- [`CodeLineElement`](/docs/components/code-block-node): Renders individual code lines.
+- [`CodeSyntaxLeaf`](/docs/components/code-block-node): Renders syntax highlighted text.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CodeBlockKit } from '@/components/editor/plugins/code-block-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...CodeBlockKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/code-block lowlight
+```
+
+### Add Plugins
+
+Include the code block plugins in your Plate plugins array when creating the editor.
+
+```tsx
+import { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from '@platejs/code-block/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CodeBlockPlugin,
+ CodeLinePlugin,
+ CodeSyntaxPlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+Configure the plugins with syntax highlighting and custom components.
+
+**Basic Setup with All Languages:**
+
+```tsx
+import { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from '@platejs/code-block/react';
+import { all, createLowlight } from 'lowlight';
+import { createPlateEditor } from 'platejs/react';
+import { CodeBlockElement, CodeLineElement, CodeSyntaxLeaf } from '@/components/ui/code-block-node';
+
+// Create a lowlight instance with all languages
+const lowlight = createLowlight(all);
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CodeBlockPlugin.configure({
+ node: { component: CodeBlockElement },
+ options: { lowlight },
+ shortcuts: { toggle: { keys: 'mod+alt+8' } },
+ }),
+ CodeLinePlugin.withComponent(CodeLineElement),
+ CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf),
+ ],
+});
+```
+
+**Custom Language Setup (Optimized Bundle):**
+
+For optimized bundle size, you can register only specific languages:
+
+```tsx
+import { createLowlight } from 'lowlight';
+import css from 'highlight.js/lib/languages/css';
+import js from 'highlight.js/lib/languages/javascript';
+import ts from 'highlight.js/lib/languages/typescript';
+import html from 'highlight.js/lib/languages/xml';
+
+// Create a lowlight instance
+const lowlight = createLowlight();
+
+// Register only the languages you need
+lowlight.register('html', html);
+lowlight.register('css', css);
+lowlight.register('js', js);
+lowlight.register('ts', ts);
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CodeBlockPlugin.configure({
+ node: { component: CodeBlockElement },
+ options: {
+ lowlight,
+ defaultLanguage: 'js', // Set default language (optional)
+ },
+ shortcuts: { toggle: { keys: 'mod+alt+8' } },
+ }),
+ CodeLinePlugin.withComponent(CodeLineElement),
+ CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf),
+ ],
+});
+```
+
+- `node.component`: Assigns [`CodeBlockElement`](/docs/components/code-block-node) to render code block containers.
+- `options.lowlight`: Lowlight instance for syntax highlighting.
+- `options.defaultLanguage`: Default language when no language is specified.
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle code blocks.
+- `withComponent`: Assigns components for code lines and syntax highlighting.
+
+### Turn Into Toolbar Button
+
+You can add this item to the [Turn Into Toolbar Button](/docs/toolbar#turn-into-toolbar-button) to convert blocks into code blocks:
+
+```tsx
+{
+ icon: ,
+ label: 'Code',
+ value: KEYS.codeBlock,
+}
+```
+
+### Insert Toolbar Button
+
+You can add this item to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert code block elements:
+
+```tsx
+{
+ icon: ,
+ label: 'Code',
+ value: KEYS.codeBlock,
+}
+```
+
+
+
+## Plugins
+
+### `CodeBlockPlugin`
+
+
+
+
+ Default language to use when no language is specified. Set to null to disable syntax highlighting by default.
+
+
+ Lowlight instance to use for highlighting. If not provided, syntax highlighting will be disabled.
+
+
+
+
+## API
+
+### `isCodeBlockEmpty`
+
+
+
+ Whether the selection is in an empty code block.
+
+
+
+### `isSelectionAtCodeBlockStart`
+
+
+
+ Whether the selection is at the start of the first code line in a code block.
+
+
+
+### `indentCodeLine`
+
+Indents the code line if the selection is expanded or there are no non-whitespace characters at left of the cursor. The indentation is 2 spaces by default.
+
+
+
+
+ The code line to be indented.
+
+
+ The size of indentation for the code line.
+ - **Default:** `2`
+
+
+
+
+### `insertCodeBlock`
+
+Inserts a code block by setting the node to code line and wrapping it with a code block. If the cursor is not at the block start, it inserts a break before the code block.
+
+
+
+
+ Options for inserting nodes.
+
+
+
+
+### `insertCodeLine`
+
+Inserts a code line starting with the specified indentation depth.
+
+
+
+
+ The depth of indentation for the code line.
+ - **Default:** `0`
+
+
+
+
+### `outdentCodeLine`
+
+Outdents a code line, removing two whitespace characters if present.
+
+
+
+
+ The code line to be outdented.
+
+
+ The code block containing the code line to be outdented.
+
+
+
+
+### `toggleCodeBlock`
+
+Toggles the code block in the editor.
+
+### `unwrapCodeBlock`
+
+Unwraps the code block in the editor.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/column.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/column.cn.mdx
new file mode 100644
index 0000000000..d405cebe90
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/column.cn.mdx
@@ -0,0 +1,249 @@
+---
+title: 分栏
+docs:
+ - route: /docs/components/column-node
+ title: 分栏节点
+---
+
+
+
+
+
+## 功能特性
+
+- 在文档中添加分栏布局
+- 通过 `column-group-node` 工具栏选择多种分栏样式
+- [ ] 可调整栏宽
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用 `ColumnKit`,它包含预配置的 `ColumnPlugin` 和 `ColumnItemPlugin` 以及 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`ColumnGroupElement`](/docs/components/column-node): 渲染分栏组容器
+- [`ColumnElement`](/docs/components/column-node): 渲染单个分栏项
+
+### 添加套件
+
+将套件加入插件列表:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { ColumnKit } from '@/components/editor/plugins/column-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...ColumnKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/layout
+```
+
+### 添加插件
+
+在创建编辑器时将分栏插件加入 Plate 插件数组。
+
+```tsx
+import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ColumnPlugin,
+ ColumnItemPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+通过自定义组件配置插件来渲染分栏布局。
+
+```tsx
+import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
+import { createPlateEditor } from 'platejs/react';
+import { ColumnGroupElement, ColumnElement } from '@/components/ui/column-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ColumnPlugin.withComponent(ColumnGroupElement),
+ ColumnItemPlugin.withComponent(ColumnElement),
+ ],
+});
+```
+
+- `withComponent`: 指定 [`ColumnGroupElement`](/docs/components/column-node) 渲染分栏组容器,[`ColumnElement`](/docs/components/column-node) 渲染单个分栏。
+
+### 转换为工具栏按钮
+
+可将此功能添加至 [转换为工具栏按钮](/docs/toolbar#turn-into-toolbar-button) 来将区块转为分栏布局:
+
+```tsx
+{
+ icon: ,
+ label: '3栏布局',
+ value: 'action_three_columns',
+}
+```
+
+
+
+## 插件
+
+### `ColumnPlugin`
+
+为文档添加分栏功能。
+
+### `ColumnItemPlugin`
+
+为文档添加分栏项功能。
+
+## 类型定义
+
+### `TColumnGroupElement`
+
+继承自 `TElement`。
+
+### `TColumnElement`
+
+继承自 `TElement`。
+
+
+
+
+ 分栏宽度(必须以 `%` 结尾)
+
+
+
+
+## 转换方法
+
+### `insertColumnGroup`
+
+插入包含两个空栏的分栏组。
+
+
+
+ - `columns`: 栏宽数组或等宽分栏数量(默认:2)
+ - 其他 `InsertNodesOptions` 用于控制插入行为
+
+ 栏宽数组或等宽分栏数量(默认:2)
+
+
+ 其他控制插入行为的选项
+
+
+
+
+### `insertColumn`
+
+插入一个空栏。
+
+
+
+
+ 分栏宽度(默认:"33%")
+
+
+ 其他控制插入行为的选项
+
+
+
+
+### `moveMiddleColumn`
+
+将中间栏向左或向右移动。
+
+
+
+
+ 分栏元素的节点 entry
+
+
+ 控制中间栏移动方向
+
+
+
+
+ 若中间节点为空则返回 `false`(并移除该节点),否则返回 `true`。
+
+
+
+### `toggleColumnGroup`
+
+将区块转换为分栏组布局或更新现有分栏组的布局。
+
+
+- 若目标区块不是分栏组,则将其包裹在新的分栏组中并创建指定数量的分栏
+- 若目标区块已是分栏组,则使用 `setColumns` 更新其布局
+- 原始内容将成为第一栏的内容
+- 其他栏将创建空段落
+
+
+ 切换分栏组的位置
+
+
+ 要创建的等宽分栏数量(默认:2)
+
+
+ 栏宽数组(如 ['50%', '50%']),优先级高于 `columns`
+
+
+
+
+### `setColumns`
+
+更新现有分栏组的布局。
+
+
+- 增加分栏时:
+ - 保留现有栏内容
+ - 添加指定宽度的新空栏
+- 减少分栏时:
+ - 将被移除栏的内容合并到最后一栏
+ - 更新剩余栏的宽度
+- 分栏数量不变时:
+ - 仅更新栏宽
+
+
+ 分栏组元素的路径
+
+
+ 要创建的等宽分栏数量
+
+
+ 栏宽数组(如 ['33%', '67%']),优先级高于 `columns`
+
+
+
+
+## 钩子函数
+
+### `useDebouncePopoverOpen`
+
+
+
+ 弹窗是否处于打开状态
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/column.mdx b/apps/www/content/docs/(plugins)/(elements)/column.mdx
new file mode 100644
index 0000000000..edf6570482
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/column.mdx
@@ -0,0 +1,250 @@
+---
+title: Column
+docs:
+ - route: /docs/components/column-node
+ title: Column Nodes
+---
+
+
+
+
+
+## Features
+
+- Add columns to your document.
+- Choose from a variety of column layouts using `column-group-node` toolbar.
+- [ ] Resizable columns
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add column functionality is with the `ColumnKit`, which includes pre-configured `ColumnPlugin` and `ColumnItemPlugin` with [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`ColumnGroupElement`](/docs/components/column-node): Renders column group containers.
+- [`ColumnElement`](/docs/components/column-node): Renders individual column items.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { ColumnKit } from '@/components/editor/plugins/column-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...ColumnKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/layout
+```
+
+### Add Plugins
+
+Include the column plugins in your Plate plugins array when creating the editor.
+
+```tsx
+import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ColumnPlugin,
+ ColumnItemPlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+Configure the plugins with custom components to render column layouts.
+
+```tsx
+import { ColumnPlugin, ColumnItemPlugin } from '@platejs/layout/react';
+import { createPlateEditor } from 'platejs/react';
+import { ColumnGroupElement, ColumnElement } from '@/components/ui/column-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ColumnPlugin.withComponent(ColumnGroupElement),
+ ColumnItemPlugin.withComponent(ColumnElement),
+ ],
+});
+```
+
+- `withComponent`: Assigns [`ColumnGroupElement`](/docs/components/column-node) to render column group containers and [`ColumnElement`](/docs/components/column-node) to render individual columns.
+
+### Turn Into Toolbar Button
+
+You can add this item to the [Turn Into Toolbar Button](/docs/toolbar#turn-into-toolbar-button) to convert blocks into column layouts:
+
+```tsx
+{
+ icon: ,
+ label: '3 columns',
+ value: 'action_three_columns',
+}
+```
+
+
+
+## Plugins
+
+### `ColumnPlugin`
+
+Add Column Plugin to your document.
+
+### `ColumnItemPlugin`
+
+Add Column Item Plugin to your document.
+
+## Types
+
+### `TColumnGroupElement`
+
+Extends `TElement`.
+
+### `TColumnElement`
+
+Extends `TElement`.
+
+
+
+
+ The column's width (must end with `%`)
+
+
+
+
+## Transforms
+
+### `insertColumnGroup`
+
+Insert a columnGroup with two empty columns.
+
+
+
+ - `columns`: Array of column widths or number of equal-width columns (default: 2)
+ - Other `InsertNodesOptions` to control insert behavior
+
+ Array of column widths or number of equal-width columns (default: 2)
+
+
+ Other options to control insert behavior
+
+
+
+
+### `insertColumn`
+
+Insert an empty column.
+
+
+
+
+ Column width (default: "33%")
+
+
+ Other options to control insert behavior
+
+
+
+
+### `moveMiddleColumn`
+
+Move the middle column to the left or right.
+
+
+
+
+ The node entry of `column` element
+
+
+ Control the direction the middle column moves to
+
+
+
+
+ Returns `false` if the middle node is empty (and removes it), `true` otherwise.
+
+
+
+### `toggleColumnGroup`
+
+Convert a block into a column group layout or update an existing column group's layout.
+
+
+- If the target block is not a column group, wraps it in a new column group with the specified number of columns
+- If the target block is already a column group, updates its column layout using `setColumns`
+- The original content becomes the content of the first column
+- Additional columns are created with empty paragraphs
+
+
+ The location to toggle the column group at.
+
+
+ Number of equal-width columns to create (default: 2)
+
+
+ Array of column widths (e.g., ['50%', '50%']). Takes precedence over `columns`.
+
+
+
+
+### `setColumns`
+
+Update the column layout of an existing column group.
+
+
+- When increasing columns:
+ - Keeps existing column content
+ - Adds new empty columns with specified widths
+- When decreasing columns:
+ - Merges content from removed columns into the last remaining column
+ - Updates widths of remaining columns
+- When keeping same number of columns:
+ - Only updates column widths
+
+
+ The path to the column group element.
+
+
+ Number of equal-width columns to create.
+
+
+ Array of column widths (e.g., ['33%', '67%']). Takes precedence over `columns`.
+
+
+
+
+
+## Hooks
+
+### `useDebouncePopoverOpen`
+
+
+
+ Whether the popover is open.
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/date.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/date.cn.mdx
new file mode 100644
index 0000000000..ba3ee8b2a4
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/date.cn.mdx
@@ -0,0 +1,155 @@
+---
+title: 日期
+docs:
+ - route: /docs/components/date-node
+ title: 日期元素
+---
+
+
+
+
+
+## 功能特性
+
+- 使用内联日期元素在文本中插入和显示日期
+- 可通过日历界面轻松选择和修改这些日期
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷添加日期功能的方式是使用 `DateKit`,它包含了预配置的 `DatePlugin` 和 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`DateElement`](/docs/components/date-node): 渲染内联日期元素
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { DateKit } from '@/components/editor/plugins/date-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...DateKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/date
+```
+
+### 添加插件
+
+在创建编辑器时将 `DatePlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { DatePlugin } from '@platejs/date/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ DatePlugin,
+ ],
+});
+```
+
+### 配置插件
+
+使用自定义组件配置插件来渲染日期元素。
+
+```tsx
+import { DatePlugin } from '@platejs/date/react';
+import { createPlateEditor } from 'platejs/react';
+import { DateElement } from '@/components/ui/date-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ DatePlugin.withComponent(DateElement),
+ ],
+});
+```
+
+- `withComponent`: 指定 [`DateElement`](/docs/components/date-node) 来渲染内联日期元素
+
+### 插入工具栏按钮
+
+你可以将此项目添加到 [插入工具栏按钮](/docs/toolbar#insert-toolbar-button) 来插入日期元素:
+
+```tsx
+{
+ focusEditor: true,
+ icon: ,
+ label: '日期',
+ value: KEYS.date,
+}
+```
+
+
+
+## 插件
+
+### `DatePlugin`
+
+用于向文档添加日期元素的插件。
+
+## API
+
+### `isPointNextToNode`
+
+检查一个点是否邻近特定类型的节点。
+
+
+
+
+ 要检查邻近性的节点类型(例如 'date' 表示内联日期元素)
+
+
+ 检查邻近性的选项
+
+
+
+
+
+ 检查的起始位置。如未提供则使用当前选区锚点
+
+
+ 检查方向。为 true 时检查前一个节点,为 false 时检查后一个节点
+
+
+
+
+## 转换操作
+
+### `tf.insert.date`
+
+
+
+
+ 要插入的日期字符串,格式为 'YYYY-MM-DD'
+ - **默认值:** 当前日期
+
+
+ 插入节点的选项
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/date.mdx b/apps/www/content/docs/(plugins)/(elements)/date.mdx
new file mode 100644
index 0000000000..24b0363f3e
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/date.mdx
@@ -0,0 +1,155 @@
+---
+title: Date
+docs:
+ - route: /docs/components/date-node
+ title: Date Element
+---
+
+
+
+
+
+## Features
+
+- Insert and display dates within your text using inline date elements.
+- These dates can be easily selected and modified using a calendar interface.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add date functionality is with the `DateKit`, which includes pre-configured `DatePlugin` with [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`DateElement`](/docs/components/date-node): Renders inline date elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { DateKit } from '@/components/editor/plugins/date-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...DateKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/date
+```
+
+### Add Plugin
+
+Include `DatePlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { DatePlugin } from '@platejs/date/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ DatePlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+Configure the plugin with a custom component to render date elements.
+
+```tsx
+import { DatePlugin } from '@platejs/date/react';
+import { createPlateEditor } from 'platejs/react';
+import { DateElement } from '@/components/ui/date-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ DatePlugin.withComponent(DateElement),
+ ],
+});
+```
+
+- `withComponent`: Assigns [`DateElement`](/docs/components/date-node) to render inline date elements.
+
+### Insert Toolbar Button
+
+You can add this item to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert date elements:
+
+```tsx
+{
+ focusEditor: true,
+ icon: ,
+ label: 'Date',
+ value: KEYS.date,
+}
+```
+
+
+
+## Plugins
+
+### `DatePlugin`
+
+Plugin for adding date elements to your document.
+
+## API
+
+### `isPointNextToNode`
+
+Check if a point is next to a specific node type.
+
+
+
+
+ The type of node to check for adjacency (e.g. 'date' for inline date elements).
+
+
+ Options for checking adjacency.
+
+
+
+
+
+ Position to check from. Uses current selection anchor if not provided.
+
+
+ Direction to check. If true, checks previous node; if false, checks next node.
+
+
+
+
+## Transforms
+
+### `tf.insert.date`
+
+
+
+
+ The date string to insert in 'YYYY-MM-DD' format.
+ - **Default:** Current date
+
+
+ Options for inserting nodes.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/equation.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/equation.cn.mdx
new file mode 100644
index 0000000000..07f47a2942
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/equation.cn.mdx
@@ -0,0 +1,158 @@
+---
+title: 数学公式
+docs:
+ - route: /docs/components/equation-node
+ title: 公式元素
+ - route: /docs/components/equation-toolbar-button
+ title: 公式工具栏按钮
+ - route: https://pro.platejs.org/docs/components/equation-node
+ title: 公式元素
+ - route: https://pro.platejs.org/docs/components/equation-toolbar-button
+ title: 公式工具栏按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 支持LaTeX语法编写复杂数学表达式
+- 提供行内公式和块级公式两种格式
+- 使用KaTeX实时渲染公式
+- 便捷的公式编辑和导航功能
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用`MathKit`套件,它包含预配置的`EquationPlugin`和`InlineEquationPlugin`以及[Plate UI](/docs/installation/plate-ui)组件。
+
+
+
+- [`EquationElement`](/docs/components/equation-node): 渲染块级公式元素
+- [`InlineEquationElement`](/docs/components/equation-node): 渲染行内公式元素
+
+### 添加套件
+
+将套件添加到插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { MathKit } from '@/components/editor/plugins/math-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...MathKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/math
+```
+
+### 添加插件
+
+在创建编辑器时将公式插件包含到Plate插件数组中。
+
+```tsx
+import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ EquationPlugin,
+ InlineEquationPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+使用自定义组件配置插件来渲染公式元素。
+
+```tsx
+import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
+import { createPlateEditor } from 'platejs/react';
+import { EquationElement, InlineEquationElement } from '@/components/ui/equation-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ EquationPlugin.withComponent(EquationElement),
+ InlineEquationPlugin.withComponent(InlineEquationElement),
+ ],
+});
+```
+
+- `withComponent`: 指定[`EquationElement`](/docs/components/equation-node)渲染块级公式,[`InlineEquationElement`](/docs/components/equation-node)渲染行内公式。
+
+### 添加工具栏按钮
+
+您可以在[工具栏](/docs/toolbar)中添加[`EquationToolbarButton`](/docs/components/equation-toolbar-button)来插入公式。
+
+
+
+## Plate Plus
+
+
+
+## 插件
+
+### `EquationPlugin`
+
+用于块级公式元素的插件。
+
+### `InlineEquationPlugin`
+
+用于行内公式元素的插件。
+
+## 转换方法
+
+### `tf.insert.equation`
+
+
+
+ 插入节点的转换选项
+
+
+
+### `tf.insert.inlineEquation`
+
+插入一个行内公式。
+
+
+
+
+ 要插入的LaTeX表达式。如未提供则为空公式。
+
+
+ 插入节点的转换选项
+
+
+
+
+## 类型定义
+
+### `TEquationElement`
+
+```typescript
+interface TEquationElement extends TElement {
+ texExpression: string;
+}
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/equation.mdx b/apps/www/content/docs/(plugins)/(elements)/equation.mdx
new file mode 100644
index 0000000000..0cda87655d
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/equation.mdx
@@ -0,0 +1,156 @@
+---
+title: Equation
+docs:
+ - route: https://pro.platejs.org/docs/examples/equation
+ title: Plus
+ - route: /docs/components/equation-node
+ title: Equation Element
+ - route: /docs/components/equation-toolbar-button
+ title: Equation Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- LaTeX syntax support for complex mathematical expressions
+- Both inline and block equation formats
+- Real-time rendering of equations using KaTeX
+- Easy editing and navigation within equations
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add equation functionality is with the `MathKit`, which includes pre-configured `EquationPlugin` and `InlineEquationPlugin` with [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`EquationElement`](/docs/components/equation-node): Renders block equation elements.
+- [`InlineEquationElement`](/docs/components/equation-node): Renders inline equation elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { MathKit } from '@/components/editor/plugins/math-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...MathKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/math
+```
+
+### Add Plugins
+
+Include the equation plugins in your Plate plugins array when creating the editor.
+
+```tsx
+import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ EquationPlugin,
+ InlineEquationPlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+Configure the plugins with custom components to render equation elements.
+
+```tsx
+import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
+import { createPlateEditor } from 'platejs/react';
+import { EquationElement, InlineEquationElement } from '@/components/ui/equation-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ EquationPlugin.withComponent(EquationElement),
+ InlineEquationPlugin.withComponent(InlineEquationElement),
+ ],
+});
+```
+
+- `withComponent`: Assigns [`EquationElement`](/docs/components/equation-node) to render block equations and [`InlineEquationElement`](/docs/components/equation-node) to render inline equations.
+
+### Add Toolbar Button
+
+You can add [`EquationToolbarButton`](/docs/components/equation-toolbar-button) to your [Toolbar](/docs/toolbar) to insert equations.
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `EquationPlugin`
+
+Plugin for block equation elements.
+
+### `InlineEquationPlugin`
+
+Plugin for inline equation elements.
+
+## Transforms
+
+### `tf.insert.equation`
+
+
+
+ Options for the insert nodes transform.
+
+
+
+### `tf.insert.inlineEquation`
+
+Inserts an inline equation.
+
+
+
+
+ The LaTeX expression to insert. Empty equation if not provided.
+
+
+ Options for the insert nodes transform.
+
+
+
+
+## Types
+
+### `TEquationElement`
+
+```typescript
+interface TEquationElement extends TElement {
+ texExpression: string;
+}
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/excalidraw.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/excalidraw.cn.mdx
new file mode 100644
index 0000000000..197ac2af2c
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/excalidraw.cn.mdx
@@ -0,0 +1,99 @@
+---
+title: Excalidraw
+docs:
+ - route: /docs/components/excalidraw-node
+ title: Excalidraw 元素
+---
+
+
+
+
+
+## 功能特性
+
+- 集成 Excalidraw 库用于创建绘图和图表
+
+
+
+## 安装
+
+```bash
+npm install @platejs/excalidraw
+```
+
+## 使用方式
+
+```tsx
+import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
+
+const plugins = [
+ // ...其他插件
+ ExcalidrawPlugin,
+];
+```
+
+### 插入工具栏按钮
+
+可以将此项添加到[插入工具栏按钮](/docs/toolbar#insert-toolbar-button)来插入 Excalidraw 元素:
+
+```tsx
+{
+ icon: ,
+ label: 'Excalidraw',
+ value: KEYS.excalidraw,
+}
+```
+
+## 插件
+
+### `ExcalidrawPlugin`
+
+Excalidraw 空元素插件。
+
+## API 接口
+
+### `insertExcalidraw`
+
+向编辑器中插入 Excalidraw 元素。
+
+
+
+
+ Excalidraw 元素的属性参数
+
+
+ 插入 Excalidraw 元素的配置选项
+
+
+
+
+## 钩子函数
+
+### `useExcalidrawElement`
+
+Excalidraw 组件的行为钩子。
+
+
+
+
+ Excalidraw 元素
+
+
+ 在 Excalidraw 组件中显示的库项目
+ - **默认值:** `[]`
+
+
+ 是否滚动到 Excalidraw 组件内部内容
+ - **默认值:** `true`
+
+
+
+
+
+ Excalidraw 组件
+
+
+ 传递给 Excalidraw 组件的属性参数
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/excalidraw.mdx b/apps/www/content/docs/(plugins)/(elements)/excalidraw.mdx
new file mode 100644
index 0000000000..720de21dc3
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/excalidraw.mdx
@@ -0,0 +1,99 @@
+---
+title: Excalidraw
+docs:
+ - route: /docs/components/excalidraw-node
+ title: Excalidraw Element
+---
+
+
+
+
+
+## Features
+
+- Integrates Excalidraw library for creation of drawings and diagrams.
+
+
+
+## Installation
+
+```bash
+npm install @platejs/excalidraw
+```
+
+## Usage
+
+```tsx
+import { ExcalidrawPlugin } from '@platejs/excalidraw/react';
+
+const plugins = [
+ // ...otherPlugins
+ ExcalidrawPlugin,
+];
+```
+
+### Insert Toolbar Button
+
+You can add this item to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert Excalidraw elements:
+
+```tsx
+{
+ icon: ,
+ label: 'Excalidraw',
+ value: KEYS.excalidraw,
+}
+```
+
+## Plugins
+
+### `ExcalidrawPlugin`
+
+Excalidraw void element plugin.
+
+## API
+
+### `insertExcalidraw`
+
+Inserts an Excalidraw element into the editor.
+
+
+
+
+ Props for the Excalidraw element.
+
+
+ Options for inserting the Excalidraw element.
+
+
+
+
+## Hooks
+
+### `useExcalidrawElement`
+
+A behavior hook for the Excalidraw component.
+
+
+
+
+ The Excalidraw element.
+
+
+ Library items to display in the Excalidraw component.
+ - **Default:** `[]`
+
+
+ Whether to scroll to content inside the Excalidraw component.
+ - **Default:** `true`
+
+
+
+
+
+ The Excalidraw component.
+
+
+ Props to pass to the Excalidraw component.
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/heading.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/heading.cn.mdx
new file mode 100644
index 0000000000..5c1c5c12d5
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/heading.cn.mdx
@@ -0,0 +1,200 @@
+---
+title: 标题
+docs:
+ - route: /docs/components/heading-node
+ title: 标题元素
+---
+
+
+
+
+
+## 功能特点
+
+- 创建不同级别的标题(H1 到 H6)来构建内容结构。
+- 默认渲染为相应的 HTML 标题标签(`` 到 ``)。
+
+
+
+## Kit 使用
+
+
+
+### 安装
+
+添加标题插件最快的方法是使用 `BasicBlocksKit`,它包含预配置的 `H1Plugin`、`H2Plugin` 和 `H3Plugin` 以及其他基本块元素及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`H1Element`](/docs/components/heading-node): 渲染 H1 元素。
+- [`H2Element`](/docs/components/heading-node): 渲染 H2 元素。
+- [`H3Element`](/docs/components/heading-node): 渲染 H3 元素。
+
+### 添加 Kit
+
+将 kit 添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+当你需要更多控制或想要包含特定标题级别时,添加单独的标题插件。
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ H1Plugin,
+ H2Plugin,
+ H3Plugin,
+ // 根据需要添加 H4Plugin、H5Plugin、H6Plugin
+ ],
+});
+```
+
+### 配置插件
+
+使用自定义组件或快捷键配置单独的标题插件。
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';
+import { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ H1Plugin.configure({
+ node: { component: H1Element },
+ shortcuts: { toggle: { keys: 'mod+alt+1' } },
+ }),
+ H2Plugin.configure({
+ node: { component: H2Element },
+ shortcuts: { toggle: { keys: 'mod+alt+2' } },
+ }),
+ H3Plugin.configure({
+ node: { component: H3Element },
+ shortcuts: { toggle: { keys: 'mod+alt+3' } },
+ }),
+ // 类似地配置 H4Plugin、H5Plugin、H6Plugin
+ ],
+});
+```
+
+- `node.component`: 分配自定义 React 组件如 [`H1Element`](/docs/components/heading-node) 来渲染特定级别的标题。
+- `shortcuts.toggle`: 定义键盘[快捷键](/docs/plugin-shortcuts)(例如 `mod+alt+1`)来切换相应的标题级别。
+
+### 转换为工具栏按钮
+
+你可以将这些项目添加到[转换为工具栏按钮](/docs/toolbar#turn-into-toolbar-button)以将块转换为标题:
+
+```tsx
+{
+ icon: ,
+ label: '标题 1',
+ value: 'h1',
+}
+```
+
+```tsx
+{
+ icon: ,
+ label: '标题 2',
+ value: 'h2',
+}
+```
+
+```tsx
+{
+ icon: ,
+ label: '标题 3',
+ value: 'h3',
+}
+```
+
+### 插入工具栏按钮
+
+你可以将这些项目添加到[插入工具栏按钮](/docs/toolbar#insert-toolbar-button)以插入标题元素:
+
+```tsx
+{
+ icon: ,
+ label: '标题 1',
+ value: 'h1',
+}
+```
+
+```tsx
+{
+ icon: ,
+ label: '标题 2',
+ value: 'h2',
+}
+```
+
+```tsx
+{
+ icon: ,
+ label: '标题 3',
+ value: 'h3',
+}
+```
+
+
+
+## 插件
+
+### `H1Plugin`
+
+用于 H1 标题元素的插件。
+
+### `H2Plugin`
+
+用于 H2 标题元素的插件。
+
+### `H3Plugin`
+
+用于 H3 标题元素的插件。
+
+### `H4Plugin`
+
+用于 H4 标题元素的插件。
+
+### `H5Plugin`
+
+用于 H5 标题元素的插件。
+
+### `H6Plugin`
+
+用于 H6 标题元素的插件。
+
+## 转换
+
+### `tf.h1.toggle`
+
+切换所选块类型为标题。
diff --git a/apps/www/content/docs/(plugins)/(elements)/heading.mdx b/apps/www/content/docs/(plugins)/(elements)/heading.mdx
new file mode 100644
index 0000000000..0640e8f3fd
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/heading.mdx
@@ -0,0 +1,200 @@
+---
+title: Heading
+docs:
+ - route: /docs/components/heading-node
+ title: Heading Element
+---
+
+
+
+
+
+## Features
+
+- Create headings of various levels (H1 to H6) to structure content.
+- Renders as appropriate HTML heading tags (`` to ``) by default.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add heading plugins is with the `BasicBlocksKit`, which includes pre-configured `H1Plugin`, `H2Plugin`, and `H3Plugin` along with other basic block elements and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`H1Element`](/docs/components/heading-node): Renders H1 elements.
+- [`H2Element`](/docs/components/heading-node): Renders H2 elements.
+- [`H3Element`](/docs/components/heading-node): Renders H3 elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugins
+
+Add individual heading plugins when you need more control or want to include specific heading levels.
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ H1Plugin,
+ H2Plugin,
+ H3Plugin,
+ // Add H4Plugin, H5Plugin, H6Plugin as needed
+ ],
+});
+```
+
+### Configure Plugins
+
+Configure individual heading plugins with custom components or shortcuts.
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';
+import { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ H1Plugin.configure({
+ node: { component: H1Element },
+ shortcuts: { toggle: { keys: 'mod+alt+1' } },
+ }),
+ H2Plugin.configure({
+ node: { component: H2Element },
+ shortcuts: { toggle: { keys: 'mod+alt+2' } },
+ }),
+ H3Plugin.configure({
+ node: { component: H3Element },
+ shortcuts: { toggle: { keys: 'mod+alt+3' } },
+ }),
+ // Configure H4Plugin, H5Plugin, H6Plugin similarly
+ ],
+});
+```
+
+- `node.component`: Assigns a custom React component like [`H1Element`](/docs/components/heading-node) to render the specific heading level.
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) (e.g., `mod+alt+1`) to toggle the respective heading level.
+
+### Turn Into Toolbar Button
+
+You can add these items to the [Turn Into Toolbar Button](/docs/toolbar#turn-into-toolbar-button) to convert blocks into headings:
+
+```tsx
+{
+ icon: ,
+ label: 'Heading 1',
+ value: 'h1',
+}
+```
+
+```tsx
+{
+ icon: ,
+ label: 'Heading 2',
+ value: 'h2',
+}
+```
+
+```tsx
+{
+ icon: ,
+ label: 'Heading 3',
+ value: 'h3',
+}
+```
+
+### Insert Toolbar Button
+
+You can add these items to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert heading elements:
+
+```tsx
+{
+ icon: ,
+ label: 'Heading 1',
+ value: 'h1',
+}
+```
+
+```tsx
+{
+ icon: ,
+ label: 'Heading 2',
+ value: 'h2',
+}
+```
+
+```tsx
+{
+ icon: ,
+ label: 'Heading 3',
+ value: 'h3',
+}
+```
+
+
+
+## Plugins
+
+### `H1Plugin`
+
+Plugin for H1 heading elements.
+
+### `H2Plugin`
+
+Plugin for H2 heading elements.
+
+### `H3Plugin`
+
+Plugin for H3 heading elements.
+
+### `H4Plugin`
+
+Plugin for H4 heading elements.
+
+### `H5Plugin`
+
+Plugin for H5 heading elements.
+
+### `H6Plugin`
+
+Plugin for H6 heading elements.
+
+## Transforms
+
+### `tf.h1.toggle`
+
+Toggles the selected block types to heading.
diff --git a/apps/www/content/docs/(plugins)/(elements)/horizontal-rule.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/horizontal-rule.cn.mdx
new file mode 100644
index 0000000000..1502b708c0
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/horizontal-rule.cn.mdx
@@ -0,0 +1,132 @@
+---
+title: 水平分隔线
+docs:
+ - route: /docs/components/hr-node
+ title: Hr 元素
+---
+
+
+
+
+
+## 功能特性
+
+- 插入水平线来分隔内容或表示主题转换
+- 在新行开头输入三个破折号 (`---`) 可自动转换为水平分隔线
+- 默认渲染为 ` ` HTML 元素
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+添加水平分隔线插件最快的方式是使用 `BasicBlocksKit`,该套件包含预配置的 `HorizontalRulePlugin` 以及其他基础块元素及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`HrElement`](/docs/components/hr-node): 渲染水平分隔线元素
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+在创建编辑器时,将 `HorizontalRulePlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { HorizontalRulePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ HorizontalRulePlugin,
+ ],
+});
+```
+
+### 配置插件
+
+你可以配置 `HorizontalRulePlugin` 的自动格式化规则,将输入的特定模式(如 `---`)自动转换为水平分隔线。
+
+```tsx
+import { insertNodes, setNodes, KEYS } from 'platejs';
+import { AutoformatPlugin } from '@platejs/autoformat';
+import { HorizontalRulePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ HorizontalRulePlugin,
+ AutoformatPlugin.configure({
+ options: {
+ rules: [
+ {
+ mode: 'block',
+ type: KEYS.hr,
+ match: ['---', '—-', '___ '],
+ format: (editor) => {
+ setNodes(editor, { type: KEYS.hr });
+ insertNodes(editor, {
+ type: KEYS.p,
+ children: [{ text: '' }],
+ });
+ },
+ },
+ ],
+ },
+ }),
+ ],
+});
+```
+
+- `AutoformatPlugin`: 自动将输入的特定模式(如 `---`)转换为水平分隔线
+
+### 插入工具栏按钮
+
+你可以将此项目添加到 [插入工具栏按钮](/docs/toolbar#insert-toolbar-button) 中来插入水平分隔线:
+
+```tsx
+{
+ icon: ,
+ label: '分隔线',
+ value: KEYS.hr,
+}
+```
+
+
+
+## 插件
+
+### `HorizontalRulePlugin`
+
+用于插入水平分隔线来分隔内容的插件。水平分隔线是 void 元素,默认渲染为 ` ` 标签。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/horizontal-rule.mdx b/apps/www/content/docs/(plugins)/(elements)/horizontal-rule.mdx
new file mode 100644
index 0000000000..4ff25b3fb2
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/horizontal-rule.mdx
@@ -0,0 +1,132 @@
+---
+title: Horizontal Rule
+docs:
+ - route: /docs/components/hr-node
+ title: Hr Element
+---
+
+
+
+
+
+## Features
+
+- Insert horizontal lines to separate content or indicate topic shifts
+- Type three dashes (`---`) at a new line start to transform into a horizontal rule
+- Renders as ` ` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the horizontal rule plugin is with the `BasicBlocksKit`, which includes pre-configured `HorizontalRulePlugin` along with other basic block elements and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`HrElement`](/docs/components/hr-node): Renders horizontal rule elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicBlocksKit } from '@/components/editor/plugins/basic-blocks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicBlocksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `HorizontalRulePlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { HorizontalRulePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ HorizontalRulePlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `HorizontalRulePlugin` with autoformat rules to automatically convert typed patterns like `---` into horizontal rules.
+
+```tsx
+import { insertNodes, setNodes, KEYS } from 'platejs';
+import { AutoformatPlugin } from '@platejs/autoformat';
+import { HorizontalRulePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ HorizontalRulePlugin,
+ AutoformatPlugin.configure({
+ options: {
+ rules: [
+ {
+ mode: 'block',
+ type: KEYS.hr,
+ match: ['---', '—-', '___ '],
+ format: (editor) => {
+ setNodes(editor, { type: KEYS.hr });
+ insertNodes(editor, {
+ type: KEYS.p,
+ children: [{ text: '' }],
+ });
+ },
+ },
+ ],
+ },
+ }),
+ ],
+});
+```
+
+- `AutoformatPlugin`: Automatically converts typed patterns (like `---`) into horizontal rules.
+
+### Insert Toolbar Button
+
+You can add this item to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert horizontal rules:
+
+```tsx
+{
+ icon: ,
+ label: 'Divider',
+ value: KEYS.hr,
+}
+```
+
+
+
+## Plugins
+
+### `HorizontalRulePlugin`
+
+Plugin for inserting horizontal rules to separate content. Horizontal rules are void elements that render as ` ` tags by default.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/link.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/link.cn.mdx
new file mode 100644
index 0000000000..008bca02c7
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/link.cn.mdx
@@ -0,0 +1,646 @@
+---
+title: 链接
+docs:
+ - route: /docs/components/link-node
+ title: 链接元素
+ - route: /docs/components/link-toolbar
+ title: 链接浮动工具栏
+ - route: /docs/components/link-toolbar-button
+ title: 链接工具栏按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 支持超链接的插入、编辑和删除。
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷添加链接功能的方式是使用 `LinkKit`,它包含预配置的 `LinkPlugin` 以及浮动工具栏和 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`LinkElement`](/docs/components/link-node): 渲染链接元素
+- [`LinkFloatingToolbar`](/docs/components/link-toolbar): 提供链接编辑的浮动工具栏
+
+### 添加套件
+
+将套件添加到插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { LinkKit } from '@/components/editor/plugins/link-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...LinkKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/link
+```
+
+### 添加插件
+
+在创建编辑器时,将 `LinkPlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { LinkPlugin } from '@platejs/link/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ LinkPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+使用浮动工具栏和自定义组件配置插件。
+
+```tsx
+import { LinkPlugin } from '@platejs/link/react';
+import { createPlateEditor } from 'platejs/react';
+import { LinkElement } from '@/components/ui/link-node';
+import { LinkFloatingToolbar } from '@/components/ui/link-toolbar';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ LinkPlugin.configure({
+ render: {
+ node: LinkElement,
+ afterEditable: () => ,
+ },
+ }),
+ ],
+});
+```
+
+- `render.afterEditable`: 在可编辑区域后渲染 [`LinkFloatingToolbar`](/docs/components/link-toolbar) 用于链接编辑
+- `render.node`: 指定 [`LinkElement`](/docs/components/link-node) 来渲染链接元素
+
+### 添加工具栏按钮
+
+您可以在[工具栏](/docs/toolbar)中添加 [`LinkToolbarButton`](/docs/components/link-toolbar-button) 来插入和编辑链接。
+
+
+
+## 键盘快捷键
+
+
+ 在选中文本上添加链接
+
+
+## 插件
+
+### `LinkPlugin`
+
+链接格式化插件。
+
+
+
+
+决定是否强制提交链接表单。
+
+
+允许自定义 rangeBeforeOptions 配置。
+- **默认值:**
+```ts
+{
+ matchString: ' ',
+ skipInvalid: true,
+ afterMatch: true,
+}
+```
+
+
+触发浮动链接的热键。
+- **默认值:** **`'meta+k, ctrl+k'`**
+
+
+允许的URL协议列表。
+- **默认值:** **`['http', 'https', 'mailto', 'tel']`**
+
+
+决定是否跳过链接的消毒处理。
+- **默认值:** **`false`**
+
+
+链接元素的默认HTML属性。
+- **默认值:** **`{}`**
+
+
+粘贴链接时默认保留选中文本。
+- **默认值:** **`true`**
+
+
+验证URL的回调函数。
+- **默认值:** **`isUrl`**
+
+
+可选获取URL href的回调函数。返回与文本内容不同的可选链接。例如,为 `google.com` 返回 `https://google.com`。
+
+
+在验证前可选转换用户提交的URL输入的回调函数。
+
+
+当使用键盘快捷键或工具栏鼠标按下时,调用此函数获取链接URL。默认行为是使用浏览器的原生 `prompt`。
+
+
+
+
+## 转换操作
+
+### `tf.insert.link`
+
+向编辑器中插入链接节点。
+
+
+
+
+ 插入链接的选项。
+
+
+
+
+ 创建链接节点的选项。
+
+
+ 插入节点的附加选项。
+
+
+
+
+## API
+
+### `api.floatingLink.hide`
+
+隐藏浮动链接并重置其状态。
+
+### `api.floatingLink.reset`
+
+重置浮动链接状态但不改变 openEditorId。
+
+### `api.floatingLink.show`
+
+为指定模式和编辑器ID显示浮动链接。
+
+
+
+
+设置浮动链接的模式('edit' 或 'insert')。
+
+
+应显示浮动链接的编辑器ID。
+
+
+
+
+### `api.link.getAttributes`
+
+获取链接元素的属性。
+
+
+
+
+要获取属性的链接元素。
+
+
+
+
+链接元素的HTML属性。
+
+
+
+### `api.link.submitFloatingLink`
+
+如果URL有效则插入链接,关闭浮动链接并聚焦编辑器。
+
+
+如果链接成功插入则返回 `true`。
+
+
+### `insertLink`
+
+向编辑器中插入链接节点。
+
+
+
+
+ 创建链接节点的选项。
+
+
+ 节点插入的附加选项。
+
+
+
+
+### `submitFloatingLink`
+
+如果URL有效则插入链接,关闭浮动链接并聚焦编辑器。
+
+- 如果URL有效则插入链接
+- 如果文本为空则使用URL作为文本
+- 关闭浮动链接
+- 聚焦编辑器
+
+
+
+如果链接被插入则返回 `true`。
+
+
+
+### `triggerFloatingLink`
+
+触发浮动链接。
+
+
+
+
+ 浮动链接是否应被聚焦。
+
+
+
+
+### `triggerFloatingLinkEdit`
+
+触发浮动链接编辑。
+
+
+
+如果链接被编辑则返回 `true`。
+
+
+
+### `triggerFloatingLinkInsert`
+
+触发浮动链接。以下情况不触发:
+- 选择跨多个块
+- 选择包含多个叶子节点
+- 最低层级选择不是文本
+- 选择包含链接节点
+
+
+
+
+ 浮动链接是否应被聚焦。
+
+
+
+
+ 如果链接被插入则返回 `true`。
+
+
+
+### `unwrapLink`
+
+解包链接节点。
+
+
+
+
+ 如果为 `true`,当选择在链接内部时分割节点。
+
+
+
+
+### `upsertLink`
+
+插入或更新链接节点。行为取决于当前选择和选项:
+
+- 如果选择在链接中或不是URL:
+ - 当 `insertTextInLink: true` 时,在链接中插入URL作为文本
+ - 否则,如果 `text` 为空,则设置为URL
+ - 除非 `skipValidation: true`,否则验证URL
+- 如果选择已展开或链接中 `update: true`:
+ - 移除链接节点并获取链接文本
+- 然后:
+ - 插入带有更新URL和目标的链接节点
+ - 如果提供 `text`,则替换链接文本
+
+
+
+
+ 更新链接的选项。
+
+
+
+
+
+ 链接的URL。
+
+
+ 链接的文本内容。
+
+
+ 链接的目标属性。
+
+
+ 如果为 `true`,在链接中插入URL作为文本。
+
+
+ 插入节点的选项。
+
+
+ 如果为 `true`,跳过URL验证。
+ - **默认值:** `false`
+
+
+
+
+ 如果链接被插入或更新则返回 `true`。
+
+
+
+### `upsertLinkText`
+
+如果文本与上方链接文本不同,则用新文本节点替换链接子节点。新文本节点具有与链接中第一个文本节点相同的标记。
+
+
+
+
+ 用于替换链接子节点的新文本。
+
+
+
+
+### `validateUrl`
+
+根据插件选项验证URL。
+
+
+
+
+ 要验证的URL。
+
+
+
+
+ 如果URL有效则返回 `true`。
+
+
+
+### `wrapLink`
+
+用分割方式包裹链接节点。
+
+
+
+
+ 链接的URL。
+
+
+ 链接的目标属性。
+
+
+
+
+### `CreateLinkNodeOptions`
+
+创建新链接节点的选项。
+
+
+
+
+ 正在创建的链接节点的URL。
+
+
+ 链接节点显示的文本。如果未提供,则使用URL作为显示文本。
+
+
+ 指定打开URL的位置:
+ - `_blank`: 新标签页
+ - `_self`: 相同框架
+ - `_parent`: 父框架
+ - `_top`: 整个窗口
+
+
+ 表示链接内容的文本节点数组。
+
+
+
+
+## API 组件
+
+### `FloatingLinkNewTabInput`
+
+控制链接是否在新标签页中打开的输入组件。
+
+
+
+
+ 链接是否应在新标签页中打开。
+
+
+ 更新选中状态的函数。
+
+
+ 输入元素的引用。
+
+
+
+
+### `FloatingLinkUrlInput`
+
+用于输入和编辑链接URL的输入组件。
+
+
+
+
+ 输入元素的引用。
+
+
+
+
+### `LinkOpenButton`
+
+用于打开链接URL的按钮组件。
+
+
+
+
+ 包含要打开URL的链接元素。
+
+
+
+
+### `useFloatingLinkEdit`
+
+浮动链接编辑功能的行为钩子。
+
+
+
+
+ 虚拟浮动返回对象。
+
+
+
+
+
+ 浮动元素的引用回调。
+
+
+ 浮动元素的属性。
+
+
+ 浮动链接的样式。
+
+
+
+
+ 编辑按钮的属性。
+
+
+ 点击编辑按钮时调用的函数。
+
+
+
+
+ 取消链接按钮的属性。
+
+
+ 点击取消链接按钮时调用的函数。
+
+
+
+
+
+
+### `useFloatingLinkEnter`
+
+监听Enter键按下事件并提交编辑器中的浮动链接。
+
+### `useFloatingLinkEscape`
+
+监听Escape键按下事件并处理编辑器中浮动链接的行为。
+
+### `useFloatingLinkInsert`
+
+插入链接的行为钩子。
+
+
+
+
+ 虚拟浮动返回对象。
+
+
+ 浮动元素的引用。
+
+
+
+
+
+ 浮动元素的引用回调。
+
+
+ 浮动元素的属性。
+
+
+ 浮动链接的样式。
+
+
+
+
+ 文本输入的属性。
+
+
+ 文本输入值变化时调用的函数。
+
+
+ 文本输入的默认值。
+
+
+
+
+
+
+### `useLink`
+
+链接元素的行为钩子。
+
+
+
+
+ 链接元素。
+
+
+
+
+
+ 链接元素的属性。
+
+
+ 鼠标悬停在链接上时调用的函数。
+
+
+
+
+
+
+### `useLinkToolbarButton`
+
+链接工具栏按钮的行为钩子。
+
+
+
+
+ 选择是否在链接中。
+
+
+
+
+
+ 工具栏按钮的属性。
+
+
+ 链接是否被按下。
+
+
+ 点击按钮时调用的函数。
+
+
+
+
+
+
+### `useVirtualFloatingLink`
+
+用于管理链接虚拟浮动的自定义钩子。
+
+
+
+
+ 链接所属编辑器的 ID。
+
+
+ 虚拟浮动的选项。
+
+
+
+
+ `useVirtualFloating` 钩子的返回值。
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/link.mdx b/apps/www/content/docs/(plugins)/(elements)/link.mdx
new file mode 100644
index 0000000000..caae1ffb02
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/link.mdx
@@ -0,0 +1,652 @@
+---
+title: Link
+docs:
+ - route: https://pro.platejs.org/docs/examples/link
+ title: Plus
+ - route: /docs/components/link-node
+ title: Link Element
+ - route: /docs/components/link-toolbar
+ title: Link Floating Toolbar
+ - route: /docs/components/link-toolbar-button
+ title: Link Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Support for hyperlink insertion, edition and removal.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add link functionality is with the `LinkKit`, which includes pre-configured `LinkPlugin` with floating toolbar and [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`LinkElement`](/docs/components/link-node): Renders link elements.
+- [`LinkFloatingToolbar`](/docs/components/link-toolbar): Provides floating toolbar for link editing.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { LinkKit } from '@/components/editor/plugins/link-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...LinkKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/link
+```
+
+### Add Plugin
+
+Include `LinkPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { LinkPlugin } from '@platejs/link/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ LinkPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+Configure the plugin with floating toolbar and custom components.
+
+```tsx
+import { LinkPlugin } from '@platejs/link/react';
+import { createPlateEditor } from 'platejs/react';
+import { LinkElement } from '@/components/ui/link-node';
+import { LinkFloatingToolbar } from '@/components/ui/link-toolbar';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ LinkPlugin.configure({
+ render: {
+ node: LinkElement,
+ afterEditable: () => ,
+ },
+ }),
+ ],
+});
+```
+
+- `render.afterEditable`: Renders [`LinkFloatingToolbar`](/docs/components/link-toolbar) after the editable area for link editing.
+- `render.node`: Assigns [`LinkElement`](/docs/components/link-node) to render link elements.
+
+### Add Toolbar Button
+
+You can add [`LinkToolbarButton`](/docs/components/link-toolbar-button) to your [Toolbar](/docs/toolbar) to insert and edit links.
+
+
+
+## Keyboard Shortcuts
+
+
+ Add a link on the selected text.
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `LinkPlugin`
+
+Plugin for link formatting.
+
+
+
+
+Determines whether to force the submission of the link form.
+
+
+Allows custom configurations for rangeBeforeOptions.
+- **Default:**
+```ts
+{
+ matchString: ' ',
+ skipInvalid: true,
+ afterMatch: true,
+}
+```
+
+
+Hotkeys to trigger floating link.
+- **Default:** **`'meta+k, ctrl+k'`**
+
+
+List of allowed URL schemes.
+- **Default:** **`['http', 'https', 'mailto', 'tel']`**
+
+
+Determines whether the sanitation of links should be skipped.
+- **Default:** **`false`**
+
+
+Default HTML attributes for link elements.
+- **Default:** **`{}`**
+
+
+Keeps selected text on pasting links by default.
+- **Default:** **`true`**
+
+
+Callback function to validate a URL.
+- **Default:** **`isUrl`**
+
+
+Callback function to optionally get the href for a URL. It returns an optional link that is different from the text content. For example, returns `https://google.com` for `google.com`.
+
+
+Callback function to optionally transform the submitted URL provided by the user to the URL input before validation.
+
+
+On keyboard shortcut or toolbar mousedown, this function is called to get the link URL. The default behavior is to use the browser's native `prompt`.
+
+
+
+
+## Transforms
+
+### `tf.insert.link`
+
+Inserts a link node into the editor.
+
+
+
+
+ Options for inserting the link.
+
+
+
+
+ Options for creating the link node.
+
+
+ Additional options for inserting nodes.
+
+
+
+
+## API
+
+### `api.floatingLink.hide`
+
+Hides the floating link and resets its state.
+
+### `api.floatingLink.reset`
+
+Resets the floating link state without changing the openEditorId.
+
+### `api.floatingLink.show`
+
+Shows the floating link for the specified mode and editor ID.
+
+
+
+
+The mode to set for the floating link ('edit' or 'insert').
+
+
+The ID of the editor where the floating link should be shown.
+
+
+
+
+### `api.link.getAttributes`
+
+Gets the attributes for a link element.
+
+
+
+
+The link element for which to get attributes.
+
+
+
+
+The HTML attributes for the link element.
+
+
+
+### `api.link.submitFloatingLink`
+
+Inserts a link if the URL is valid, closes the floating link, and focuses the editor.
+
+
+Returns `true` if the link was inserted successfully.
+
+
+### `insertLink`
+
+Inserts a link node into the editor.
+
+
+
+
+ Options for creating link node.
+
+
+ Additional options for node insertion.
+
+
+
+
+### `submitFloatingLink`
+
+Inserts a link if the URL is valid, closes the floating link, and focuses the editor.
+
+- Insert link if url is valid.
+- Text is url if empty.
+- Close floating link.
+- Focus editor.
+
+
+
+Returns `true` if the link was inserted.
+
+
+
+### `triggerFloatingLink`
+
+Triggers the floating link.
+
+
+
+
+ Whether the floating link should be focused.
+
+
+
+
+### `triggerFloatingLinkEdit`
+
+Triggers the floating link edit.
+
+
+
+Returns `true` if the link was edited.
+
+
+
+### `triggerFloatingLinkInsert`
+
+Trigger floating link. Do not trigger when:
+- Selection is across blocks
+- Selection has more than one leaf node
+- Lowest selection is not text
+- Selection has a link node
+
+
+
+
+ Whether the floating link should be focused.
+
+
+
+
+ Returns `true` if the link was inserted.
+
+
+
+### `unwrapLink`
+
+Unwraps a link node.
+
+
+
+
+ If `true`, split the nodes if the selection is inside the link.
+
+
+
+
+### `upsertLink`
+
+Insert or update a link node. The behavior depends on the current selection and options:
+
+- If selection is in a link or not a URL:
+ - With `insertTextInLink: true`, inserts URL as text in link
+ - Otherwise, if `text` is empty, sets it to URL
+ - Validates URL unless `skipValidation: true`
+- If selection is expanded or `update: true` in a link:
+ - Removes link node and gets link text
+- Then:
+ - Inserts link node with updated URL and target
+ - If `text` is provided, replaces link text
+
+
+
+
+ Options for upserting the link.
+
+
+
+
+
+ The URL of the link.
+
+
+ The text content of the link.
+
+
+ The target attribute of the link.
+
+
+ If `true`, insert the URL as text in the link.
+
+
+ The options for inserting nodes.
+
+
+ If `true`, skips URL validation.
+ - **Default:** `false`
+
+
+
+
+ Returns `true` if the link was inserted or updated.
+
+
+
+### `upsertLinkText`
+
+If the text is different from the link above text, replaces the link children with a new text node. The new text node has the same marks as the first text node in the link.
+
+
+
+
+ The new text to replace the link children with.
+
+
+
+
+### `validateUrl`
+
+Validates a URL based on the plugin options.
+
+
+
+
+ The URL to validate.
+
+
+
+
+ Returns `true` if the URL is valid.
+
+
+
+### `wrapLink`
+
+Wrap a link node with split.
+
+
+
+
+ The URL of the link.
+
+
+ The target attribute of the link.
+
+
+
+
+### `CreateLinkNodeOptions`
+
+Options for creating a new link node.
+
+
+
+
+ The URL of the link node that is being created.
+
+
+ The text that is displayed for the link node. If not provided, the URL is used as the display text.
+
+
+ Specifies where to open the URL:
+ - `_blank`: new tab
+ - `_self`: same frame
+ - `_parent`: parent frame
+ - `_top`: full window
+
+
+ An array of text nodes that represent the link content.
+
+
+
+
+## API Components
+
+### `FloatingLinkNewTabInput`
+
+The input component for controlling whether a link opens in a new tab.
+
+
+
+
+ Whether the link should open in a new tab.
+
+
+ Function to update the checked state.
+
+
+ Reference to the input element.
+
+
+
+
+### `FloatingLinkUrlInput`
+
+The input component for entering and editing link URLs.
+
+
+
+
+ Reference to the input element.
+
+
+
+
+### `LinkOpenButton`
+
+The button component for opening the link URL.
+
+
+
+
+ The link element containing the URL to open.
+
+
+
+
+### `useFloatingLinkEdit`
+
+The behavior hook for the floating link edit functionality.
+
+
+
+
+ The virtual floating returned object.
+
+
+
+
+
+ The ref callback for the floating element.
+
+
+ Props for the floating element.
+
+
+ The style of the floating link.
+
+
+
+
+ Props for the edit button.
+
+
+ The function to call when the edit button is clicked.
+
+
+
+
+ Props for the unlink button.
+
+
+ The function to call when the unlink button is clicked.
+
+
+
+
+
+
+### `useFloatingLinkEnter`
+
+Listens for the Enter key press event and submits the floating link in the editor.
+
+### `useFloatingLinkEscape`
+
+Listens for the Escape key press event and handles the behavior of the floating link in the editor.
+
+### `useFloatingLinkInsert`
+
+The behavior hook for inserting a link.
+
+
+
+
+ The virtual floating returned object.
+
+
+ The ref of the floating element.
+
+
+
+
+
+ The ref callback for the floating element.
+
+
+ Props for the floating element.
+
+
+ The style of the floating link.
+
+
+
+
+ Props for the text input.
+
+
+ The function to call when the text input value changes.
+
+
+ The default value of the text input.
+
+
+
+
+
+
+### `useLink`
+
+The behavior hook for the link element.
+
+
+
+
+ The link element.
+
+
+
+
+
+ Props for the link element.
+
+
+ The function to call when the mouse is over the link.
+
+
+
+
+
+
+### `useLinkToolbarButton`
+
+The behavior hook for the link toolbar button.
+
+
+
+
+ Whether the selection is in a link.
+
+
+
+
+
+ Props for the toolbar button.
+
+
+ Whether the link is pressed.
+
+
+ The function to call when the button is clicked.
+
+
+
+
+
+
+### `useVirtualFloatingLink`
+
+Custom hook for managing virtual floating of a link.
+
+
+
+
+ The ID of the editor to which the link belongs.
+
+
+ Options for virtual floating.
+
+
+
+
+ The return value of the `useVirtualFloating` hook.
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/list-classic.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/list-classic.cn.mdx
new file mode 100644
index 0000000000..52c548b77c
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/list-classic.cn.mdx
@@ -0,0 +1,514 @@
+---
+title: 经典列表
+docs:
+ - route: /docs/components/list-classic-node
+ title: 列表节点
+ - route: /docs/components/list-classic-toolbar-button
+ title: 列表工具栏按钮
+---
+
+
+
+
+ Plate 提供两种实现列表的方法:
+
+ 1. **此经典列表插件** - 具有严格嵌套规则的传统 HTML 规范列表:
+ - 遵循标准 HTML 列表结构(`ul`/`ol` > `li`)
+ - 保持一致的列表层次结构
+ - 最适合可能导出为 HTML/markdown 的内容
+ - 复杂度最高
+
+ 2. [**列表插件**](/docs/list) - 灵活的基于缩进的列表:
+ - 更像 Word/Google Docs 的行为
+ - 任何块都可以缩进以创建类似列表的结构
+ - 在 [AI 编辑器](/editors#editor-ai) 中使用
+ - 支持嵌套待办事项列表
+ - 更适合自由形式的内容组织
+
+ 根据您的需求选择:
+ - 当您需要标准 HTML 列表兼容性时,使用**经典列表插件**
+ - 当您想要更灵活的缩进行为时,使用**列表插件**
+
+
+
+
+
+## 功能
+
+- **符合 HTML 的列表**:
+ - 标准 `ul`/`ol` > `li` 结构
+ - 正确的嵌套和层次结构
+ - SEO 友好的标记
+ - 干净的 HTML/markdown 导出
+
+- **列表类型**:
+ - 无序(项目符号)列表
+ - 有序(编号)列表
+ - 带复选框的任务列表
+ - 嵌套子列表
+
+- **拖放**:
+ - 目前仅支持根级列表项
+ - 同级和嵌套项拖放尚未支持
+
+- **快捷键**:
+ - 结合自动格式化插件,使用 markdown 快捷键(**`-`**、**`*`**、**`1.`**、**`[ ]`**)创建列表
+ - Tab/Shift+Tab 用于缩进控制
+
+- **限制(使用 [列表插件](/docs/list) 获取这些功能)**:
+ - 拖放仅支持根级列表
+ - 列表项无法对齐
+
+对于更灵活的、类似 Word 的方法,请参阅[列表插件](/docs/list)。
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+添加列表功能的最快方法是使用 `ListKit`,它包含预配置的列表插件,带有 [Plate UI](/docs/installation/plate-ui) 组件和键盘快捷键。
+
+
+
+- [`BulletedListElement`](/docs/components/list-classic-node):渲染无序列表元素。
+- [`NumberedListElement`](/docs/components/list-classic-node):渲染有序列表元素。
+- [`TaskListElement`](/docs/components/list-classic-node):渲染带复选框的任务列表元素。
+
+### 添加套件
+
+将套件添加到您的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { ListKit } from '@/components/editor/plugins/list-classic-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...ListKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/list-classic
+```
+
+### 添加插件
+
+在创建编辑器时,将列表插件包含在您的 Plate 插件数组中。
+
+```tsx
+import { ListPlugin, BulletedListPlugin, NumberedListPlugin, TaskListPlugin, ListItemPlugin } from '@platejs/list-classic/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ListPlugin,
+ BulletedListPlugin,
+ NumberedListPlugin,
+ TaskListPlugin,
+ ListItemPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+使用自定义组件和键盘快捷键配置插件。
+
+```tsx
+import { ListPlugin, BulletedListPlugin, NumberedListPlugin, TaskListPlugin, ListItemPlugin } from '@platejs/list-classic/react';
+import { createPlateEditor } from 'platejs/react';
+import { BulletedListElement, NumberedListElement, TaskListElement } from '@/components/ui/list-classic-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ListPlugin,
+ BulletedListPlugin.configure({
+ node: { component: BulletedListElement },
+ shortcuts: { toggle: { keys: 'mod+alt+5' } },
+ }),
+ NumberedListPlugin.configure({
+ node: { component: NumberedListElement },
+ shortcuts: { toggle: { keys: 'mod+alt+6' } },
+ }),
+ TaskListPlugin.configure({
+ node: { component: TaskListElement },
+ shortcuts: { toggle: { keys: 'mod+alt+7' } },
+ }),
+ ListItemPlugin,
+ ],
+});
+```
+
+- `node.component`:分配 [`BulletedListElement`](/docs/components/list-classic-node)、[`NumberedListElement`](/docs/components/list-classic-node) 和 [`TaskListElement`](/docs/components/list-classic-node) 来渲染列表元素。
+- `shortcuts.toggle`:定义键盘[快捷键](/docs/plugin-shortcuts)来切换列表类型(`mod+alt+5` 用于项目符号,`mod+alt+6` 用于编号,`mod+alt+7` 用于任务列表)。
+
+### 添加工具栏按钮
+
+您可以将 [`ListToolbarButton`](/docs/components/list-classic-toolbar-button) 添加到您的[工具栏](/docs/toolbar)中以创建和管理列表。
+
+### 转换为工具栏按钮
+
+使用 `ListPlugin` 时,请使用 [`turn-into-toolbar-classic-button`](/docs/components/turn-into-toolbar-classic-button),它包含所有列表类型(项目符号、编号和任务列表)。
+
+### 插入工具栏按钮
+
+使用 `ListPlugin` 时,请使用 [`insert-toolbar-classic-button`](/docs/components/insert-toolbar-classic-button),它包含所有列表类型(项目符号、编号和任务列表)。
+
+
+
+## 插件
+
+### `ListPlugin`
+
+
+包含以下元素插件:
+- `BulletedListPlugin`
+- `NumberedListPlugin`
+- `TaskListPlugin`
+- `ListItemPlugin`
+- `ListItemContentPlugin`
+
+
+
+ 列表项的有效子节点类型(除了 `p` 和 `ul`)。
+
+
+ 是否应该用 Shift+Tab 重置列表缩进级别。
+
+
+ 是否在行尾插入换行符后继承上方节点的选中状态。仅适用于任务列表。
+ - **默认:** `false`
+
+
+ 是否在行首插入换行符后继承下方节点的选中状态。仅适用于任务列表。
+ - **默认:** `false`
+
+
+
+
+### `BulletedListPlugin`
+
+用于无序(项目符号)列表的插件。
+
+### `NumberedListPlugin`
+
+用于有序(编号)列表的插件。
+
+### `TaskListPlugin`
+
+用于带复选框的任务列表的插件。
+
+### `ListItemPlugin`
+
+用于列表项的插件。
+
+### `ListItemContentPlugin`
+
+用于列表项内容的插件。
+
+## 转换
+
+### `tf.ul.toggle()`
+
+切换项目符号列表(ul)。
+
+示例快捷键:`Mod+Alt+5`
+
+### `tf.ol.toggle()`
+
+切换编号列表(ol)。
+
+示例快捷键:`Mod+Alt+6`
+
+### `tf.taskList.toggle()`
+
+切换带复选框的任务列表。
+
+示例快捷键:`Mod+Alt+7`
+
+## API
+
+### `getHighestEmptyList`
+
+查找可以删除的最高端列表。列表的路径应该与 `diffListPath` 不同。如果最高端列表有 2 个或更多项目,则返回 `liPath`。它会向上遍历父列表,直到:
+- 列表的项目少于 2 个
+- 其路径不等于 `diffListPath`
+
+
+
+
+ 列表项的路径。
+
+
+ 不同列表的路径。
+
+
+
+
+ 最高可删除端列表的路径。
+
+
+
+### `getListItemEntry`
+
+返回给定路径的最近 `li` 和 `ul`/`ol` 包装节点条目(`默认 = 选择`)。
+
+
+
+
+ 获取条目的位置。
+ - **默认:** `editor.selection`
+
+
+
+
+ 最近的 `li` 和 `ul`/`ol` 包装节点条目。
+
+
+
+### `getListRoot`
+
+向上搜索根列表元素。
+
+
+
+
+ 开始搜索的位置。
+ - **默认:** `editor.selection`
+
+
+
+
+ 根列表元素条目。
+
+
+
+### `getListTypes`
+
+获取支持的列表类型数组。
+
+
+
+ 支持的列表类型数组。
+
+
+
+### `getTaskListProps`
+
+根据提供的类型返回任务列表项的属性。
+
+
+
+
+ 要检查的列表类型。
+
+
+ 任务列表选项。
+
+
+
+ 任务项是否应被选中。
+ - **默认:** `false`
+
+
+
+
+
+ 如果类型是任务列表,则返回带有 `checked` 属性的对象,否则返回 `undefined`。
+
+
+
+### `moveListSiblingsAfterCursor`
+
+将光标后的列表兄弟项移动到指定路径。
+
+
+
+
+ 光标位置路径。
+
+
+ 目标路径。
+
+
+
+
+ 移动的兄弟项数量。
+
+
+
+### `removeFirstListItem`
+
+如果未嵌套且不是第一个子项,则删除第一个列表项。
+
+
+
+
+ 包含项的列表条目。
+
+
+ 要删除的列表项。
+
+
+
+
+ 项是否被删除。
+
+
+
+### `removeListItem`
+
+删除列表项,如果有子列表则移动到父级。
+
+
+
+
+ 包含项的列表条目。
+
+
+ 要删除的列表项。
+
+
+ 是否反转子列表项。
+ - **默认:** `true`
+
+
+
+
+ 项是否被删除。
+
+
+
+### `someList`
+
+检查选择是否在特定类型的列表内。
+
+
+
+
+ 要检查的列表类型。
+
+
+
+
+ 选择是否在指定的列表类型中。
+
+
+
+### `unindentListItems`
+
+减少列表项的缩进级别。
+
+
+
+ 取消缩进的目标路径。
+
+
+
+### `unwrapList`
+
+移除选中项的列表格式。
+
+
+
+
+ 取消包装的目标路径。
+
+
+
+
+## Hooks
+
+### `useListToolbarButton`
+
+列表工具栏按钮的行为钩子。
+
+
+
+
+ 按钮按下状态。
+
+
+ 列表节点类型。
+ - **默认:** `BulletedListPlugin.key`
+
+
+
+
+
+ 工具栏按钮的属性。
+
+
+
+ 按钮按下状态。
+
+
+ 切换列表并聚焦编辑器的处理函数。
+
+
+
+
+
+### `useTodoListElementState`
+
+管理任务列表项状态的钩子。
+
+
+
+
+ 任务列表项元素。
+
+
+
+
+
+ 任务项是否被选中。
+
+
+ 编辑器是否处于只读模式。
+
+
+ 切换选中状态的处理函数。
+
+
+
+
+### `useTodoListElement`
+
+获取任务列表项复选框属性的钩子。
+
+
+
+
+ 任务项是否被选中。
+
+
+ 编辑器是否处于只读模式。
+
+
+ 切换选中状态的处理函数。
+
+
+
+
+
+ 要扩展到复选框组件的属性。
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/list-classic.mdx b/apps/www/content/docs/(plugins)/(elements)/list-classic.mdx
new file mode 100644
index 0000000000..60997904b4
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/list-classic.mdx
@@ -0,0 +1,490 @@
+---
+title: List Classic
+docs:
+ - route: /docs/components/list-classic-node
+ title: List Nodes
+ - route: /docs/components/list-classic-toolbar-button
+ title: List Toolbar Button
+---
+
+
+
+
+ Plate offers two approaches for implementing lists:
+
+ 1. **This List Classic plugin** - Traditional HTML-spec lists with strict nesting rules:
+ - Follows standard HTML list structure (`ul`/`ol` > `li`)
+ - Maintains consistent list hierarchy
+ - Best for content that may be exported to HTML/markdown
+ - Highest complexity
+
+ 2. The [**List plugin**](/docs/list) - Flexible indentation-based lists:
+ - More like Word/Google Docs behavior
+ - Any block can be indented to create list-like structures
+ - Used in the [AI editor](/editors#editor-ai)
+ - Supports nested todo lists
+ - Better for free-form content organization
+
+ Choose based on your needs:
+ - Use the **List Classic plugin** when you need standard HTML list compatibility
+ - Use the **List plugin** when you want more flexible indentation behavior
+
+
+
+
+
+
+## Features
+
+- **HTML-compliant lists**:
+ - Standard `ul`/`ol` > `li` structure
+ - Proper nesting and hierarchy
+ - SEO-friendly markup
+ - Clean HTML/markdown export
+
+- **List types**:
+ - Unordered (bulleted) lists
+ - Ordered (numbered) lists
+ - Task lists with checkboxes
+ - Nested sublists
+
+- **Drag & drop**:
+ - Currently supports root-level list items only
+ - Sibling and nested items drag & drop not yet supported
+
+- **Shortcuts**:
+ - Combined with the autoformat plugin, use markdown shortcuts (**`-`**, **`*`**, **`1.`**, **`[ ]`**) to create lists
+ - Tab/Shift+Tab for indentation control
+
+- **Limitations (use the [List plugin](/docs/list) for these features)**:
+ - Drag & drop only supports root-level lists
+ - List items cannot be aligned
+
+For a more flexible, Word-like approach, see the [List plugin](/docs/list).
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add list functionality is with the `ListKit`, which includes pre-configured list plugins with [Plate UI](/docs/installation/plate-ui) components and keyboard shortcuts.
+
+
+
+- [`BulletedListElement`](/docs/components/list-classic-node): Renders unordered list elements.
+- [`NumberedListElement`](/docs/components/list-classic-node): Renders ordered list elements.
+- [`TaskListElement`](/docs/components/list-classic-node): Renders task list elements with checkboxes.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { ListKit } from '@/components/editor/plugins/list-classic-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...ListKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/list-classic
+```
+
+### Add Plugins
+
+Include the list plugins in your Plate plugins array when creating the editor.
+
+```tsx
+import { ListPlugin, BulletedListPlugin, NumberedListPlugin, TaskListPlugin, ListItemPlugin } from '@platejs/list-classic/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ListPlugin,
+ BulletedListPlugin,
+ NumberedListPlugin,
+ TaskListPlugin,
+ ListItemPlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+Configure the plugins with custom components and keyboard shortcuts.
+
+```tsx
+import { ListPlugin, BulletedListPlugin, NumberedListPlugin, TaskListPlugin, ListItemPlugin } from '@platejs/list-classic/react';
+import { createPlateEditor } from 'platejs/react';
+import { BulletedListElement, NumberedListElement, TaskListElement } from '@/components/ui/list-classic-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ListPlugin,
+ BulletedListPlugin.configure({
+ node: { component: BulletedListElement },
+ shortcuts: { toggle: { keys: 'mod+alt+5' } },
+ }),
+ NumberedListPlugin.configure({
+ node: { component: NumberedListElement },
+ shortcuts: { toggle: { keys: 'mod+alt+6' } },
+ }),
+ TaskListPlugin.configure({
+ node: { component: TaskListElement },
+ shortcuts: { toggle: { keys: 'mod+alt+7' } },
+ }),
+ ListItemPlugin,
+ ],
+});
+```
+
+- `node.component`: Assigns [`BulletedListElement`](/docs/components/list-classic-node), [`NumberedListElement`](/docs/components/list-classic-node), and [`TaskListElement`](/docs/components/list-classic-node) to render list elements.
+- `shortcuts.toggle`: Defines keyboard [shortcuts](/docs/plugin-shortcuts) to toggle list types (`mod+alt+5` for bulleted, `mod+alt+6` for numbered, `mod+alt+7` for task lists).
+
+### Add Toolbar Button
+
+You can add [`ListToolbarButton`](/docs/components/list-classic-toolbar-button) to your [Toolbar](/docs/toolbar) to create and manage lists.
+
+### Turn Into Toolbar Button
+
+When using the `ListPlugin`, use the [`turn-into-toolbar-classic-button`](/docs/components/turn-into-toolbar-classic-button) which includes all list types (bulleted, numbered, and task lists).
+
+### Insert Toolbar Button
+
+When using the `ListPlugin`, use the [`insert-toolbar-classic-button`](/docs/components/insert-toolbar-classic-button) which includes all list types (bulleted, numbered, and task lists).
+
+
+
+## Plugins
+
+### `ListPlugin`
+
+
+Contains the following element plugins:
+- `BulletedListPlugin`
+- `NumberedListPlugin`
+- `TaskListPlugin`
+- `ListItemPlugin`
+- `ListItemContentPlugin`
+
+
+
+ Valid child node types for list items (besides `p` and `ul`).
+
+
+ Whether Shift+Tab should reset list indent level.
+
+
+ Whether to inherit the checked state of above node after insert break at the end. Only applies to task lists.
+ - **Default:** `false`
+
+
+ Whether to inherit the checked state of below node after insert break at the start. Only applies to task lists.
+ - **Default:** `false`
+
+
+
+
+### `BulletedListPlugin`
+
+Plugin for unordered (bulleted) lists.
+
+### `NumberedListPlugin`
+
+Plugin for ordered (numbered) lists.
+
+### `TaskListPlugin`
+
+Plugin for task lists with checkboxes.
+
+### `ListItemPlugin`
+
+Plugin for list items.
+
+### `ListItemContentPlugin`
+
+Plugin for list item content.
+
+## Transforms
+
+### `tf.ul.toggle()`
+
+Toggles a bulleted list (ul).
+
+Example Shortcut: `Mod+Alt+5`
+
+### `tf.ol.toggle()`
+
+Toggles an ordered list (ol).
+
+Example Shortcut: `Mod+Alt+6`
+
+### `tf.taskList.toggle()`
+
+Toggles a task list with checkboxes.
+
+Example Shortcut: `Mod+Alt+7`
+
+## API
+
+### `getHighestEmptyList`
+
+Finds the highest end list that can be deleted. The path of the list should be different from `diffListPath`. If the highest end list has 2 or more items, returns `liPath`. It traverses up the parent lists until:
+- The list has less than 2 items
+- Its path is not equal to `diffListPath`
+
+
+
+
+ Path of list item.
+
+
+ Path of different list.
+
+
+
+
+ Path of highest deletable end list.
+
+
+
+### `getListItemEntry`
+
+Returns the nearest `li` and `ul`/`ol` wrapping node entries for a given path (`default = selection`).
+
+
+
+
+ Location to get entries from.
+ - **Default:** `editor.selection`
+
+
+
+
+ Nearest `li` and `ul`/`ol` wrapping node entries.
+
+
+
+### `getListRoot`
+
+Searches upward for root list element.
+
+
+
+
+ Location to start search from.
+ - **Default:** `editor.selection`
+
+
+
+
+ Root list element entry.
+
+
+
+### `getListTypes`
+
+Gets array of supported list types.
+
+
+
+ Array of supported list types.
+
+
+
+### `moveListSiblingsAfterCursor`
+
+Moves list siblings after cursor to specified path.
+
+
+
+
+ Cursor position path.
+
+
+ Destination path.
+
+
+
+
+ Number of siblings moved.
+
+
+
+### `removeFirstListItem`
+
+Removes first list item if not nested and not first child.
+
+
+
+
+ List entry containing item.
+
+
+ List item to remove.
+
+
+
+
+ Whether item was removed.
+
+
+
+### `removeListItem`
+
+Removes list item and moves sublist to parent if any.
+
+
+
+
+ List entry containing item.
+
+
+ List item to remove.
+
+
+ Whether to reverse sublist items.
+ - **Default:** `true`
+
+
+
+
+ Whether item was removed.
+
+
+
+### `someList`
+
+Checks if selection is inside list of specific type.
+
+
+
+
+ List type to check.
+
+
+
+
+ Whether selection is in specified list type.
+
+
+
+### `unindentListItems`
+
+Decreases indentation level of list items.
+
+
+
+ Target path for unindenting.
+
+
+
+### `unwrapList`
+
+Removes list formatting from selected items.
+
+
+
+
+ Target path for unwrapping.
+
+
+
+
+## Hooks
+
+### `useListToolbarButton`
+
+A behavior hook for a list toolbar button.
+
+
+
+
+ Button pressed state.
+
+
+ List node type.
+ - **Default:** `BulletedListPlugin.key`
+
+
+
+
+
+ Props for toolbar button.
+
+
+
+ Button pressed state.
+
+
+ Handler to toggle list and focus editor.
+
+
+
+
+
+### `useTodoListElementState`
+
+Hook to manage task list item state.
+
+
+
+
+ Task list item element.
+
+
+
+
+
+ Whether the task item is checked.
+
+
+ Whether the editor is in read-only mode.
+
+
+ Handler to toggle the checked state.
+
+
+
+
+### `useTodoListElement`
+
+Hook to get props for task list item checkbox.
+
+
+
+
+ Whether the task item is checked.
+
+
+ Whether the editor is in read-only mode.
+
+
+ Handler to toggle the checked state.
+
+
+
+
+
+ Props to be spread on the checkbox component.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/media.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/media.cn.mdx
new file mode 100644
index 0000000000..894cc58194
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/media.cn.mdx
@@ -0,0 +1,949 @@
+---
+title: 媒体组件
+docs:
+ - route: /docs/components/media-image-node
+ title: 图片元素
+ - route: /docs/components/media-video-node
+ title: 视频元素
+ - route: /docs/components/media-audio-node
+ title: 音频元素
+ - route: /docs/components/media-file-node
+ title: 文件元素
+ - route: /docs/components/media-embed-node
+ title: 媒体嵌入元素
+ - route: /docs/components/media-toolbar
+ title: 媒体弹出框
+ - route: /docs/components/media-placeholder-node
+ title: 媒体占位元素
+ - route: /docs/components/media-upload-toast
+ title: 媒体上传提示
+ - route: /docs/components/media-toolbar-button
+ title: 媒体工具栏按钮
+ - route: https://pro.platejs.org/docs/examples/media
+ title: 上传功能
+ - route: https://pro.platejs.org/docs/components/media-toolbar
+ title: 媒体工具栏
+---
+
+
+
+
+
+## 功能特性
+
+### 媒体支持
+- **文件类型**:
+ - 图片
+ - 视频
+ - 音频
+ - 其他 (PDF, Word等)
+- **视频平台**:
+ - 本地视频文件
+ - YouTube, Vimeo, Dailymotion, Youku, Coub
+- **嵌入支持**:
+ - 推特推文
+
+### 媒体功能
+- 可编辑的标题说明
+- 可调整大小的元素
+
+### 上传功能
+- **多种上传方式**:
+ - 工具栏按钮文件选择器
+ - 从文件系统拖放
+ - 从剪贴板粘贴(图片)
+ - 外部媒体URL嵌入
+- **上传体验**:
+ - 实时进度跟踪
+ - 上传过程中预览
+ - 上传或嵌入完成后自动将占位符转换为相应的媒体元素(图片/视频/音频/文件)
+ - 错误处理
+ - 文件大小验证
+ - 类型验证
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的媒体支持方式是使用`MediaKit`,它包含预配置的`ImagePlugin`、`VideoPlugin`、`AudioPlugin`、`FilePlugin`、`MediaEmbedPlugin`、`PlaceholderPlugin`和`CaptionPlugin`及其[Plate UI](/docs/installation/plate-ui)组件。
+
+
+
+- [`ImageElement`](/docs/components/media-image-node): 渲染图片元素
+- [`VideoElement`](/docs/components/media-video-node): 渲染视频元素
+- [`AudioElement`](/docs/components/media-audio-node): 渲染音频元素
+- [`FileElement`](/docs/components/media-file-node): 渲染文件元素
+- [`MediaEmbedElement`](/docs/components/media-embed-node): 渲染嵌入媒体
+- [`PlaceholderElement`](/docs/components/media-placeholder-node): 渲染上传占位符
+- [`MediaUploadToast`](/docs/components/media-upload-toast): 显示上传进度通知
+- [`MediaPreviewDialog`](/docs/components/media-preview-dialog): 提供媒体预览功能
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { MediaKit } from '@/components/editor/plugins/media-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...MediaKit,
+ ],
+});
+```
+
+### 添加API路由
+
+
+
+### 环境配置
+
+从[UploadThing](https://uploadthing.com/dashboard/settings)获取密钥并添加到`.env`:
+
+```bash title=".env"
+UPLOADTHING_TOKEN=xxx
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/media
+```
+
+### 添加插件
+
+在创建编辑器时将媒体插件包含到Plate插件数组中。
+
+```tsx
+import {
+ AudioPlugin,
+ FilePlugin,
+ ImagePlugin,
+ MediaEmbedPlugin,
+ PlaceholderPlugin,
+ VideoPlugin,
+} from '@platejs/media/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ImagePlugin,
+ VideoPlugin,
+ AudioPlugin,
+ FilePlugin,
+ MediaEmbedPlugin,
+ PlaceholderPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+使用自定义组件和上传设置配置插件。
+
+```tsx
+import {
+ AudioPlugin,
+ FilePlugin,
+ ImagePlugin,
+ MediaEmbedPlugin,
+ PlaceholderPlugin,
+ VideoPlugin,
+} from '@platejs/media/react';
+import { KEYS } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+import {
+ AudioElement,
+ FileElement,
+ ImageElement,
+ MediaEmbedElement,
+ PlaceholderElement,
+ VideoElement
+} from '@/components/ui/media-nodes';
+import { MediaUploadToast } from '@/components/ui/media-upload-toast';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ImagePlugin.withComponent(ImageElement),
+ VideoPlugin.withComponent(VideoElement),
+ AudioPlugin.withComponent(AudioElement),
+ FilePlugin.withComponent(FileElement),
+ MediaEmbedPlugin.withComponent(MediaEmbedElement),
+ PlaceholderPlugin.configure({
+ options: { disableEmptyPlaceholder: true },
+ render: { afterEditable: MediaUploadToast, node: PlaceholderElement },
+ }),
+ ],
+});
+```
+
+- `withComponent`: 为每种媒体类型分配自定义渲染组件
+- `options.disableEmptyPlaceholder`: 无文件上传时禁止显示占位符
+- `render.afterEditable`: 在编辑器外部渲染上传进度提示
+
+### 标题支持
+
+要启用媒体标题,添加[Caption Plugin](/docs/caption):
+
+```tsx
+import { CaptionPlugin } from '@platejs/caption/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ // ...媒体插件
+ CaptionPlugin.configure({
+ options: {
+ query: {
+ allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],
+ },
+ },
+ }),
+ ],
+});
+```
+
+### 自定义上传实现
+
+对于自定义上传实现,创建符合此接口的上传钩子:
+
+```ts
+interface UseUploadFileProps {
+ onUploadComplete?: (file: UploadedFile) => void;
+ onUploadError?: (error: unknown) => void;
+ headers?: Record;
+ onUploadBegin?: (fileName: string) => void;
+ onUploadProgress?: (progress: { progress: number }) => void;
+ skipPolling?: boolean;
+}
+
+interface UploadedFile {
+ key: string; // 唯一标识符
+ url: string; // 上传文件的公开URL
+ name: string; // 原始文件名
+ size: number; // 文件大小(字节)
+ type: string; // MIME类型
+}
+```
+
+使用S3预签名URL的示例实现:
+
+```ts
+export function useUploadFile({
+ onUploadComplete,
+ onUploadError,
+ onUploadProgress
+}: UseUploadFileProps = {}) {
+ const [uploadedFile, setUploadedFile] = useState();
+ const [uploadingFile, setUploadingFile] = useState();
+ const [progress, setProgress] = useState(0);
+ const [isUploading, setIsUploading] = useState(false);
+
+ async function uploadFile(file: File) {
+ setIsUploading(true);
+ setUploadingFile(file);
+
+ try {
+ // 从后端获取预签名URL和最终URL
+ const { presignedUrl, fileUrl, fileKey } = await fetch('/api/upload', {
+ method: 'POST',
+ body: JSON.stringify({
+ filename: file.name,
+ contentType: file.type,
+ }),
+ }).then(r => r.json());
+
+ // 使用预签名URL上传到S3
+ await axios.put(presignedUrl, file, {
+ headers: { 'Content-Type': file.type },
+ onUploadProgress: (progressEvent) => {
+ const progress = (progressEvent.loaded / progressEvent.total) * 100;
+ setProgress(progress);
+ onUploadProgress?.({ progress });
+ },
+ });
+
+ const uploadedFile = {
+ key: fileKey,
+ url: fileUrl,
+ name: file.name,
+ size: file.size,
+ type: file.type,
+ };
+
+ setUploadedFile(uploadedFile);
+ onUploadComplete?.(uploadedFile);
+
+ return uploadedFile;
+ } catch (error) {
+ onUploadError?.(error);
+ throw error;
+ } finally {
+ setProgress(0);
+ setIsUploading(false);
+ setUploadingFile(undefined);
+ }
+ }
+
+ return {
+ isUploading,
+ progress,
+ uploadFile,
+ uploadedFile,
+ uploadingFile,
+ };
+}
+```
+
+然后将你的自定义上传钩子与媒体组件集成:
+
+```tsx
+import { useUploadFile } from '@/hooks/use-upload-file'; // 你的自定义钩子
+
+// 在你的PlaceholderElement组件中
+export function PlaceholderElement({ className, children, element, ...props }) {
+ const { uploadFile, isUploading, progress } = useUploadFile({
+ onUploadComplete: (uploadedFile) => {
+ // 将占位符替换为实际媒体元素
+ const { url, type } = uploadedFile;
+
+ // 将占位符转换为适当的媒体类型
+ editor.tf.replace.placeholder({
+ id: element.id,
+ url,
+ type: getMediaType(type), // image, video, audio, file
+ });
+ },
+ onUploadError: (error) => {
+ console.error('上传失败:', error);
+ // 处理上传错误,可能显示提示
+ },
+ });
+
+ // 当文件被拖放或选择时使用uploadFile
+ // 这与PlaceholderPlugin的文件处理集成
+}
+```
+
+### 添加工具栏按钮
+
+你可以将[`MediaToolbarButton`](/docs/components/media-toolbar-button)添加到[Toolbar](/docs/toolbar)以上传和插入媒体。
+
+### 插入工具栏按钮
+
+你可以将这些项添加到[插入工具栏按钮](/docs/toolbar#insert-toolbar-button)来插入媒体元素:
+
+```tsx
+{
+ icon: ,
+ label: '图片',
+ value: KEYS.img,
+}
+```
+
+
+
+## Plate Plus
+
+
+
+## 插件
+
+### `ImagePlugin`
+
+用于void图片元素的插件。
+
+
+
+
+ 上传图片到服务器的函数。接收:
+ - 来自`FileReader.readAsDataURL`的数据URL(字符串)
+ - 来自剪贴板数据的ArrayBuffer
+ 返回:
+ - 上传图片的URL字符串
+ - 如果不需要上传则返回原始数据URL/ArrayBuffer
+ - **默认:** 返回原始输入
+
+
+ 禁用数据插入时的文件上传。
+ - **默认:** `false`
+
+
+ 禁用数据插入时的URL嵌入。
+ - **默认:** `false`
+
+
+ 检查文本字符串是否为URL的函数。
+
+
+ 转换URL的函数。
+
+
+
+
+### `VideoPlugin`
+
+用于void视频元素的插件。扩展`MediaPluginOptions`。
+
+### `AudioPlugin`
+
+用于void音频元素的插件。扩展`MediaPluginOptions`。
+
+### `FilePlugin`
+
+用于void文件元素的插件。扩展`MediaPluginOptions`。
+
+### `MediaEmbedPlugin`
+
+用于void媒体嵌入元素的插件。扩展`MediaPluginOptions`。
+
+### `PlaceholderPlugin`
+
+管理上传过程中媒体占位符的插件。处理文件上传、拖放和剪贴板粘贴事件。
+
+
+
+
+不同文件类型的配置。默认配置:
+```ts
+{
+ audio: {
+ maxFileCount: 1,
+ maxFileSize: '8MB',
+ mediaType: KEYS.audio,
+ minFileCount: 1,
+ },
+ blob: {
+ maxFileCount: 1,
+ maxFileSize: '8MB',
+ mediaType: KEYS.file,
+ minFileCount: 1,
+ },
+ image: {
+ maxFileCount: 3,
+ maxFileSize: '4MB',
+ mediaType: KEYS.image,
+ minFileCount: 1,
+ },
+ pdf: {
+ maxFileCount: 1,
+ maxFileSize: '4MB',
+ mediaType: KEYS.file,
+ minFileCount: 1,
+ },
+ text: {
+ maxFileCount: 1,
+ maxFileSize: '64KB',
+ mediaType: KEYS.file,
+ minFileCount: 1,
+ },
+ video: {
+ maxFileCount: 1,
+ maxFileSize: '16MB',
+ mediaType: KEYS.video,
+ minFileCount: 1,
+ },
+}
+```
+支持的文件类型: `'image' | 'video' | 'audio' | 'pdf' | 'text' | 'blob'`
+
+
+ 此配置对应的媒体插件键: `'audio' | 'file' | 'image' | 'video'`
+
+
+ 此类型文件可上传的最大数量。
+
+
+ 此类型文件的最大文件大小。格式: `${1|2|4|8|16|32|64|128|256|512|1024}${B|KB|MB|GB}`
+
+
+ 此类型文件必须上传的最小数量。
+
+
+
+
+无文件上传时禁用空占位符。
+- **默认:** `false`
+
+
+禁用拖放文件上传功能。
+- **默认:** `false`
+
+
+如果`uploadConfig`未指定,可一次上传的最大文件数。
+- **默认:** `5`
+
+
+允许上传多个相同类型的文件。
+- **默认:** `true`
+
+
+
+
+## API
+
+### `api.placeholder.addUploadingFile`
+
+跟踪当前正在上传的文件。
+
+
+
+
+ 占位符元素的唯一标识符。
+
+
+ 正在上传的文件。
+
+
+
+
+### `api.placeholder.getUploadingFile`
+
+获取当前正在上传的文件。
+
+
+
+
+ 占位符元素的唯一标识符。
+
+
+
+
+
+ 如果找到则返回上传文件,否则返回undefined。
+
+
+
+
+### `api.placeholder.removeUploadingFile`
+
+上传完成或失败后从上传跟踪状态中移除文件。
+
+
+
+
+ 要移除的占位符元素的唯一标识符。
+
+
+
+
+## 转换方法
+
+### `tf.insert.media`
+
+使用上传占位符将媒体文件插入编辑器。
+
+
+
+
+ 要上传的文件。根据配置的文件类型和限制进行验证。
+
+
+ 插入节点的转换选项。
+
+
+
+
+
+ 插入媒体的位置。默认为当前选区。
+
+
+ 是否在媒体后插入新块。
+ - **默认:** `true`
+
+
+
+
+根据配置的限制(大小、数量、类型)验证文件,为每个文件创建占位符元素,处理多个文件顺序上传,维护撤销/重做操作的上传历史记录,如果验证失败则触发错误处理。
+
+错误代码:
+```ts
+enum UploadErrorCode {
+ INVALID_FILE_TYPE = 400,
+ TOO_MANY_FILES = 402,
+ INVALID_FILE_SIZE = 403,
+ TOO_LESS_FILES = 405,
+ TOO_LARGE = 413,
+}
+```
+
+### `tf.insert.imagePlaceholder`
+
+插入一个在上传完成后转换为图片元素的占位符。
+
+### `tf.insert.videoPlaceholder`
+
+插入一个在上传完成后转换为视频元素的占位符。
+
+### `tf.insert.audioPlaceholder`
+
+插入一个在上传完成后转换为音频元素的占位符。
+
+### `tf.insert.filePlaceholder`
+
+插入一个在上传完成后转换为文件元素的占位符。
+
+### `tf.insert.image`
+
+在编辑器中插入图片元素。
+
+
+
+
+ 图片的 URL 或 ArrayBuffer。
+
+
+ 插入图片元素的额外选项。
+
+
+
+
+
+ 如果为 true,图片将被插入到下一个块中。
+
+
+
+
+### `tf.insert.mediaEmbed`
+
+在当前选区插入媒体嵌入元素。
+
+
+
+
+ 媒体嵌入的 URL。
+ - **默认值:** `''`
+
+
+ 媒体嵌入元素的键。
+ - **默认值:** `KEYS.mediaEmbed`
+
+
+ 插入节点的额外选项。
+
+
+
+
+## Hooks
+
+### `useResizable`
+
+处理媒体元素的可调整大小属性。
+
+
+
+
+ 可调整大小元素内容的对齐方式。
+
+
+ 可调整大小元素可以调整到的最小宽度。
+
+
+ 可调整大小元素可以调整到的最大宽度。
+
+
+ 调整大小时设置节点宽度的函数。
+
+
+ 直接设置可调整大小元素宽度的函数。
+
+
+ 可调整大小元素的当前宽度(百分比、'auto' 或像素)。
+
+
+
+
+
+ 最外层包装 div 的 React 引用。
+
+
+ 包装 div 的 CSS 样式。
+
+
+ 可调整大小元素的 CSS 样式。
+
+
+ 元素调整大小时调用的回调函数。
+
+
+
+
+### `useMediaState`
+
+媒体元素的状态钩子。
+
+
+
+
+ 用于解析媒体元素 URL 的 URL 解析器数组。
+
+ - **`EmbedUrlParser`:** `(url: string) => EmbedUrlData | undefined`
+
+
+
+
+
+ 媒体元素的对齐方式。
+
+
+ 媒体元素是否当前获得焦点。
+
+
+ 媒体元素是否当前被选中。
+
+
+ 编辑器是否处于只读模式。
+
+
+ 媒体元素的解析嵌入数据。
+
+
+ 媒体元素是否为推文。
+
+
+ 媒体元素是否为视频。
+
+
+ 媒体元素是否为 YouTube 视频。
+
+
+
+
+### `useMediaToolbarButton`
+
+媒体工具栏按钮的行为钩子。
+
+
+
+
+ 要插入的媒体节点类型。
+
+
+
+
+
+ 插入媒体节点并使编辑器获得焦点的回调函数。
+
+
+
+
+### `useFloatingMediaEditButton`
+
+处理浮动媒体编辑按钮。
+
+
+
+
+ 处理按钮点击的回调函数。
+
+
+
+
+### `useFloatingMediaUrlInput`
+
+处理媒体元素的 URL 输入字段。
+
+
+
+
+ URL 输入字段的默认值。
+
+
+
+
+
+ 处理输入变化的回调函数。
+
+
+ URL 输入字段是否应在挂载时获得焦点。
+
+
+ URL 输入字段的默认值。
+
+
+
+
+### `useImage`
+
+图片元素的钩子。
+
+
+
+
+ 媒体元素的 URL。
+
+
+ 图片的说明文字。
+
+
+ 图片是否可拖动。
+
+
+
+
+## 工具函数
+
+### `parseMediaUrl`
+
+解析媒体 URL 以进行插件特定的处理。
+
+
+
+
+ 媒体插件的键。
+
+
+ 要解析的媒体 URL。
+
+
+
+
+### `parseVideoUrl`
+
+解析视频 URL 并提取视频 ID 和提供商特定的嵌入 URL。
+
+
+
+
+ 要解析的视频 URL。
+
+
+
+
+ 如果解析成功,返回包含视频 ID 和提供商的对象;如果 URL 无效或不支持,则返回 undefined。
+
+
+
+### `parseTwitterUrl`
+
+解析 Twitter URL 并提取推文 ID。
+
+
+
+
+ Twitter URL。
+
+
+
+
+
+ 如果解析成功,返回包含推文 ID 和提供商的对象。
+ 如果 URL 无效或不匹配任何支持的视频提供商,则返回 undefined。
+
+
+
+
+### `parseIframeUrl`
+
+解析 iframe 嵌入的 URL。
+
+
+
+
+ iframe 的 URL 或嵌入代码。
+
+
+
+
+### `isImageUrl`
+
+检查 URL 是否为有效的图片 URL。
+
+
+
+
+ 要检查的 URL。
+
+
+
+
+ URL 是否为有效的图片 URL。
+
+
+
+### `submitFloatingMedia`
+
+提交浮动媒体元素。
+
+
+
+
+ 要提交的浮动媒体元素。
+
+
+ 媒体插件的键。
+
+
+
+
+### `withImageUpload`
+
+为编辑器实例添加图片上传功能。
+
+
+
+
+ Plate 插件。
+
+
+
+
+### `withImageEmbed`
+
+为编辑器实例添加图片相关功能。
+
+
+
+
+ Plate 插件。
+
+
+
+
+## 类型
+
+### `TMediaElement`
+
+```tsx
+export interface TMediaElement extends TElement {
+ url: string;
+ id?: string;
+ align?: 'center' | 'left' | 'right';
+ isUpload?: boolean;
+ name?: string;
+ placeholderId?: string;
+}
+```
+
+### `TPlaceholderElement`
+
+```tsx
+export interface TPlaceholderElement extends TElement {
+ mediaType: string;
+}
+```
+
+### `EmbedUrlData`
+
+```tsx
+export interface EmbedUrlData {
+ url?: string;
+ provider?: string;
+ id?: string;
+ component?: React.FC;
+}
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/media.mdx b/apps/www/content/docs/(plugins)/(elements)/media.mdx
new file mode 100644
index 0000000000..379999d7d6
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/media.mdx
@@ -0,0 +1,26 @@
+---
+title: Media
+docs:
+ - route: https://pro.platejs.org/docs/examples/media
+ title: Plus
+ - route: /docs/components/media-image-node
+ title: Image Element
+ - route: /docs/components/media-video-node
+ title: Video Element
+ - route: /docs/components/media-audio-node
+ title: Audio Element
+ - route: /docs/components/media-file-node
+ title: File Element
+ - route: /docs/components/media-embed-node
+ title: Media Embed Element
+ - route: /docs/components/media-toolbar
+ title: Media Popover
+ - route: /docs/components/media-placeholder-node
+ title: Media Placeholder Element
+ - route: /docs/components/media-upload-toast
+ title: Media Upload Toast
+ - route: /docs/components/media-toolbar-button
+ title: Media Toolbar Button
+---
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/mention.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/mention.cn.mdx
new file mode 100644
index 0000000000..11517f7903
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/mention.cn.mdx
@@ -0,0 +1,175 @@
+---
+title: 提及功能
+docs:
+ - route: /docs/combobox
+ title: 组合框
+ - route: /docs/components/mention-node
+ title: 提及节点
+---
+
+
+
+
+
+## 功能特性
+
+- 智能自动补全,可用于提及用户、页面或任何引用
+- 通过可配置字符触发(默认:`@`)
+- 键盘导航与选择
+- 可自定义的提及数据与渲染方式
+
+
+
+## 套件使用方式
+
+
+
+### 安装
+
+最快捷的添加提及功能方式是使用 `MentionKit`,它包含预配置的 `MentionPlugin` 和 `MentionInputPlugin` 以及它们的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`MentionElement`](/docs/components/mention-node): 渲染提及元素
+- [`MentionInputElement`](/docs/components/mention-node): 渲染提及输入组合框
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { MentionKit } from '@/components/editor/plugins/mention-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...MentionKit,
+ ],
+});
+```
+
+
+
+## 手动配置方式
+
+
+
+### 安装
+
+```bash
+npm install @platejs/mention
+```
+
+### 添加插件
+
+```tsx
+import { MentionPlugin, MentionInputPlugin } from '@platejs/mention/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ MentionPlugin,
+ MentionInputPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+```tsx
+import { MentionPlugin, MentionInputPlugin } from '@platejs/mention/react';
+import { createPlateEditor } from 'platejs/react';
+import { MentionElement, MentionInputElement } from '@/components/ui/mention-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ MentionPlugin.configure({
+ options: {
+ trigger: '@',
+ triggerPreviousCharPattern: /^$|^[\s"']$/,
+ insertSpaceAfterMention: false,
+ },
+ }).withComponent(MentionElement),
+ MentionInputPlugin.withComponent(MentionInputElement),
+ ],
+});
+```
+
+- `options.trigger`: 触发提及组合框的字符
+- `options.triggerPreviousCharPattern`: 触发字符前一个字符的正则表达式模式。套件使用 `/^$|^[\s"']$/` 允许在行首、空白符后或引号后触发提及
+- `options.insertSpaceAfterMention`: 是否在插入提及后自动添加空格
+- `withComponent`: 分配用于渲染提及和提及输入的UI组件
+
+
+
+## 插件说明
+
+### MentionPlugin
+
+提供提及功能的插件。扩展自 [TriggerComboboxPluginOptions](/docs/combobox#triggercomboboxpluginoptions)。
+
+
+
+
+ 是否在提及后插入空格。
+ - **默认值:** `false`
+
+
+ 触发提及组合框的字符。
+ - **默认值:** `'@'`
+
+
+ 匹配触发字符前一个字符的模式。
+ - **默认值:** `/^\s?$/`
+
+
+ 当触发字符激活时创建输入元素的函数。
+
+
+
+
+### MentionInputPlugin
+
+提供提及输入功能的插件。
+
+## API接口
+
+### api.insert.mention
+
+在当前选区插入提及元素。
+
+
+
+
+
+
+ 触发提及的搜索文本。
+
+
+ 存储在提及元素中的值。
+
+
+ 提及元素的可选键值。
+
+
+
+
+
+
+### getMentionOnSelectItem
+
+获取处理提及组合框项选择的处理器。
+
+
+
+
+ 提及插件的插件键。
+ - **默认值:** `MentionPlugin.key`
+
+
+
+
+ 处理提及项选择的函数。
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/mention.mdx b/apps/www/content/docs/(plugins)/(elements)/mention.mdx
new file mode 100644
index 0000000000..77a10a22b8
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/mention.mdx
@@ -0,0 +1,181 @@
+---
+title: Mention
+docs:
+ - route: https://pro.platejs.org/docs/examples/mention
+ title: Plus
+ - route: /docs/combobox
+ title: Combobox
+ - route: /docs/components/mention-node
+ title: Mention Nodes
+---
+
+
+
+
+
+## Features
+
+- Smart autocompletion for mentioning users, pages, or any reference
+- Triggered by configurable characters (default: `@`)
+- Keyboard navigation and selection
+- Customizable mention data and rendering
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add mentions is with the `MentionKit`, which includes pre-configured `MentionPlugin` and `MentionInputPlugin` along with their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`MentionElement`](/docs/components/mention-node): Renders mention elements
+- [`MentionInputElement`](/docs/components/mention-node): Renders the mention input combobox
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { MentionKit } from '@/components/editor/plugins/mention-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...MentionKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/mention
+```
+
+### Add Plugins
+
+```tsx
+import { MentionPlugin, MentionInputPlugin } from '@platejs/mention/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ MentionPlugin,
+ MentionInputPlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+```tsx
+import { MentionPlugin, MentionInputPlugin } from '@platejs/mention/react';
+import { createPlateEditor } from 'platejs/react';
+import { MentionElement, MentionInputElement } from '@/components/ui/mention-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ MentionPlugin.configure({
+ options: {
+ trigger: '@',
+ triggerPreviousCharPattern: /^$|^[\s"']$/,
+ insertSpaceAfterMention: false,
+ },
+ }).withComponent(MentionElement),
+ MentionInputPlugin.withComponent(MentionInputElement),
+ ],
+});
+```
+
+- `options.trigger`: Character that triggers the mention combobox
+- `options.triggerPreviousCharPattern`: RegExp pattern for the character before trigger. The kit uses `/^$|^[\s"']$/` to allow mentions at start of line, after whitespace, or after quotes
+- `options.insertSpaceAfterMention`: Whether to automatically insert a space after inserting a mention
+- `withComponent`: Assigns the UI components for rendering mentions and mention input
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### MentionPlugin
+
+Plugin for mention functionality. Extends [TriggerComboboxPluginOptions](/docs/combobox#triggercomboboxpluginoptions).
+
+
+
+
+ Whether to insert a space after the mention.
+ - **Default:** `false`
+
+
+ Character that triggers the mention combobox.
+ - **Default:** `'@'`
+
+
+ Pattern to match the character before trigger.
+ - **Default:** `/^\s?$/`
+
+
+ Function to create the input element when trigger is activated.
+
+
+
+
+### MentionInputPlugin
+
+Plugin for mention input functionality.
+
+## API
+
+### api.insert.mention
+
+Inserts a mention element at the current selection.
+
+
+
+
+
+
+ The search text that triggered the mention.
+
+
+ The value to store in the mention element.
+
+
+ Optional key for the mention element.
+
+
+
+
+
+
+### getMentionOnSelectItem
+
+Gets handler for selecting mention combobox item.
+
+
+
+
+ Plugin key for mention plugin.
+ - **Default:** `MentionPlugin.key`
+
+
+
+
+ Handler function for mention item selection.
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/table.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/table.cn.mdx
new file mode 100644
index 0000000000..d4205c6751
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/table.cn.mdx
@@ -0,0 +1,1145 @@
+---
+title: 表格
+docs:
+ - route: /docs/components/table-toolbar-button
+ title: 表格工具栏按钮
+ - route: /docs/components/table-node
+ title: 表格节点
+---
+
+
+
+
+
+## 功能特性
+
+- 支持创建和编辑具有高级行为的表格
+- 箭头键导航(上/下)
+- 网格单元格选择
+- 使用 `Shift+箭头键` 扩展单元格选择范围
+- 单元格复制粘贴功能
+- 行拖拽重新排序
+- 通过拖拽手柄选择整行
+
+
+
+## 套件使用指南
+
+
+
+### 安装
+
+最快捷的表格功能集成方式是使用 `TableKit`,它包含预配置的 `TablePlugin`、`TableRowPlugin`、`TableCellPlugin` 和 `TableCellHeaderPlugin` 及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`TableElement`](/docs/components/table-node): 渲染表格容器
+- [`TableRowElement`](/docs/components/table-node): 渲染表格行
+- [`TableCellElement`](/docs/components/table-node): 渲染表格单元格
+- [`TableCellHeaderElement`](/docs/components/table-node): 渲染表头单元格
+
+### 添加套件
+
+将套件添加到插件列表:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { TableKit } from '@/components/editor/plugins/table-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...TableKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/table
+```
+
+### 添加插件
+
+在创建编辑器时将 `TablePlugin` 加入插件数组:
+
+```tsx
+import { TablePlugin } from '@platejs/table/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ TablePlugin,
+ ],
+});
+```
+
+### 插件配置
+
+使用自定义组件和选项配置表格插件:
+
+```tsx
+import {
+ TableCellHeaderPlugin,
+ TableCellPlugin,
+ TablePlugin,
+ TableRowPlugin,
+} from '@platejs/table/react';
+import { createPlateEditor } from 'platejs/react';
+import {
+ TableCellElement,
+ TableCellHeaderElement,
+ TableElement,
+ TableRowElement,
+} from '@/components/ui/table-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ TablePlugin.configure({
+ node: { component: TableElement },
+ options: {
+ initialTableWidth: 600,
+ disableMerge: false,
+ minColumnWidth: 48,
+ },
+ }),
+ TableRowPlugin.withComponent(TableRowElement),
+ TableCellPlugin.withComponent(TableCellElement),
+ TableCellHeaderPlugin.withComponent(TableCellHeaderElement),
+ ],
+});
+```
+
+- `node.component`: 指定 [`TableElement`](/docs/components/table-node) 渲染表格容器
+- `withComponent`: 为表格行、单元格和表头单元格分配组件
+- `options.initialTableWidth`: 设置新建表格的初始宽度
+- `options.disableMerge`: 禁用单元格合并功能
+- `options.minColumnWidth`: 设置表格列的最小宽度
+
+### 添加工具栏按钮
+
+可以在[工具栏](/docs/toolbar)中添加 [`TableToolbarButton`](/docs/components/table-toolbar-button) 来插入表格。
+
+### 插入工具栏按钮
+
+将此条目添加到[插入工具栏按钮](/docs/toolbar#insert-toolbar-button)以插入表格元素:
+
+```tsx
+{
+ icon: ,
+ label: '表格',
+ value: KEYS.table,
+}
+```
+
+### 禁用合并示例
+
+
+
+
+
+## 插件说明
+
+### TablePlugin
+
+
+
+
+ 禁用单元格合并行为
+
+
+ 禁用插入单元格时的表格扩展
+
+
+ 禁用表格第一列的左侧调整手柄
+
+
+ 禁用当表格只有一列时取消设置第一列宽度。设置为 `true` 可在单列情况下调整表格宽度,保持 `false` 可实现全宽表格
+
+
+ 若定义此值,标准化器会将每个未定义的表格 `colSizes` 设置为该值除以列数(注意:暂不支持合并单元格)
+
+
+ 表格列的最小宽度
+ - **默认值:** `48`
+
+
+
+
+### TableRowPlugin
+
+表格行插件
+
+### TableCellPlugin
+
+表格单元格插件
+
+### TableCellHeaderPlugin
+
+表头单元格插件
+
+## API 参考
+
+### editor.api.create.table
+
+
+
+
+扩展 `GetEmptyRowNodeOptions`
+
+
+
+
+
+设为 `true` 表示表格包含表头行
+
+
+表格行数
+- **默认值:** `0`
+
+
+表格列数
+
+
+
+
+
+表格节点
+
+
+
+
+### editor.api.create.tableCell
+
+创建表格的空单元格节点
+
+
+
+
+设为 `true` 表示创建表头单元格
+
+
+行元素。若未指定 `header`,将根据行的子元素判断是否为表头单元格
+
+
+新单元格节点的子元素
+- **默认值:** `[editor.api.create.block()]`
+
+
+
+
+
+单元格节点
+
+
+
+
+### editor.api.create.tableRow
+
+创建具有指定列数的空行节点
+
+
+
+
+设为 `true` 表示创建表头行
+
+
+行的列数
+- **默认值:** `1`
+
+
+
+
+
+行节点
+
+
+
+
+### editor.api.table.getCellBorders
+
+获取表格单元格的边框样式,处理首行和首列单元格的特殊情况
+
+
+
+
+要获取边框样式的表格单元格元素
+
+
+当单元格边框未定义时使用的默认边框样式
+
+
+ 边框颜色
+ - **默认值:** `'rgb(209 213 219)'`
+
+
+ 边框尺寸
+ - **默认值:** `1`
+
+
+ 边框样式
+ - **默认值:** `'solid'`
+
+
+
+
+
+
+
+ 包含以下属性的对象:
+
+
+ 底部边框样式
+
+
+ 右侧边框样式
+
+
+ 左侧边框样式(仅首列单元格存在)
+
+
+ 顶部边框样式(仅首行单元格存在)
+
+
+
+
+
+
+### editor.api.table.getCellChildren
+
+获取表格单元格的子元素
+
+
+
+
+表格单元格元素
+
+
+
+
+
+表格单元格的子元素
+
+
+
+
+### editor.api.table.getCellSize
+
+获取表格单元格的宽度和最小高度,考虑列跨度和列宽设置
+
+
+
+
+要获取尺寸的表格单元格元素
+
+
+列宽数组。若未提供,将使用表格的覆盖列宽设置
+
+
+
+
+
+单元格总宽度(根据跨列数计算各列宽度之和)
+
+
+单元格最小高度(根据行的尺寸属性确定)
+
+
+
+
+### editor.api.table.getColSpan
+
+获取表格单元格的列跨度
+
+
+
+
+要获取列跨度的表格单元格元素
+
+
+
+
+
+ 单元格跨越的列数
+ - **默认值:** `1`
+
+
+
+
+### editor.api.table.getRowSpan
+
+获取表格单元格的行跨度
+
+
+
+
+ 要获取行跨度的表格单元格元素
+
+
+
+
+
+ 单元格跨越的行数
+ - **默认值:** `1`
+
+
+
+
+### getCellType
+
+获取插件单元格类型
+
+
+
+
+
+ 编辑器表格单元格元素类型数组(td 和 th)
+
+
+
+
+
+### getNextTableCell
+
+获取表格中的下一个单元格
+
+
+
+
+ 当前单元格的 entry
+
+
+ 当前单元格的路径
+
+
+ 当前行的 entry
+
+
+
+
+
+ 下一行单元格的节点 entry,若当前行是最后一行则返回 `undefined`
+
+
+
+
+### getPreviousTableCell
+
+获取表格中的上一个单元格
+
+
+
+
+ 当前单元格的 entry
+
+
+ 当前单元格的路径
+
+
+ 当前行的 entry
+
+
+
+
+
+ 上一行单元格的节点 entry,若当前行是首行则返回 `undefined`
+
+
+
+
+### getTableColumnCount
+
+获取表格列数
+
+
+
+
+ 要获取列数的表格节点
+
+
+
+
+
+表格列数
+
+
+
+
+### getTableColumnIndex
+
+获取单元格在表格中的列索引
+
+
+
+
+ 要获取列索引的单元格节点
+
+
+
+
+
+单元格的列索引
+
+
+
+
+### getTableEntries
+
+根据当前选区或指定位置获取表格、行和单元格的节点 entry
+
+
+
+
+表格单元格所在位置
+- **默认值:** `editor.selection`
+
+
+
+
+
+ 表格节点 entry
+
+
+ 行节点 entry
+
+
+ 单元格节点 entry
+
+
+
+
+### getTableGridAbove
+
+根据指定格式(表格或单元格)获取锚点和焦点位置上方的子表格
+
+
+
+
+要获取的子表格格式
+- **默认值:** `'table'`
+
+
+
+
+ 子表格 entry 数组
+
+
+
+### getTableGridByRange
+
+获取指定范围内两个单元格路径之间的子表格
+
+
+
+
+指定起始和结束单元格路径的范围
+
+
+输出格式
+- **默认值:** `'table'`
+
+
+
+
+ 子表格 entry 数组
+
+
+
+### getTableRowIndex
+
+获取单元格在表格中的行索引
+
+
+
+
+ 要获取行索引的单元格节点
+
+
+
+
+
+单元格的行索引
+
+
+
+
+### getTopTableCell
+
+获取表格中当前单元格上方的单元格
+
+
+
+
+ 当前单元格路径。若未提供,函数将在编辑器中搜索当前单元格
+
+
+
+
+
+单元格节点 entry
+
+
+
+
+### isTableBorderHidden
+
+检查表格单元格或表格本身的指定方向边框是否隐藏
+
+
+
+
+ 要检查的边框方向
+
+
+
+
+
+`true` 表示边框隐藏,`false` 表示可见
+
+
+
+
+## 转换操作
+
+### `tf.insert.table`
+
+在当前选区插入表格(若编辑器中无现有表格)。选中插入表格的起始位置。
+
+
+
+
+扩展 `GetEmptyRowNodeOptions`
+
+
+表格行数
+- **默认值:** `2`
+
+
+表格列数
+- **默认值:** `2`
+
+
+设为 `true` 时表格首行将作为表头行
+
+
+
+
+
+插入表格节点的选项
+
+
+
+
+### `tf.insert.tableColumn`
+
+在表格的当前选区或指定单元格路径处插入一列。
+
+
+
+
+要插入列的单元格的确切路径。这会覆盖 `fromCell` 选项。
+
+
+如果为 true,则在当前列之前插入列,而不是之后。
+
+
+要从中插入列的单元格路径。
+
+
+如果为 true,则插入的列将被视为表头列。
+
+
+如果为 true,则插入后会自动选中该列。
+
+
+
+
+### `tf.insert.tableRow`
+
+在表格的当前选区或指定行路径处插入一行。
+
+
+
+
+要插入行的确切路径。传入表格路径可在表格末尾插入。这将覆盖 `fromRow`。
+
+
+如果为 true,则在当前行之前插入行,而不是之后。
+
+
+要从中插入新行的行路径。
+
+
+如果为 true,则插入的行将被视为表头行。
+
+
+如果为 true,则插入后会自动选中该行。
+
+
+
+
+### `tf.remove.tableColumn`
+
+删除表格中包含选中单元格的列。
+
+### `tf.remove.tableRow`
+
+删除表格中包含选中单元格的行。
+
+### `tf.remove.table`
+
+删除整个表格。
+
+### `tf.table.merge`
+
+将多个选中的单元格合并为一个。
+
+合并后的单元格将:
+- 列跨度等于所选单元格跨越的列数
+- 行跨度等于所选单元格跨越的行数
+- 包含所有合并单元格的组合内容(仅非空单元格)
+- 继承第一个选中单元格的表头状态
+
+### `tf.table.split`
+
+将合并的单元格拆分回单独的单元格。
+
+拆分操作将:
+- 为每个跨越的列和行创建新单元格
+- 从原始合并单元格复制表头状态
+- 将原始单元格的内容放在第一个单元格中
+- 为剩余空间创建空单元格
+
+### `tf.moveSelectionFromCell`
+
+在表格内按单元格单位移动选区。
+
+
+
+
+要从中移动选区的位置。
+
+
+设置为 `true` 将选区移动到上方单元格,`false` 移动到下方单元格。
+
+
+要扩展单元格选区的边缘。
+
+
+设置为 `true` 仅从单个选中单元格移动选区。
+
+
+
+
+### `tf.setBorderSize`
+
+设置表格单元格中指定边框的大小。
+
+
+
+
+边框的大小。
+
+
+设置边框大小的选项。
+
+
+
+
+
+要设置边框大小的单元格位置。
+
+
+要设置大小的边框方向。
+
+- **默认值:** `'all'`
+
+
+
+
+
+### `tf.setTableColSize`
+
+设置表格中特定列的宽度。
+
+
+
+
+要设置宽度的列索引。
+
+
+所需的列宽度。
+
+
+查找表格节点的其他选项。
+
+
+
+
+### `tf.setTableMarginLeft`
+
+设置表格的左边距。
+
+
+
+
+包含所需左边距值的对象。
+
+
+查找表格节点的其他选项。
+
+
+
+
+### `tf.setTableRowSize`
+
+设置表格行的大小(高度)。
+
+
+
+
+要设置大小的行索引。
+
+
+所需的行高度。
+
+
+查找表格节点的其他选项。
+
+
+
+
+## 插件扩展
+
+### `onKeyDownTable`
+
+处理表格的键盘事件。
+
+
+
+
+ Plate 插件。
+
+
+
+
+
+ 键盘处理程序返回类型。
+
+
+
+
+### `withDeleteTable`
+
+防止删除表格中的单元格。
+
+### `withGetFragmentTable`
+
+如果选区在表格内,它会获取选区上方的子表格作为片段。这在复制和粘贴表格单元格时很有用。
+
+### `withInsertFragmentTable`
+
+如果插入表格:
+
+- 如果选区锚点上方的块是表格,则用插入的表格替换上方的每个单元格,直到超出边界。选中插入的单元格。
+- 如果锚点上方没有表格,检查选区是否在表格内。如果是,找到锚点位置的单元格并用插入的片段替换其子元素。
+
+### `withInsertTextTable`
+
+如果选区已展开:
+
+- 检查选区是否在表格内。如果是,将选区折叠到焦点边缘。
+
+### `withNormalizeTable`
+
+通过执行以下操作规范化表格结构:
+
+- 如果单元格子元素是文本,则将其包装在段落中。
+- 解包不是有效表格元素的节点。
+- 如果指定了初始列大小,则为表格设置初始列大小。
+
+### `withSelectionTable`
+
+通过执行以下操作处理表格选区:
+
+- 当锚点在表格内且焦点在表格前后的块中时,调整选区的焦点。
+- 当焦点在表格内且锚点在表格前后的块中时,调整选区的焦点。
+- 如果前一个和新选区在不同的单元格中,则覆盖从单元格的选区。
+
+### `withSetFragmentDataTable`
+
+通过执行以下操作处理复制或剪切表格数据时设置剪贴板数据:
+
+- 检查是否存在表格条目和选中的单元格条目。
+- 通过复制单元格内容而不是表格结构来处理单单元格复制或剪切操作。
+- 创建包含选中单元格内容的表格结构。
+- 将文本、HTML、CSV、TSV 和 Slate 片段数据设置到剪贴板。
+
+### `withTable`
+
+通过应用以下高阶函数增强编辑器实例的表格相关功能:
+
+- `withNormalizeTable`:规范化表格结构和内容。
+- `withDeleteTable`:防止删除表格中的单元格。
+- `withGetFragmentTable`:处理复制或剪切表格单元格时获取片段数据。
+- `withInsertFragmentTable`:处理插入表格片段。
+- `withInsertTextTable`:处理在表格中插入文本。
+- `withSelectionTable`:处理调整表格内的选区。
+- `withSetFragmentDataTable`:处理复制或剪切表格数据时设置片段数据。
+
+## Hooks
+
+### `useTableCellElementResizable`
+
+提供表格单元格元素调整大小功能的 hook。
+
+
+
+
+列索引。
+
+
+此单元格跨越的列数。
+
+
+行索引。
+
+
+按步长而不是像素调整大小。
+
+
+水平调整大小的步长。
+
+
+垂直调整大小的步长。
+
+- **默认值:** `step`
+
+
+
+
+
+
+ 底部调整大小手柄的属性,包括调整方向和处理器。
+
+
+ 是否应该隐藏左侧调整大小手柄。如果不是第一列或左边距被禁用则为 true。
+
+
+ 左侧调整大小手柄的属性,包括调整方向和处理器。
+
+
+ 右侧调整大小手柄的属性,包括调整方向、初始大小和处理器。
+
+
+
+
+
+### `useTableStore`
+
+表格存储存储表格插件的状态。
+
+
+
+
+ 列大小覆盖。
+
+
+ 行大小覆盖。
+
+
+ 左边距覆盖。
+
+
+ 选中的单元格。
+
+
+ 选中的表格。
+
+
+
+
+### `useIsCellSelected`
+
+检查表格单元格是否被选中的自定义 hook。
+
+
+
+
+ 要检查的表格单元格元素。
+
+
+
+
+### `useSelectedCells`
+
+管理表格中单元格选择的 hook。
+
+它跟踪当前选中的单元格,并根据编辑器选择的变化更新它们。
+
+### `useTableBordersDropdownMenuContentState`
+
+表格边框下拉菜单内容的状态 hook。
+
+
+
+一个具有以下属性的对象:
+
+ 指示选中的表格单元格是否有底部边框。
+
+
+ 指示选中的表格单元格是否有顶部边框。
+
+
+ 指示选中的表格单元格是否有左侧边框。
+
+
+ 指示选中的表格单元格是否有右侧边框。
+
+
+ 指示选中的表格单元格是否没有边框。
+
+
+ 指示选中的表格单元格是否有外部边框(即所有边都有边框)。
+
+
+ 返回特定边框类型的 `onSelectTableBorder` 处理程序的工厂函数。
+
+- `onSelectTableBorder` 处理程序负责为选中的表格单元格设置边框样式。
+
+
+
+
+
+### `useTableColSizes`
+
+返回应用了覆盖的表格列大小的自定义 hook。如果表格的 `colCount` 更新为 1 且启用了 `enableUnsetSingleColSize` 选项,它将取消设置 `colSizes` 节点。
+
+
+
+
+如果为 `true`,则禁用对列大小应用覆盖。
+- **默认值:** `false`
+
+
+
+
+
+ 应用了覆盖的表格列大小。
+
+
+
+
+### `useTableElement`
+
+处理单元格选择和左边距计算的表格元素 hook。
+
+
+
+
+ 是否正在选择单元格。
+
+
+ 表格的左边距,考虑覆盖和插件选项。
+
+
+ 表格元素的属性:
+
+
+ 当单元格被选中时点击表格时折叠选择的处理程序。
+
+
+
+
+
+
+### `useTableCellElement`
+
+为表格单元格提供状态和功能的表格单元格元素 hook。
+
+
+
+
+ 表格单元格的边框样式。
+
+
+ 结束列索引(考虑 colSpan)。
+
+
+ 此单元格跨越的列数。
+
+
+ 是否正在选择单元格。
+
+
+ 单元格的最小高度。
+
+
+ 结束行索引(考虑 rowSpan)。
+
+
+ 此单元格当前是否被选中。
+
+
+ 单元格的宽度。
+
+
+
+
+### `useTableCellBorders`
+
+返回表格单元格边框样式的 hook。
+
+
+
+
+ 包含单元格边框样式的对象:
+
+
+ 底部边框样式。
+
+
+ 右侧边框样式。
+
+
+ 左侧边框样式。仅在第一列的单元格中存在。
+
+
+ 顶部边框样式。仅在第一行的单元格中存在。
+
+
+
+
+
+
+### `useTableCellSize`
+
+返回表格单元格大小(宽度和最小高度)的 hook。
+
+
+
+
+ 包含以下内容的对象:
+
+
+ 单元格的总宽度,通过对其跨越的所有列的宽度求和计算得出。
+
+
+ 单元格的最小高度,从行的 size 属性派生。
+
+
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/table.mdx b/apps/www/content/docs/(plugins)/(elements)/table.mdx
new file mode 100644
index 0000000000..7ad7039cc2
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/table.mdx
@@ -0,0 +1,1188 @@
+---
+title: Table
+docs:
+ - route: https://pro.platejs.org/docs/examples/table
+ title: Plus
+ - route: /docs/components/table-toolbar-button
+ title: Table Toolbar Button
+ - route: /docs/components/table-node
+ title: Table Nodes
+---
+
+
+
+
+
+## Features
+
+- Enables creation and editing of tables with advanced behaviors.
+- Arrow navigation (up/down).
+- Grid cell selection.
+- Cell selection expansion with `Shift+Arrow` keys.
+- Copying and pasting cells.
+- Row drag-and-drop reordering
+- Row selection via drag handle
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add table functionality is with the `TableKit`, which includes pre-configured `TablePlugin`, `TableRowPlugin`, `TableCellPlugin`, and `TableCellHeaderPlugin` with their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`TableElement`](/docs/components/table-node): Renders table containers.
+- [`TableRowElement`](/docs/components/table-node): Renders table rows.
+- [`TableCellElement`](/docs/components/table-node): Renders table cells.
+- [`TableCellHeaderElement`](/docs/components/table-node): Renders table header cells.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { TableKit } from '@/components/editor/plugins/table-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...TableKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/table
+```
+
+### Add Plugin
+
+Include `TablePlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { TablePlugin } from '@platejs/table/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ TablePlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+Configure the table plugins with custom components and options.
+
+```tsx
+import {
+ TableCellHeaderPlugin,
+ TableCellPlugin,
+ TablePlugin,
+ TableRowPlugin,
+} from '@platejs/table/react';
+import { createPlateEditor } from 'platejs/react';
+import {
+ TableCellElement,
+ TableCellHeaderElement,
+ TableElement,
+ TableRowElement,
+} from '@/components/ui/table-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ TablePlugin.configure({
+ node: { component: TableElement },
+ options: {
+ initialTableWidth: 600,
+ disableMerge: false,
+ minColumnWidth: 48,
+ },
+ }),
+ TableRowPlugin.withComponent(TableRowElement),
+ TableCellPlugin.withComponent(TableCellElement),
+ TableCellHeaderPlugin.withComponent(TableCellHeaderElement),
+ ],
+});
+```
+
+- `node.component`: Assigns [`TableElement`](/docs/components/table-node) to render table containers.
+- `withComponent`: Assigns components for table rows, cells, and header cells.
+- `options.initialTableWidth`: Sets the initial width for new tables.
+- `options.disableMerge`: Disables cell merging functionality.
+- `options.minColumnWidth`: Sets the minimum width for table columns.
+
+### Add Toolbar Button
+
+You can add [`TableToolbarButton`](/docs/components/table-toolbar-button) to your [Toolbar](/docs/toolbar) to insert tables.
+
+### Insert Toolbar Button
+
+You can add this item to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert table elements:
+
+```tsx
+{
+ icon: ,
+ label: 'Table',
+ value: KEYS.table,
+}
+```
+
+### Disable Merging Example
+
+
+
+
+
+## Plugins
+
+### TablePlugin
+
+
+
+
+ Disables the merging behavior of cells.
+
+
+ Disables the expansion of the table when inserting cells.
+
+
+ Disables the left resizer of the first column in the table.
+
+
+ Disables unsetting the width of the first column when the table has only one column. Set this to `true` if you want to resize the table width when there's only one column. Leave it `false` if you have a full-width table.
+
+
+ If defined, a normalizer will set each undefined table `colSizes` to this value divided by the number of columns. Note that merged cells are not yet supported.
+
+
+ The minimum width of a column in the table.
+ - **Default:** `48`
+
+
+
+
+### TableRowPlugin
+
+Plugin for table rows.
+
+### TableCellPlugin
+
+Plugin for table cells.
+
+### TableCellHeaderPlugin
+
+Plugin for table header cells.
+
+## API
+
+### editor.api.create.table
+
+
+
+
+Extends `GetEmptyRowNodeOptions`.
+
+
+
+
+
+Specify `true` if the table has a header row.
+
+
+The number of rows in the table.
+
+- **Default:** `0`
+
+
+
+The number of columns in the table.
+
+
+
+
+
+
+The table node.
+
+
+
+
+
+### editor.api.create.tableCell
+
+Creates an empty cell node for a table.
+
+
+
+
+Specify `true` if the cell is a header cell.
+
+
+The row element. If `header` is not specified, it will determine if the cell is a header cell based on the row's children.
+
+
+The children of the new cell node.
+
+- **Default:** `[editor.api.create.block()]`
+
+
+
+
+
+
+
+The cell node.
+
+
+
+
+
+### editor.api.create.tableRow
+
+Creates an empty row node with the specified number of columns.
+
+
+
+
+Specify `true` if the row is a header row.
+
+
+The number of columns in the row.
+
+- **Default:** `1`
+
+
+
+
+
+
+
+The row node.
+
+
+
+
+
+### editor.api.table.getCellBorders
+
+Gets the border styles for a table cell, handling special cases for first row and first column cells.
+
+
+
+
+The table cell element to get the border styles for.
+
+
+The default border style to use when cell borders are not defined.
+
+
+ The border color.
+ - **Default:** `'rgb(209 213 219)'`
+
+
+ The border size.
+ - **Default:** `1`
+
+
+ The border style.
+ - **Default:** `'solid'`
+
+
+
+
+
+
+
+ An object containing:
+
+
+ The bottom border style.
+
+
+ The right border style.
+
+
+ The left border style. Only present for cells in the first column.
+
+
+ The top border style. Only present for cells in the first row.
+
+
+
+
+
+
+### editor.api.table.getCellChildren
+
+Gets the children of a table cell.
+
+
+
+
+The table cell element.
+
+
+
+
+
+
+The children of the table cell.
+
+
+
+
+
+### editor.api.table.getCellSize
+
+Gets the width and minimum height of a table cell, taking into account column spans and column sizes.
+
+
+
+
+The table cell element to get the size for.
+
+
+Optional array of column sizes. If not provided, will use the table's overridden column sizes.
+
+
+
+
+
+The total width of the cell, calculated by summing the widths of all columns it spans.
+
+
+The minimum height of the cell, derived from the row's size property.
+
+
+
+
+### editor.api.table.getColSpan
+
+Gets the column span of a table cell.
+
+
+
+
+The table cell element to get the column span for.
+
+
+
+
+
+ The number of columns this cell spans.
+ - **Default:** `1`
+
+
+
+
+### editor.api.table.getRowSpan
+
+Gets the row span of a table cell.
+
+
+
+
+ The table cell element to get the row span for.
+
+
+
+
+
+ The number of rows this cell spans.
+ - **Default:** `1`
+
+
+
+
+### getCellType
+
+Get the plugin cell types.
+
+
+
+
+
+ An array of element types for table cells (td and th) in the editor.
+
+
+
+
+
+### getNextTableCell
+
+Gets the next cell in the table.
+
+
+
+
+ The entry of the current cell.
+
+
+ The path of the current cell.
+
+
+ The entry of the current row.
+
+
+
+
+
+ The node entry of the cell in the next row, or `undefined` if the current
+ row is the last row.
+
+
+
+
+### getPreviousTableCell
+
+Gets the previous cell in the table.
+
+
+
+
+ The entry of the current cell.
+
+
+ The path of the current cell.
+
+
+ The entry of the current row.
+
+
+
+
+
+ The node entry of the cell in the previous row, or `undefined` if the
+ current row is the first row.
+
+
+
+
+### getTableColumnCount
+
+Gets the number of columns in a table.
+
+
+
+
+ The table node for which to retrieve the column count.
+
+
+
+
+
+
+The number of columns in the table.
+
+
+
+
+### getTableColumnIndex
+
+Gets the column index of a cell node within a table.
+
+
+
+
+ The cell node for which to retrieve the column index.
+
+
+
+
+
+
+The column index of the cell node.
+
+
+
+
+
+### getTableEntries
+
+Gets the table, row, and cell node entries based on the current selection or a specified location.
+
+
+
+
+The location where the table cell is located.
+
+- **Default:** `editor.selection`
+
+
+
+
+
+ The table node entry.
+
+
+ The row node entry.
+
+
+ The cell node entry.
+
+
+
+
+
+### getTableGridAbove
+
+Gets the sub table above the anchor and focus positions based on the specified format (tables or cells).
+
+
+
+
+The format of the sub table to retrieve.
+
+- **Default:** `'table'`
+
+
+
+
+
+ The sub table entries.
+
+
+### getTableGridByRange
+
+Gets the sub table between two cell paths within a given range.
+
+
+
+
+The range specifying the start and end cell paths.
+
+
+The format of the output.
+
+- **Default:** `'table'`
+
+
+
+
+
+ The sub table entries.
+
+
+
+### getTableRowIndex
+
+Gets the row index of a cell node within a table.
+
+
+
+
+ The cell node for which to retrieve the row index.
+
+
+
+
+
+
+The row index of the cell node.
+
+
+
+
+
+### getTopTableCell
+
+Gets the cell above the current cell in the table.
+
+
+
+
+ The path to the current cell. If not provided, the function will search for
+ the current cell in the editor.
+
+
+
+
+
+
+The cell node entry.
+
+
+
+
+### isTableBorderHidden
+
+Checks if the border of a table cell or the table itself is hidden based on the specified border direction.
+
+
+
+
+ The border direction to check.
+
+
+
+
+
+
+`true` if the border is hidden, `false` otherwise.
+
+
+
+
+
+## Transforms
+
+### `tf.insert.table`
+
+Inserts a table at the current selection if there is no existing table in the editor. Selects the start of the inserted table.
+
+
+
+
+Extends `GetEmptyRowNodeOptions`.
+
+
+The number of rows in the table.
+
+- **Default:** `2`
+
+
+
+The number of columns in the table.
+
+- **Default:** `2`
+
+
+
+If `true`, the first row of the table will be treated as a header row.
+
+
+
+
+
+
+The options for inserting the table nodes.
+
+
+
+
+### `tf.insert.tableColumn`
+
+Inserts a column into the table at the current selection or a specified cell path.
+
+
+
+
+The exact path of the cell to insert the column at. This overrules the
+`fromCell` option.
+
+
+If true, insert the column before the current column instead of after.
+
+
+The path of the cell to insert the column from.
+
+
+If true, the inserted column will be treated as a header column.
+
+
+If true, the inserted column will be selected after insertion.
+
+
+
+
+### `tf.insert.tableRow`
+
+Inserts a row into the table at the current selection or a specified row path.
+
+
+
+
+Exact path of the row to insert the column at. Pass the table path to
+insert at the end of the table. Will overrule `fromRow`.
+
+
+If true, insert the row before the current row instead of after.
+
+
+The path of the row to insert the new row from.
+
+
+If true, the inserted row will be treated as a header row.
+
+
+If true, the inserted row will be selected after insertion.
+
+
+
+
+### `tf.remove.tableColumn`
+
+Deletes the column containing the selected cell in a table.
+
+### `tf.remove.tableRow`
+
+Deletes the row containing the selected cell in a table.
+
+### `tf.remove.table`
+
+Deletes the entire table.
+
+### `tf.table.merge`
+
+Merges multiple selected cells into one.
+
+The merged cell will:
+- Have a colSpan equal to the number of columns spanned by the selected cells
+- Have a rowSpan equal to the number of rows spanned by the selected cells
+- Contain the combined content of all merged cells (non-empty cells only)
+- Inherit the header status from the first selected cell
+
+### `tf.table.split`
+
+Splits a merged cell back into individual cells.
+
+The split operation will:
+- Create new cells for each column and row that was spanned
+- Copy the header status from the original merged cell
+- Place the original cell's content in the first cell
+- Create empty cells for the remaining spaces
+
+### `tf.moveSelectionFromCell`
+
+Moves the selection by cell unit within a table.
+
+
+
+
+The location to move the selection from.
+
+
+ Set to `true` to move the selection to the cell above, `false` to move
+the selection to the cell below.
+
+
+The edge to expand the cell selection to.
+
+
+Set to `true` to move the selection from only one selected cell.
+
+
+
+
+### `tf.setBorderSize`
+
+Sets the size of the specified border in a table cell.
+
+
+
+
+The size of the border.
+
+
+Options for setting the border size.
+
+
+
+
+
+The location of the cell to set the border size.
+
+
+The border direction to set the size.
+
+- **Default:** `'all'`
+
+
+
+
+
+### `tf.setTableColSize`
+
+Sets the width of a specific column in a table.
+
+
+
+
+The index of the column to set the width.
+
+
+The desired width of the column.
+
+
+Additional options for finding the table node.
+
+
+
+
+### `tf.setTableMarginLeft`
+
+Sets the margin left of a table.
+
+
+
+
+An object containing the desired margin left value.
+
+
+Additional options for finding the table node.
+
+
+
+
+### `tf.setTableRowSize`
+
+Sets the size (height) of a table row.
+
+
+
+
+The index of the row to set the size.
+
+
+The desired height of the row.
+
+
+Additional options for finding the table node.
+
+
+
+
+## Plugin Extensions
+
+### `onKeyDownTable`
+
+Handles the keyboard events for tables.
+
+
+
+
+ The plate plugin.
+
+
+
+
+
+ The keyboard handler return type.
+
+
+
+
+### `withDeleteTable`
+
+Prevents the deletion of cells in tables.
+
+### `withGetFragmentTable`
+
+If the selection is inside a table, it retrieves the subtable above the selection as the fragment. This is useful when copying and pasting table cells.
+
+### `withInsertFragmentTable`
+
+If inserting a table:
+
+- If the block above the anchor of the selection is a table, replace each cell above with the inserted table until out of bounds. Select the inserted cells.
+- If there is no table above the anchor, check if the selection is inside a table. If it is, find the cell at the anchor position and replace its children with the inserted fragment.
+
+### `withInsertTextTable`
+
+If the selection is expanded:
+
+- Check if the selection is inside a table. If it is, collapse the selection to the focus edge.
+
+### `withNormalizeTable`
+
+Normalize table structure by performing the following actions:
+
+- Wrap cell children in a paragraph if they are texts.
+- Unwrap nodes that are not valid table elements.
+- Set initial column sizes for tables if specified.
+
+### `withSelectionTable`
+
+Handle table selections by performing the following actions:
+
+- Adjust the focus of the selection when the anchor is inside a table and the focus is in a block before or after the table.
+- Adjust the focus of the selection when the focus is inside a table and the anchor is in a block before or after the table.
+- Override the selection from a cell if the previous and new selections are in different cells.
+
+### `withSetFragmentDataTable`
+
+Handle setting data to the clipboard when copying or cutting table data by performing the following actions:
+
+- Check if a table entry and selected cell entries exist.
+- Handle single-cell copy or cut operations by copying the cell content instead of the table structure.
+- Create a table structure with the selected cells' content.
+- Set the text, HTML, CSV, TSV, and Slate fragment data to the clipboard.
+
+### `withTable`
+
+Enhance the editor instance with table-related functionality by applying the following higher-order functions:
+
+- `withNormalizeTable`: Normalize table structure and content.
+- `withDeleteTable`: Prevent cell deletion within a table.
+- `withGetFragmentTable`: Handle getting the fragment data when copying or cutting table cells.
+- `withInsertFragmentTable`: Handle inserting table fragments.
+- `withInsertTextTable`: Handle inserting text within a table.
+- `withSelectionTable`: Handle adjusting the selection within a table.
+- `withSetFragmentDataTable`: Handle setting the fragment data when copying or cutting table data.
+
+## Hooks
+
+### `useTableCellElementResizable`
+
+A hook that provides resizing functionality for table cell elements.
+
+
+
+
+The index of the column.
+
+
+The number of columns this cell spans.
+
+
+ The index of the row.
+
+
+Resize by step instead of by pixel.
+
+
+Step size for horizontal resizing.
+
+
+Step size for vertical resizing.
+
+- **Default:** `step`
+
+
+
+
+
+
+ Props for the bottom resize handle, including resize direction and handler.
+
+
+ Whether the left resize handle should be hidden. True if not the first column or margin left is disabled.
+
+
+ Props for the left resize handle, including resize direction and handler.
+
+
+ Props for the right resize handle, including resize direction, initial size, and handler.
+
+
+
+
+
+### `useTableStore`
+
+The table store stores the state of the table plugin.
+
+
+
+
+ The column size overrides.
+
+
+ The row size overrides.
+
+
+ The margin left override.
+
+
+ The selected cells.
+
+
+ The selected tables.
+
+
+
+
+### `useIsCellSelected`
+
+Custom hook that checks if a table cell is selected.
+
+
+
+
+ The table cell element to check.
+
+
+
+
+### `useSelectedCells`
+
+A hook that manages the selection of cells in a table.
+
+It keeps track of the currently selected cells and updates them based on changes in editor selection.
+
+### `useTableBordersDropdownMenuContentState`
+
+A state hook for the table borders dropdown menu content.
+
+
+
+An object with the following properties:
+
+Indicates whether the selected table cells have a bottom border.
+
+
+Indicates whether the selected table cells have a top border.
+
+
+Indicates whether the selected table cells have a left border.
+
+
+Indicates whether the selected table cells have a right border.
+
+
+Indicates whether the selected table cells have no borders.
+
+
+Indicates whether the selected table cells have outer borders (i.e.,
+borders on all sides).
+
+
+A factory function that returns the `onSelectTableBorder` handler for a
+specific border type.
+
+- The `onSelectTableBorder` handler is responsible for setting the border style for the selected table cells.
+
+
+
+
+
+### `useTableColSizes`
+
+Custom hook that returns the column sizes of a table with overrides applied. If the `colCount` of the table updates to 1 and the `enableUnsetSingleColSize` option is enabled, it unsets the `colSizes` node.
+
+
+
+
+If `true`, disables applying overrides to the column sizes.
+- **Default:** `false`
+
+
+
+
+
+ The column sizes of the table with overrides applied.
+
+
+
+
+### `useTableElement`
+
+A hook for a table element that handles cell selection and margin left calculations.
+
+
+
+
+ Whether cells are currently being selected.
+
+
+ The margin left of the table, considering overrides and plugin options.
+
+
+ Props for the table element:
+
+
+ Handler that collapses selection when clicking on the table while cells are selected.
+
+
+
+
+
+
+### `useTableCellElement`
+
+A hook for a table cell element that provides state and functionality for table cells.
+
+
+
+
+ The border styles of the table cell.
+
+
+ The ending column index (considering colSpan).
+
+
+ The number of columns this cell spans.
+
+
+ Whether cells are currently being selected.
+
+
+ The minimum height of the cell.
+
+
+ The ending row index (considering rowSpan).
+
+
+ Whether this cell is currently selected.
+
+
+ The width of the cell.
+
+
+
+
+### `useTableCellBorders`
+
+A hook that returns the border styles for a table cell.
+
+
+
+
+ An object containing the border styles for the cell:
+
+
+ The bottom border style.
+
+
+ The right border style.
+
+
+ The left border style. Only present for cells in the first column.
+
+
+ The top border style. Only present for cells in the first row.
+
+
+
+
+
+
+### `useTableCellSize`
+
+A hook that returns the size (width and minimum height) of a table cell.
+
+
+
+
+ An object containing:
+
+
+ The total width of the cell, calculated by summing the widths of all columns it spans.
+
+
+ The minimum height of the cell, derived from the row's size property.
+
+
+
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/toc.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/toc.cn.mdx
new file mode 100644
index 0000000000..60333b9edb
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/toc.cn.mdx
@@ -0,0 +1,329 @@
+---
+title: 目录
+docs:
+ - route: components/toc-node
+ title: Toc 元素
+ - route: https://pro.platejs.org/docs/components/toc-sidebar
+ title: Toc 侧边栏
+---
+
+
+
+
+
+## 功能特性
+
+- 自动从文档标题生成目录
+- 平滑滚动至标题位置
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的目录功能添加方式是使用 `TocKit`,它已预配置了 [Plate UI](/docs/installation/plate-ui) 组件的 `TocPlugin`。
+
+
+
+- [`TocElement`](/docs/components/toc-node): 渲染目录元素
+
+### 添加套件
+
+将套件加入插件列表:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { TocKit } from '@/components/editor/plugins/toc-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...TocKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes @platejs/toc
+```
+
+### 添加插件
+
+在创建编辑器时,将 `TocPlugin` 和 `HnPlugin` 加入 Plate 插件数组。
+
+```tsx
+import { TocPlugin } from '@platejs/toc/react';
+import { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ H1Plugin,
+ H2Plugin,
+ H3Plugin,
+ TocPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+使用自定义组件和滚动选项配置 `TocPlugin`。
+
+```tsx
+import { TocPlugin } from '@platejs/toc/react';
+import { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { TocElement } from '@/components/ui/toc-node';
+import { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ H1Plugin.withComponent(H1Element),
+ H2Plugin.withComponent(H2Element),
+ H3Plugin.withComponent(H3Element),
+ TocPlugin.configure({
+ node: { component: TocElement },
+ options: {
+ topOffset: 80,
+ isScroll: true,
+ },
+ }),
+ ],
+});
+```
+
+- `node.component`: 指定渲染目录元素的 [`TocElement`](/docs/components/toc-node)
+- `options.topOffset`: 设置滚动至标题时的顶部偏移量
+- `options.isScroll`: 启用滚动至标题的行为
+
+### 添加工具栏按钮
+
+可将此项加入[插入工具栏按钮](/docs/toolbar#insert-toolbar-button)来插入目录元素:
+
+```tsx
+{
+ icon: ,
+ label: '目录',
+ value: KEYS.toc,
+}
+```
+
+### 滚动容器设置
+
+- 若您的滚动元素是 [EditorContainer](/docs/components/editor),可跳过此步骤
+- 若您的滚动元素是编辑器容器,将 `useEditorContainerRef()` 作为 `ref` 属性传入。例如:
+
+```tsx
+// 在 组件下方
+function EditorContainer({ children }: { children: React.ReactNode }) {
+ const containerRef = useEditorContainerRef();
+
+ return {children}
;
+}
+```
+
+- 若您的滚动元素是编辑器容器的祖先元素,将 `useEditorScrollRef()` 作为 `ref` 属性传入。例如:
+
+```tsx
+// 在 组件下方
+function Layout() {
+ const scrollRef = useEditorScrollRef();
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+
+
+## Plate Plus
+
+
+
+## 插件
+
+### `TocPlugin`
+
+目录生成插件。
+
+
+
+
+ 启用滚动行为
+ - **默认值:** `true`
+
+
+ 滚动至标题时的顶部偏移量
+ - **默认值:** `80`
+
+
+ 自定义标题查询函数
+
+
+
+
+## 转换器
+
+### `tf.insertToc`
+
+插入目录元素。
+
+
+
+ 节点插入选项
+
+
+
+## 钩子
+
+### `useTocElementState`
+
+管理 TOC 元素状态。
+
+
+
+
+ 文档标题数组
+
+
+ 标题滚动处理器
+
+
+
+
+### `useTocElement`
+
+处理 TOC 元素交互。
+
+
+
+
+
+ 来自 useTocElementState 的滚动处理器
+
+
+
+
+
+ TOC 元素属性
+
+
+
+ TOC 项点击处理器
+
+
+
+
+
+### `useTocSideBarState`
+
+管理 TOC 侧边栏状态。
+
+
+
+
+ 初始展开状态
+ - **默认值:** `true`
+
+
+ Intersection Observer 根边距
+ - **默认值:** `'0px 0px 0px 0px'`
+
+
+ 滚动顶部偏移量
+ - **默认值:** `0`
+
+
+
+
+
+ 当前活动区块 ID
+
+
+ 文档标题列表
+
+
+ 鼠标悬停 TOC 状态
+
+
+ 侧边栏展开状态
+
+
+ 设置观察状态
+
+
+ 设置鼠标悬停状态
+
+
+ TOC 元素引用
+
+
+ 内容滚动处理器
+
+
+
+
+### `useTocSideBar`
+
+该钩子为 TOC 侧边栏组件提供属性和处理器。
+
+
+
+
+ 鼠标悬停 TOC 状态
+
+
+ 侧边栏展开状态
+
+
+ 设置观察状态
+
+
+ 设置鼠标悬停状态
+
+
+ TOC 元素引用
+
+
+ 内容滚动处理器
+
+
+
+
+ 导航元素属性
+
+
+
+ TOC 元素引用
+
+
+ 鼠标进入处理器
+
+
+ 鼠标离开处理器
+
+
+
+ TOC 项点击处理器
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/toc.mdx b/apps/www/content/docs/(plugins)/(elements)/toc.mdx
new file mode 100644
index 0000000000..30e3331b8e
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/toc.mdx
@@ -0,0 +1,329 @@
+---
+title: Table of Contents
+docs:
+ - route: https://pro.platejs.org/docs/examples/toc
+ title: Plus
+ - route: components/toc-node
+ title: Toc Element
+---
+
+
+
+
+
+## Features
+
+- Automatically generates a table of contents from document headings
+- Smooth scrolling to headings
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add table of contents functionality is with the `TocKit`, which includes pre-configured `TocPlugin` with the [Plate UI](/docs/installation/plate-ui) component.
+
+
+
+- [`TocElement`](/docs/components/toc-node): Renders table of contents elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { TocKit } from '@/components/editor/plugins/toc-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...TocKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes @platejs/toc
+```
+
+### Add Plugins
+
+Include `TocPlugin` and `HnPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { TocPlugin } from '@platejs/toc/react';
+import { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ H1Plugin,
+ H2Plugin,
+ H3Plugin,
+ TocPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+Configure the `TocPlugin` with custom component and scroll options.
+
+```tsx
+import { TocPlugin } from '@platejs/toc/react';
+import { H1Plugin, H2Plugin, H3Plugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { TocElement } from '@/components/ui/toc-node';
+import { H1Element, H2Element, H3Element } from '@/components/ui/heading-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ H1Plugin.withComponent(H1Element),
+ H2Plugin.withComponent(H2Element),
+ H3Plugin.withComponent(H3Element),
+ TocPlugin.configure({
+ node: { component: TocElement },
+ options: {
+ topOffset: 80,
+ isScroll: true,
+ },
+ }),
+ ],
+});
+```
+
+- `node.component`: Assigns [`TocElement`](/docs/components/toc-node) to render table of contents elements.
+- `options.topOffset`: Sets the top offset when scrolling to headings.
+- `options.isScroll`: Enables scrolling behavior to headings.
+
+### Insert Toolbar Button
+
+You can add this item to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert table of contents elements:
+
+```tsx
+{
+ icon: ,
+ label: 'Table of contents',
+ value: KEYS.toc,
+}
+```
+
+### Scroll Container Setup
+
+- If your scrolling element is [EditorContainer](/docs/components/editor), you can skip this step.
+- If your scrolling element is the editor container, pass `useEditorContainerRef()` as the `ref` prop. For example:
+
+```tsx
+// Below component
+function EditorContainer({ children }: { children: React.ReactNode }) {
+ const containerRef = useEditorContainerRef();
+
+ return {children}
;
+}
+```
+
+- If your scrolling element is an ancestor of the editor container, pass `useEditorScrollRef()` as the `ref` prop. For example:
+
+```tsx
+// Below component
+function Layout() {
+ const scrollRef = useEditorScrollRef();
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `TocPlugin`
+
+Plugin for generating table of contents.
+
+
+
+
+ Enable scrolling behavior.
+ - **Default:** `true`
+
+
+ Top offset when scrolling to heading.
+ - **Default:** `80`
+
+
+ Custom function to query headings.
+
+
+
+
+## Transforms
+
+### `tf.insertToc`
+
+Insert table of contents element.
+
+
+
+ Node insertion options.
+
+
+
+## Hooks
+
+### `useTocElementState`
+
+Manage TOC element state.
+
+
+
+
+ Document headings array.
+
+
+ Heading scroll handler.
+
+
+
+
+### `useTocElement`
+
+Handle TOC element interactions.
+
+
+
+
+
+ Scroll handler from useTocElementState.
+
+
+
+
+
+ Props for TOC element.
+
+
+
+ TOC item click handler.
+
+
+
+
+
+### `useTocSideBarState`
+
+Manage TOC sidebar state.
+
+
+
+
+ Initial open state.
+ - **Default:** `true`
+
+
+ Intersection Observer root margin.
+ - **Default:** `'0px 0px 0px 0px'`
+
+
+ Scroll top offset.
+ - **Default:** `0`
+
+
+
+
+
+ Active section ID.
+
+
+ Document headings.
+
+
+ Mouse over TOC state.
+
+
+ Sidebar open state.
+
+
+ Set observation state.
+
+
+ Set mouse over state.
+
+
+ TOC element ref.
+
+
+ Content scroll handler.
+
+
+
+
+### `useTocSideBar`
+
+This hook provides props and handlers for the TOC sidebar component.
+
+
+
+
+ Mouse over TOC state.
+
+
+ Sidebar open state.
+
+
+ Set observation state.
+
+
+ Set mouse over state.
+
+
+ TOC element ref.
+
+
+ Content scroll handler.
+
+
+
+
+ Navigation element props.
+
+
+
+ TOC element ref.
+
+
+ Mouse enter handler.
+
+
+ Mouse leave handler.
+
+
+
+ TOC item click handler.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(elements)/toggle.cn.mdx b/apps/www/content/docs/(plugins)/(elements)/toggle.cn.mdx
new file mode 100644
index 0000000000..a4cb4f1e86
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/toggle.cn.mdx
@@ -0,0 +1,287 @@
+---
+title: 折叠开关
+docs:
+ - route: /docs/components/toggle-node
+ title: 折叠元素
+ - route: /docs/components/toggle-toolbar-button
+ title: 折叠按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 在文档中添加可折叠内容
+- 折叠项默认收起,点击可展开显示内部内容
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用 `ToggleKit`,它包含预配置的 `TogglePlugin`(支持缩进)及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`IndentKit`](/docs/indent): 为折叠元素提供缩进支持
+- [`ToggleElement`](/docs/components/toggle-node): 渲染折叠元素
+
+### 添加套件
+
+将套件加入插件列表:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { ToggleKit } from '@/components/editor/plugins/toggle-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...ToggleKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装依赖
+
+```bash
+npm install @platejs/indent @platejs/toggle
+```
+
+### 添加插件
+
+在创建编辑器时,将 `TogglePlugin` 和 `IndentPlugin` 加入插件数组。
+
+```tsx
+import { IndentPlugin } from '@platejs/indent/react';
+import { TogglePlugin } from '@platejs/toggle/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ IndentPlugin,
+ TogglePlugin,
+ ],
+});
+```
+
+### 配置插件
+
+为 `IndentPlugin` 和 `TogglePlugin` 配置目标元素和组件分配。
+
+```tsx
+import { IndentPlugin } from '@platejs/indent/react';
+import { TogglePlugin } from '@platejs/toggle/react';
+import { createPlateEditor } from 'platejs/react';
+import { ToggleElement } from '@/components/ui/toggle-node';
+import { KEYS } from 'platejs';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ IndentPlugin.configure({
+ inject: {
+ targetPlugins: [...KEYS.heading, KEYS.p, KEYS.toggle],
+ },
+ }),
+ TogglePlugin.withComponent(ToggleElement),
+ ],
+});
+```
+
+- `withComponent`: 指定 [`ToggleElement`](/docs/components/toggle-node) 渲染折叠元素
+- `IndentPlugin.inject.targetPlugins`: 配置支持缩进的元素类型(包括折叠项)
+
+### 添加工具栏按钮
+
+可在[工具栏](/docs/toolbar)中添加 [`ToggleToolbarButton`](/docs/components/toggle-toolbar-button) 来插入折叠元素。
+
+### 转换工具栏按钮
+
+将此选项加入[转换工具栏按钮](/docs/toolbar#turn-into-toolbar-button)可将区块转为折叠项:
+
+```tsx
+{
+ icon: ,
+ label: '折叠列表',
+ value: KEYS.toggle,
+}
+```
+
+### 插入工具栏按钮
+
+将此选项加入[插入工具栏按钮](/docs/toolbar#insert-toolbar-button)可插入折叠元素:
+
+```tsx
+{
+ icon: ,
+ label: '折叠列表',
+ value: KEYS.toggle,
+}
+```
+
+
+
+## 插件
+
+### `TogglePlugin`
+
+管理折叠功能的插件。
+
+
+
+
+ 当前展开的折叠项ID集合
+ - **默认值:** `new Set()`
+
+
+ 检查折叠项是否展开的函数
+ - **默认值:** `(id) => openIds.has(id)`
+
+
+ 检查是否存在收起状态的折叠项
+ - **默认值:** `(ids) => ids.some(id => !isOpen(id))`
+
+
+
+
+## API接口
+
+### `api.toggle.toggleIds`
+
+切换指定折叠项的展开状态。
+
+
+切换折叠项展开状态
+
+
+
+ 需要切换的元素ID数组
+
+
+ 强制切换状态:
+ - `true`: 展开所有
+ - `false`: 收起所有
+ - `null`: 切换当前状态
+
+
+
+
+### `openNextToggles`
+
+将当前选区所在区块标记为展开的折叠项。
+
+## 钩子函数
+
+### `useToggleToolbarButtonState`
+
+获取折叠工具栏按钮状态的钩子。
+
+
+
+
+ 按钮是否处于按下状态
+
+
+
+
+### `useToggleToolbarButton`
+
+处理折叠工具栏按钮行为的钩子。
+
+
+
+
+ 按钮是否处于按下状态
+
+
+
+
+
+ 折叠按钮属性
+
+
+ 是否按下状态
+
+
+ 切换节点类型并聚焦编辑器
+
+
+ 防止点击时失去焦点
+
+
+
+
+
+
+### `useToggleButtonState`
+
+获取折叠按钮状态的钩子。
+
+
+获取折叠按钮状态
+
+
+
+ 折叠元素ID
+
+
+
+
+
+ 折叠元素ID
+
+
+ 是否处于展开状态
+
+
+
+
+### `useToggleButton`
+
+处理折叠按钮行为的钩子。
+
+
+处理折叠按钮行为
+
+
+
+ 折叠元素ID
+
+
+ 是否展开状态
+
+
+
+
+
+ 折叠元素ID
+
+
+ 是否展开状态
+
+
+ 折叠按钮属性
+
+
+ 切换展开状态
+
+
+ 防止点击时失去焦点
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(elements)/toggle.mdx b/apps/www/content/docs/(plugins)/(elements)/toggle.mdx
new file mode 100644
index 0000000000..8ac437f88b
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(elements)/toggle.mdx
@@ -0,0 +1,287 @@
+---
+title: Toggle
+docs:
+ - route: /docs/components/toggle-node
+ title: Toggle Element
+ - route: /docs/components/toggle-toolbar-button
+ title: Toggle Button
+---
+
+
+
+
+
+## Features
+
+- Add toggles to your document
+- Toggles are closed by default, and can be opened to reveal the content inside
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add toggle functionality is with the `ToggleKit`, which includes pre-configured `TogglePlugin` with indent support and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`IndentKit`](/docs/indent): Provides indent support for toggle elements.
+- [`ToggleElement`](/docs/components/toggle-node): Renders toggle elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { ToggleKit } from '@/components/editor/plugins/toggle-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...ToggleKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/indent @platejs/toggle
+```
+
+### Add Plugins
+
+Include `TogglePlugin` and `IndentPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { IndentPlugin } from '@platejs/indent/react';
+import { TogglePlugin } from '@platejs/toggle/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ IndentPlugin,
+ TogglePlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+Configure the `IndentPlugin` and `TogglePlugin` with proper targeting and component assignment.
+
+```tsx
+import { IndentPlugin } from '@platejs/indent/react';
+import { TogglePlugin } from '@platejs/toggle/react';
+import { createPlateEditor } from 'platejs/react';
+import { ToggleElement } from '@/components/ui/toggle-node';
+import { KEYS } from 'platejs';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ IndentPlugin.configure({
+ inject: {
+ targetPlugins: [...KEYS.heading, KEYS.p, KEYS.toggle],
+ },
+ }),
+ TogglePlugin.withComponent(ToggleElement),
+ ],
+});
+```
+
+- `withComponent`: Assigns [`ToggleElement`](/docs/components/toggle-node) to render toggle elements.
+- `IndentPlugin.inject.targetPlugins`: Configures which element types support indentation, including toggles.
+
+### Add Toolbar Button
+
+You can add [`ToggleToolbarButton`](/docs/components/toggle-toolbar-button) to your [Toolbar](/docs/toolbar) to insert toggle elements.
+
+### Turn Into Toolbar Button
+
+You can add this item to the [Turn Into Toolbar Button](/docs/toolbar#turn-into-toolbar-button) to convert blocks into toggles:
+
+```tsx
+{
+ icon: ,
+ label: 'Toggle list',
+ value: KEYS.toggle,
+}
+```
+
+### Insert Toolbar Button
+
+You can add this item to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert toggle elements:
+
+```tsx
+{
+ icon: ,
+ label: 'Toggle list',
+ value: KEYS.toggle,
+}
+```
+
+
+
+## Plugins
+
+### `TogglePlugin`
+
+Plugin for managing toggle functionality.
+
+
+
+
+ Set of open toggle IDs.
+ - **Default:** `new Set()`
+
+
+ Function to check if toggle is open.
+ - **Default:** `(id) => openIds.has(id)`
+
+
+ Function to check if any toggles are closed.
+ - **Default:** `(ids) => ids.some(id => !isOpen(id))`
+
+
+
+
+## API
+
+### `api.toggle.toggleIds`
+
+Toggles the open state of specified toggles.
+
+
+Toggle open state of toggles.
+
+
+
+ Array of element IDs to toggle.
+
+
+ Force toggle state:
+ - `true`: expand all toggles
+ - `false`: collapse all toggles
+ - `null`: toggle current state
+
+
+
+
+### `openNextToggles`
+
+Mark block at current selection as open toggle.
+
+## Hooks
+
+### `useToggleToolbarButtonState`
+
+Hook for getting toggle toolbar button state.
+
+
+
+
+ Whether button is pressed.
+
+
+
+
+### `useToggleToolbarButton`
+
+Hook for handling toggle toolbar button behavior.
+
+
+
+
+ Whether button is pressed.
+
+
+
+
+
+ Props for toggle button.
+
+
+ Whether button is pressed.
+
+
+ Toggle node type and focus editor.
+
+
+ Prevent focus loss on click.
+
+
+
+
+
+
+### `useToggleButtonState`
+
+Hook for getting toggle button state.
+
+
+Get toggle button state.
+
+
+
+ Toggle element ID.
+
+
+
+
+
+ Toggle element ID.
+
+
+ Whether toggle is expanded.
+
+
+
+
+### `useToggleButton`
+
+Hook for handling toggle button behavior.
+
+
+Handle toggle button behavior.
+
+
+
+ Toggle element ID.
+
+
+ Whether toggle is expanded.
+
+
+
+
+
+ Toggle element ID.
+
+
+ Whether toggle is expanded.
+
+
+ Props for toggle button.
+
+
+ Toggle open state.
+
+
+ Prevent focus loss on click.
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(combobox)/combobox.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/combobox.cn.mdx
new file mode 100644
index 0000000000..a30a836be2
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/combobox.cn.mdx
@@ -0,0 +1,320 @@
+---
+title: 组合框(Combobox)
+docs:
+ - route: /docs/components/inline-combobox
+ title: 内联组合框
+---
+
+
+
+
+使用`@`插入用户、页面或任何参考的提及
+
+
+
+通过`/`快速访问编辑器命令和块
+
+
+
+使用`:`自动补全插入表情符号
+
+
+
+
+
+
+## 功能特性
+
+- 创建基于触发器的组合框功能的实用工具
+- 可配置的触发字符和模式
+- 键盘导航和选择处理
+
+
+
+## 创建组合框插件
+
+
+
+### 安装
+
+```bash
+npm install @platejs/combobox
+```
+
+### 创建输入插件
+
+首先创建一个输入插件,当触发器激活时将被插入:
+
+```tsx
+import { createSlatePlugin } from 'platejs';
+
+const TagInputPlugin = createSlatePlugin({
+ key: 'tag_input',
+ editOnly: true,
+ node: {
+ isElement: true,
+ isInline: true,
+ isVoid: true,
+ },
+});
+```
+
+### 创建主插件
+
+使用`withTriggerCombobox`创建主插件:
+
+```tsx
+import { createTSlatePlugin, type PluginConfig } from 'platejs';
+import {
+ type TriggerComboboxPluginOptions,
+ withTriggerCombobox
+} from '@platejs/combobox';
+
+type TagConfig = PluginConfig<'tag', TriggerComboboxPluginOptions>;
+
+export const TagPlugin = createTSlatePlugin({
+ key: 'tag',
+ node: { isElement: true, isInline: true, isVoid: true },
+ options: {
+ trigger: '#',
+ triggerPreviousCharPattern: /^\s?$/,
+ createComboboxInput: () => ({
+ children: [{ text: '' }],
+ type: 'tag_input',
+ }),
+ },
+ plugins: [TagInputPlugin],
+}).overrideEditor(withTriggerCombobox);
+```
+
+- `node.isElement`: 定义此为元素节点(非文本)
+- `node.isInline`: 使标签元素内联(非块级)
+- `node.isVoid`: 防止在标签元素内编辑
+- `options.trigger`: 触发组合框的字符(本例为`#`)
+- `options.triggerPreviousCharPattern`: 必须匹配触发器前字符的正则表达式。`/^\s?$/`允许在行首或空白后触发
+- `options.createComboboxInput`: 触发器激活时创建输入元素节点的函数
+
+### 创建组件
+
+使用`InlineCombobox`创建输入元素组件:
+
+```tsx
+import { PlateElement, useFocused, useReadOnly, useSelected } from 'platejs/react';
+import {
+ InlineCombobox,
+ InlineComboboxContent,
+ InlineComboboxEmpty,
+ InlineComboboxInput,
+ InlineComboboxItem,
+} from '@/components/ui/inline-combobox';
+import { cn } from '@/lib/utils';
+
+const tags = [
+ { id: 'frontend', name: '前端', color: 'blue' },
+ { id: 'backend', name: '后端', color: 'green' },
+ { id: 'design', name: '设计', color: 'purple' },
+ { id: 'urgent', name: '紧急', color: 'red' },
+];
+
+export function TagInputElement({ element, ...props }) {
+ return (
+
+
+
+
+
+ 未找到标签
+
+ {tags.map((tag) => (
+ {
+ // 插入实际标签元素
+ editor.tf.insertNodes({
+ type: 'tag',
+ tagId: tag.id,
+ children: [{ text: tag.name }],
+ });
+ }}
+ >
+
+ #{tag.name}
+
+ ))}
+
+
+
+ {props.children}
+
+ );
+}
+
+export function TagElement({ element, ...props }) {
+ const selected = useSelected();
+ const focused = useFocused();
+ const readOnly = useReadOnly();
+
+ return (
+
+ #{element.value}
+ {props.children}
+
+ );
+}
+```
+
+### 添加到编辑器
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { TagPlugin, TagInputPlugin } from './tag-plugin';
+import { TagElement, TagInputElement } from './tag-components';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ TagPlugin.configure({
+ options: {
+ triggerQuery: (editor) => {
+ // 在代码块中禁用
+ return !editor.api.some({ match: { type: 'code_block' } });
+ },
+ },
+ }).withComponent(TagElement),
+ TagInputPlugin.withComponent(TagInputElement),
+ ],
+});
+```
+
+- `options.triggerQuery`: 根据编辑器状态有条件启用/禁用触发器的可选函数
+
+
+
+## 示例
+
+
+
+
+
+## 配置选项
+
+### TriggerComboboxPluginOptions
+
+基于触发器的组合框插件的配置选项。
+
+
+
+
+ 触发器激活时创建输入节点的函数。
+
+
+ 触发组合框的字符。可以是:
+ - 单个字符(如'@')
+ - 字符数组
+ - 正则表达式
+
+
+ 匹配触发器前字符的模式。
+ - **示例:** `/^\s?$/` 匹配行首或空格
+
+
+ 控制触发器何时激活的自定义查询函数。
+
+
+
+
+## 钩子函数
+
+### useComboboxInput
+
+管理组合框输入行为和键盘交互的钩子。
+
+
+
+
+ 输入元素的引用。
+
+
+ 挂载时自动聚焦输入。
+ - **默认:** `true`
+
+
+ 方向键取消输入。
+ - **默认:** `true`
+
+
+ 起始位置退格键取消输入。
+ - **默认:** `true`
+
+
+ 失去焦点时取消输入。
+ - **默认:** `true`
+
+
+ 取消选择时取消输入。
+ - **默认:** `true`
+
+
+ Escape键取消输入。
+ - **默认:** `true`
+
+
+ 当前光标位置状态。
+
+
+ 将撤销/重做转发给编辑器。
+ - **默认:** `true`
+
+
+ 输入取消时的回调函数。
+
+
+
+
+
+ 取消输入的函数。
+
+
+ 输入元素的属性。
+
+
+ 移除输入节点的函数。
+
+
+
+
+### useHTMLInputCursorState
+
+跟踪HTML输入元素中光标位置的钩子。
+
+
+
+
+ 要跟踪的输入元素的引用。
+
+
+
+
+
+ 光标是否在输入起始位置。
+
+
+ 光标是否在输入结束位置。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(combobox)/combobox.mdx b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/combobox.mdx
new file mode 100644
index 0000000000..1a69b717a6
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/combobox.mdx
@@ -0,0 +1,320 @@
+---
+title: Combobox
+docs:
+ - route: /docs/components/inline-combobox
+ title: Inline Combobox
+---
+
+
+
+
+Insert mentions for users, pages, or any reference with `@`
+
+
+
+Quick access to editor commands and blocks with `/`
+
+
+
+Insert emojis with autocomplete using `:`
+
+
+
+
+
+
+## Features
+
+- Utilities for creating trigger-based combobox functionality
+- Configurable trigger characters and patterns
+- Keyboard navigation and selection handling
+
+
+
+## Create a Combobox Plugin
+
+
+
+### Installation
+
+```bash
+npm install @platejs/combobox
+```
+
+### Create Input Plugin
+
+First, create an input plugin that will be inserted when the trigger is activated:
+
+```tsx
+import { createSlatePlugin } from 'platejs';
+
+const TagInputPlugin = createSlatePlugin({
+ key: 'tag_input',
+ editOnly: true,
+ node: {
+ isElement: true,
+ isInline: true,
+ isVoid: true,
+ },
+});
+```
+
+### Create Main Plugin
+
+Create your main plugin using `withTriggerCombobox`:
+
+```tsx
+import { createTSlatePlugin, type PluginConfig } from 'platejs';
+import {
+ type TriggerComboboxPluginOptions,
+ withTriggerCombobox
+} from '@platejs/combobox';
+
+type TagConfig = PluginConfig<'tag', TriggerComboboxPluginOptions>;
+
+export const TagPlugin = createTSlatePlugin({
+ key: 'tag',
+ node: { isElement: true, isInline: true, isVoid: true },
+ options: {
+ trigger: '#',
+ triggerPreviousCharPattern: /^\s?$/,
+ createComboboxInput: () => ({
+ children: [{ text: '' }],
+ type: 'tag_input',
+ }),
+ },
+ plugins: [TagInputPlugin],
+}).overrideEditor(withTriggerCombobox);
+```
+
+- `node.isElement`: Defines this as an element node (not text)
+- `node.isInline`: Makes the tag element inline (not block)
+- `node.isVoid`: Prevents editing inside the tag element
+- `options.trigger`: Character that triggers the combobox (in this case `#`)
+- `options.triggerPreviousCharPattern`: RegExp pattern that must match the character before the trigger. `/^\s?$/` allows the trigger at the start of a line or after whitespace
+- `options.createComboboxInput`: Function that creates the input element node when the trigger is activated
+
+### Create Component
+
+Create the input element component using `InlineCombobox`:
+
+```tsx
+import { PlateElement, useFocused, useReadOnly, useSelected } from 'platejs/react';
+import {
+ InlineCombobox,
+ InlineComboboxContent,
+ InlineComboboxEmpty,
+ InlineComboboxInput,
+ InlineComboboxItem,
+} from '@/components/ui/inline-combobox';
+import { cn } from '@/lib/utils';
+
+const tags = [
+ { id: 'frontend', name: 'Frontend', color: 'blue' },
+ { id: 'backend', name: 'Backend', color: 'green' },
+ { id: 'design', name: 'Design', color: 'purple' },
+ { id: 'urgent', name: 'Urgent', color: 'red' },
+];
+
+export function TagInputElement({ element, ...props }) {
+ return (
+
+
+
+
+
+ No tags found
+
+ {tags.map((tag) => (
+ {
+ // Insert actual tag element
+ editor.tf.insertNodes({
+ type: 'tag',
+ tagId: tag.id,
+ children: [{ text: tag.name }],
+ });
+ }}
+ >
+
+ #{tag.name}
+
+ ))}
+
+
+
+ {props.children}
+
+ );
+}
+
+export function TagElement({ element, ...props }) {
+ const selected = useSelected();
+ const focused = useFocused();
+ const readOnly = useReadOnly();
+
+ return (
+
+ #{element.value}
+ {props.children}
+
+ );
+}
+```
+
+### Add to Editor
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { TagPlugin, TagInputPlugin } from './tag-plugin';
+import { TagElement, TagInputElement } from './tag-components';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ TagPlugin.configure({
+ options: {
+ triggerQuery: (editor) => {
+ // Disable in code blocks
+ return !editor.api.some({ match: { type: 'code_block' } });
+ },
+ },
+ }).withComponent(TagElement),
+ TagInputPlugin.withComponent(TagInputElement),
+ ],
+});
+```
+
+- `options.triggerQuery`: Optional function to conditionally enable/disable the trigger based on editor state
+
+
+
+## Examples
+
+
+
+
+
+## Options
+
+### TriggerComboboxPluginOptions
+
+Configuration options for trigger-based combobox plugins.
+
+
+
+
+ Function to create the input node when trigger is activated.
+
+
+ Character(s) that trigger the combobox. Can be:
+ - A single character (e.g. '@')
+ - An array of characters
+ - A regular expression
+
+
+ Pattern to match the character before trigger.
+ - **Example:** `/^\s?$/` matches start of line or space
+
+
+ Custom query function to control when trigger is active.
+
+
+
+
+## Hooks
+
+### useComboboxInput
+
+Hook for managing combobox input behavior and keyboard interactions.
+
+
+
+
+ Reference to the input element.
+
+
+ Auto focus the input when mounted.
+ - **Default:** `true`
+
+
+ Cancel on arrow keys.
+ - **Default:** `true`
+
+
+ Cancel on backspace at start.
+ - **Default:** `true`
+
+
+ Cancel on blur.
+ - **Default:** `true`
+
+
+ Cancel when deselected.
+ - **Default:** `true`
+
+
+ Cancel on escape key.
+ - **Default:** `true`
+
+
+ Current cursor position state.
+
+
+ Forward undo/redo to editor.
+ - **Default:** `true`
+
+
+ Callback when input is cancelled.
+
+
+
+
+
+ Function to cancel the input.
+
+
+ Props for the input element.
+
+
+ Function to remove the input node.
+
+
+
+
+### useHTMLInputCursorState
+
+Hook for tracking cursor position in an HTML input element.
+
+
+
+
+ Reference to the input element to track.
+
+
+
+
+
+ Whether cursor is at the start of input.
+
+
+ Whether cursor is at the end of input.
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(combobox)/emoji.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/emoji.cn.mdx
new file mode 100644
index 0000000000..d47557d69e
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/emoji.cn.mdx
@@ -0,0 +1,145 @@
+---
+title: Emoji
+docs:
+ - route: /docs/combobox
+ title: 组合框
+ - route: /docs/components/emoji-node
+ title: Emoji输入元素
+ - route: /docs/components/emoji-dropdown-menu
+ title: Emoji工具栏按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 通过自动补全建议内联插入表情符号
+- 通过输入`:`字符后跟表情名称触发(例如`:apple:` 🍎)
+- 可自定义的表情数据源和渲染方式
+
+
+
+## 套件使用方式
+
+
+
+### 安装
+
+最快捷的添加表情功能方式是使用`EmojiKit`,它包含预配置的`EmojiPlugin`和`EmojiInputPlugin`以及它们的[Plate UI](/docs/installation/plate-ui)组件。
+
+
+
+- [`EmojiInputElement`](/docs/components/emoji-node): 渲染表情输入组合框
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { EmojiKit } from '@/components/editor/plugins/emoji-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...EmojiKit,
+ ],
+});
+```
+
+
+
+## 手动配置方式
+
+
+
+### 安装
+
+```bash
+npm install @platejs/emoji @emoji-mart/data
+```
+
+### 添加插件
+
+```tsx
+import { EmojiPlugin, EmojiInputPlugin } from '@platejs/emoji/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ EmojiPlugin,
+ EmojiInputPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+```tsx
+import { EmojiPlugin, EmojiInputPlugin } from '@platejs/emoji/react';
+import { createPlateEditor } from 'platejs/react';
+import { EmojiInputElement } from '@/components/ui/emoji-node';
+import emojiMartData from '@emoji-mart/data';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ EmojiPlugin.configure({
+ options: {
+ data: emojiMartData,
+ trigger: ':',
+ triggerPreviousCharPattern: /^\s?$/,
+ createEmojiNode: (emoji) => ({ text: emoji.skins[0].native }),
+ },
+ }),
+ EmojiInputPlugin.withComponent(EmojiInputElement),
+ ],
+});
+```
+
+- `options.data`: 来自@emoji-mart/data包的表情数据
+- `options.trigger`: 触发表情组合框的字符(默认:`:`)
+- `options.triggerPreviousCharPattern`: 匹配触发字符前一个字符的正则表达式
+- `options.createEmojiNode`: 选择表情时创建表情节点的函数。默认插入Unicode字符作为文本
+- `withComponent`: 为表情输入组合框分配UI组件
+
+### 添加工具栏按钮
+
+您可以在[工具栏](/docs/toolbar)中添加[`EmojiToolbarButton`](/docs/components/emoji-toolbar-button)来插入表情符号。
+
+
+
+## 插件说明
+
+### EmojiPlugin
+
+提供表情功能的核心插件。扩展自[TriggerComboboxPluginOptions](/docs/combobox#triggercomboboxpluginoptions)。
+
+
+
+
+ 来自@emoji-mart/data包的表情数据。
+ - **默认值:** 内置表情库
+
+
+ 指定选择表情时插入节点的函数。
+ - **默认行为:** 插入包含表情Unicode字符的文本节点
+
+
+ 触发表情组合框的字符。
+ - **默认值:** `':'`
+
+
+ 匹配触发字符前一个字符的模式。
+ - **默认值:** `/^\s?$/`
+
+
+ 创建触发激活时输入元素的函数。
+
+
+
+
+### EmojiInputPlugin
+
+处理表情插入的输入行为。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(combobox)/emoji.mdx b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/emoji.mdx
new file mode 100644
index 0000000000..aaced666be
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/emoji.mdx
@@ -0,0 +1,145 @@
+---
+title: Emoji
+docs:
+ - route: /docs/combobox
+ title: Combobox
+ - route: /docs/components/emoji-node
+ title: Emoji Input Element
+ - route: /docs/components/emoji-dropdown-menu
+ title: Emoji Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Insert emojis inline with autocomplete suggestions
+- Triggered by `:` character followed by emoji name (e.g., `:apple:` 🍎)
+- Customizable emoji data source and rendering
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add emoji functionality is with the `EmojiKit`, which includes pre-configured `EmojiPlugin` and `EmojiInputPlugin` along with their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`EmojiInputElement`](/docs/components/emoji-node): Renders the emoji input combobox
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { EmojiKit } from '@/components/editor/plugins/emoji-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...EmojiKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/emoji @emoji-mart/data
+```
+
+### Add Plugins
+
+```tsx
+import { EmojiPlugin, EmojiInputPlugin } from '@platejs/emoji/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ EmojiPlugin,
+ EmojiInputPlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+```tsx
+import { EmojiPlugin, EmojiInputPlugin } from '@platejs/emoji/react';
+import { createPlateEditor } from 'platejs/react';
+import { EmojiInputElement } from '@/components/ui/emoji-node';
+import emojiMartData from '@emoji-mart/data';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ EmojiPlugin.configure({
+ options: {
+ data: emojiMartData,
+ trigger: ':',
+ triggerPreviousCharPattern: /^\s?$/,
+ createEmojiNode: (emoji) => ({ text: emoji.skins[0].native }),
+ },
+ }),
+ EmojiInputPlugin.withComponent(EmojiInputElement),
+ ],
+});
+```
+
+- `options.data`: Emoji data from @emoji-mart/data package
+- `options.trigger`: Character that triggers the emoji combobox (default: `:`)
+- `options.triggerPreviousCharPattern`: RegExp pattern for character before trigger
+- `options.createEmojiNode`: Function to create the emoji node when selected. Default inserts Unicode character as text
+- `withComponent`: Assigns the UI component for the emoji input combobox
+
+### Add Toolbar Button
+
+You can add [`EmojiToolbarButton`](/docs/components/emoji-toolbar-button) to your [Toolbar](/docs/toolbar) to insert emojis.
+
+
+
+## Plugins
+
+### EmojiPlugin
+
+Plugin for emoji functionality. Extends [TriggerComboboxPluginOptions](/docs/combobox#triggercomboboxpluginoptions).
+
+
+
+
+ The emoji data from @emoji-mart/data package.
+ - **Default:** Built-in emoji library
+
+
+ Function to specify the node inserted when an emoji is selected.
+ - **Default:** Inserts a text node with the emoji Unicode character
+
+
+ Character that triggers the emoji combobox.
+ - **Default:** `':'`
+
+
+ Pattern to match the character before trigger.
+ - **Default:** `/^\s?$/`
+
+
+ Function to create the input element when trigger is activated.
+
+
+
+
+### EmojiInputPlugin
+
+Handles the input behavior for emoji insertion.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(combobox)/slash-command.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/slash-command.cn.mdx
new file mode 100644
index 0000000000..22bb2cf357
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/slash-command.cn.mdx
@@ -0,0 +1,168 @@
+---
+title: 斜杠命令
+docs:
+ - route: /docs/combobox
+ title: 组合框
+ - route: components/slash-node
+ title: 斜杠输入元素
+ - route: https://pro.platejs.org/docs/components/slash-node
+ title: 斜杠输入元素
+---
+
+
+
+
+
+## 特性
+
+- 快速访问各种编辑器命令
+- 通过 `/` 字符触发
+- 支持键盘导航和筛选
+- 可自定义的命令组和选项
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+添加斜杠命令功能最快的方式是使用 `SlashKit`,它包含预配置的 `SlashPlugin` 和 `SlashInputPlugin` 以及它们的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`SlashInputElement`](/docs/components/slash-node): 渲染带有预定义选项的斜杠命令组合框
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { SlashKit } from '@/components/editor/plugins/slash-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...SlashKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/slash-command
+```
+
+### 添加插件
+
+```tsx
+import { SlashPlugin, SlashInputPlugin } from '@platejs/slash-command/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ SlashPlugin,
+ SlashInputPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+```tsx
+import { SlashPlugin, SlashInputPlugin } from '@platejs/slash-command/react';
+import { createPlateEditor } from 'platejs/react';
+import { SlashInputElement } from '@/components/ui/slash-node';
+import { KEYS } from 'platejs';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ SlashPlugin.configure({
+ options: {
+ trigger: '/',
+ triggerPreviousCharPattern: /^\s?$/,
+ triggerQuery: (editor) =>
+ !editor.api.some({
+ match: { type: editor.getType(KEYS.codeBlock) },
+ }),
+ },
+ }),
+ SlashInputPlugin.withComponent(SlashInputElement),
+ ],
+});
+```
+
+- `options.trigger`: 触发斜杠命令组合框的字符(默认: `/`)
+- `options.triggerPreviousCharPattern`: 匹配触发字符前字符的正则表达式
+- `options.triggerQuery`: 判断何时启用斜杠命令的函数
+- `withComponent`: 指定斜杠命令界面的UI组件
+
+
+
+## 使用方式
+
+如何使用斜杠命令:
+
+1. 在文档任意位置输入 `/` 打开斜杠菜单
+2. 开始输入以筛选选项,或使用方向键导航
+3. 按回车或点击选择选项
+4. 按ESC键不选择直接关闭菜单
+
+可用选项包括:
+- 文本块(段落、标题)
+- 列表(项目符号、编号、待办事项)
+- 高级块(表格、代码块、标注)
+- 行内元素(日期、公式)
+
+
+ 使用关键词快速查找选项。例如输入 '/ul' 查找项目符号列表,或 '/h1' 查找一级标题。
+
+
+## Plate Plus
+
+
+
+## 插件
+
+### SlashPlugin
+
+实现斜杠命令功能的插件。扩展自 [TriggerComboboxPluginOptions](/docs/combobox#triggercomboboxpluginoptions)。
+
+
+
+
+ 触发斜杠命令组合框的字符。
+ - **默认值:** `'/'`
+
+
+ 匹配触发字符前字符的正则表达式。
+ - **默认值:** `/^\s?$/`
+
+
+ 创建组合框输入元素的函数。
+ - **默认值:**
+ ```tsx
+ () => ({
+ children: [{ text: '' }],
+ type: KEYS.slashInput,
+ });
+ ```
+
+
+ 判断何时启用斜杠命令的函数。可用于在代码块等特定上下文中禁用功能。
+
+
+
+
+### SlashInputPlugin
+
+处理斜杠命令插入的输入行为。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(combobox)/slash-command.mdx b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/slash-command.mdx
new file mode 100644
index 0000000000..703c4036d5
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(combobox)/slash-command.mdx
@@ -0,0 +1,168 @@
+---
+title: Slash Command
+docs:
+ - route: https://pro.platejs.org/docs/examples/slash-command
+ title: Plus
+ - route: /docs/combobox
+ title: Combobox
+ - route: /docs/components/slash-node
+ title: Slash Nodes
+---
+
+
+
+
+
+## Features
+
+- Quick access to various editor commands
+- Triggered by `/` character
+- Keyboard navigation and filtering
+- Customizable command groups and options
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add slash command functionality is with the `SlashKit`, which includes pre-configured `SlashPlugin` and `SlashInputPlugin` along with their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`SlashInputElement`](/docs/components/slash-node): Renders the slash command combobox with predefined options
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { SlashKit } from '@/components/editor/plugins/slash-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...SlashKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/slash-command
+```
+
+### Add Plugins
+
+```tsx
+import { SlashPlugin, SlashInputPlugin } from '@platejs/slash-command/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SlashPlugin,
+ SlashInputPlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+```tsx
+import { SlashPlugin, SlashInputPlugin } from '@platejs/slash-command/react';
+import { createPlateEditor } from 'platejs/react';
+import { SlashInputElement } from '@/components/ui/slash-node';
+import { KEYS } from 'platejs';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SlashPlugin.configure({
+ options: {
+ trigger: '/',
+ triggerPreviousCharPattern: /^\s?$/,
+ triggerQuery: (editor) =>
+ !editor.api.some({
+ match: { type: editor.getType(KEYS.codeBlock) },
+ }),
+ },
+ }),
+ SlashInputPlugin.withComponent(SlashInputElement),
+ ],
+});
+```
+
+- `options.trigger`: Character that triggers the slash command combobox (default: `/`)
+- `options.triggerPreviousCharPattern`: RegExp pattern for character before trigger
+- `options.triggerQuery`: Function to determine when slash commands should be enabled
+- `withComponent`: Assigns the UI component for the slash command interface
+
+
+
+## Usage
+
+How to use slash commands:
+
+1. Type `/` anywhere in your document to open the slash menu
+2. Start typing to filter options or use arrow keys to navigate
+3. Press Enter or click to select an option
+4. Press Escape to close the menu without selecting
+
+Available options include:
+- Text blocks (paragraphs, headings)
+- Lists (bulleted, numbered, to-do)
+- Advanced blocks (tables, code blocks, callouts)
+- Inline elements (dates, equations)
+
+
+ Use keywords to quickly find options. For example, type '/ul' for Bulleted List or '/h1' for Heading 1.
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### SlashPlugin
+
+Plugin for slash command functionality. Extends [TriggerComboboxPluginOptions](/docs/combobox#triggercomboboxpluginoptions).
+
+
+
+
+ Character that triggers slash command combobox.
+ - **Default:** `'/'`
+
+
+ RegExp to match character before trigger.
+ - **Default:** `/^\s?$/`
+
+
+ Function to create combobox input element.
+ - **Default:**
+ ```tsx
+ () => ({
+ children: [{ text: '' }],
+ type: KEYS.slashInput,
+ });
+ ```
+
+
+ Function to determine when slash commands should be enabled. Useful for disabling in certain contexts like code blocks.
+
+
+
+
+### SlashInputPlugin
+
+Handles the input behavior for slash command insertion.
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(utils)/exit-break.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/(utils)/exit-break.cn.mdx
new file mode 100644
index 0000000000..51e6d83dbd
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(utils)/exit-break.cn.mdx
@@ -0,0 +1,203 @@
+---
+title: 退出块结构
+---
+
+
+
+
+
+## 功能特性
+
+- 通过快捷键从嵌套块结构(如代码块、表格、列)中退出
+- 根据块层级自动确定合适的退出位置
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用 `ExitBreakKit`,它包含预配置的 `ExitBreakPlugin` 及键盘快捷键。
+
+
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { ExitBreakKit } from '@/components/editor/plugins/exit-break-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...ExitBreakKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 添加插件
+
+```tsx
+import { ExitBreakPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ExitBreakPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+```tsx
+import { ExitBreakPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ExitBreakPlugin.configure({
+ shortcuts: {
+ insert: { keys: 'mod+enter' },
+ insertBefore: { keys: 'mod+shift+enter' },
+ },
+ }),
+ ],
+});
+```
+
+- `shortcuts.insert`: 定义退出并在之后插入新块的键盘[快捷键](/docs/plugin-shortcuts)
+- `shortcuts.insertBefore`: 定义退出并在之前插入新块的键盘[快捷键](/docs/plugin-shortcuts)
+
+
+
+## 快捷键
+
+
+
+ 退出当前块结构并在之后插入新块
+
+
+ 退出当前块结构并在之前插入新块
+
+
+
+## 插件
+
+### `ExitBreakPlugin`
+
+提供自动退出嵌套块结构的转换功能。该插件通过查找允许标准块兄弟节点的首个祖先节点来确定合适的退出位置。
+
+
+
+
+ 键盘快捷键配置
+
+
+ 退出并在之后插入块的快捷键
+ - **示例:** `{ keys: 'mod+enter' }`
+
+
+ 退出并在之前插入块的快捷键
+ - **示例:** `{ keys: 'mod+shift+enter' }`
+
+
+
+
+
+
+## 工作原理
+
+退出块功能使用 [`isStrictSiblings`](/docs/api/core/plate-plugin#isstrictsiblings) 属性来确定退出嵌套结构时新块的插入位置。
+
+### 退出点判定
+
+触发退出块时:
+
+1. 从当前文本块开始(例如表格单元格内的段落)
+2. 向上遍历文档树检查每个祖先节点
+3. 找到第一个 `isStrictSiblings: false` 的祖先节点
+4. 在该祖先节点同级位置插入新段落
+
+### 示例
+
+**代码块:**
+```tsx
+ // ← 退出点(顶层块)
+ 代码| // ← 起始位置
+
+|
// ← 在此处插入新段落
+```
+
+**列中的表格(在表格层级退出):**
+```tsx
+// 第一次退出
+
+
+ // ← 退出点(isStrictSiblings: false)
+ // isStrictSiblings: true
+ // isStrictSiblings: true
+ 内容|
// ← 起始位置
+
+
+
+ |
// ← 在此处插入新段落
+
+
+
+// 第二次退出
+ // ← 退出点(isStrictSiblings: false)
+ // isStrictSiblings: true
+
+ |
// ← 起始位置
+
+
+|
// ← 在此处插入新段落
+```
+
+### 自定义插件配置
+
+为自定义插件配置 [`isStrictSiblings`](/docs/api/core/plate-plugin#isstrictsiblings):
+
+```tsx
+// 表格结构
+const CustomTablePlugin = createSlatePlugin({
+ key: 'table',
+ node: {
+ isElement: true,
+ // isStrictSiblings: false (默认值) - 允许段落兄弟节点
+ },
+});
+
+const CustomTableRowPlugin = createSlatePlugin({
+ key: 'tr',
+ node: {
+ isElement: true,
+ isStrictSiblings: true, // 仅允许 tr 兄弟节点
+ },
+});
+
+const CustomTableCellPlugin = createSlatePlugin({
+ key: 'td',
+ node: {
+ isElement: true,
+ isStrictSiblings: true, // 仅允许 td/th 兄弟节点
+ },
+});
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(utils)/exit-break.mdx b/apps/www/content/docs/(plugins)/(functionality)/(utils)/exit-break.mdx
new file mode 100644
index 0000000000..d8e5d78dbf
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(utils)/exit-break.mdx
@@ -0,0 +1,202 @@
+---
+title: Exit Break
+---
+
+
+
+
+
+## Features
+
+- Exit from nested block structures (like code blocks, tables, columns) using keyboard shortcuts.
+- Automatically determines the appropriate exit point based on block hierarchy.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add exit break functionality is with the `ExitBreakKit`, which includes pre-configured `ExitBreakPlugin` with keyboard shortcuts.
+
+
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { ExitBreakKit } from '@/components/editor/plugins/exit-break-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...ExitBreakKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Add Plugin
+
+```tsx
+import { ExitBreakPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ExitBreakPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+```tsx
+import { ExitBreakPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ExitBreakPlugin.configure({
+ shortcuts: {
+ insert: { keys: 'mod+enter' },
+ insertBefore: { keys: 'mod+shift+enter' },
+ },
+ }),
+ ],
+});
+```
+
+- `shortcuts.insert`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to exit and insert block after.
+- `shortcuts.insertBefore`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to exit and insert block before.
+
+
+
+## Keyboard Shortcuts
+
+
+
+ Exit the current block structure and insert a new block after.
+
+
+ Exit the current block structure and insert a new block before.
+
+
+
+## Plugins
+
+### `ExitBreakPlugin`
+
+Provides transforms to exit nested block structures automatically. The plugin determines the appropriate exit point by finding the first ancestor that allows standard block siblings.
+
+
+
+
+ Keyboard shortcuts configuration.
+
+
+ Shortcut to exit and insert block after.
+ - **Example:** `{ keys: 'mod+enter' }`
+
+
+ Shortcut to exit and insert block before.
+ - **Example:** `{ keys: 'mod+shift+enter' }`
+
+
+
+
+
+
+## How Exit Break Works
+
+Exit break uses the [`isStrictSiblings`](/docs/api/core/plate-plugin#isstrictsiblings) property to determine where to insert new blocks when exiting nested structures.
+
+### Exit Point Determination
+
+When you trigger exit break:
+
+1. Starts from the current text block (e.g., paragraph inside a table cell)
+2. Traverses up the document tree looking at each ancestor
+3. Finds the first ancestor with `isStrictSiblings: false`
+4. Inserts a new paragraph as a sibling to that ancestor
+
+### Examples
+
+**Code Block:**
+```tsx
+ // ← Exit point (top-level block)
+ code| // ← Starting position
+
+|
// ← New paragraph inserted here
+```
+
+**Table in Column (exits at table level):**
+```tsx
+// First exit
+
+
+ // ← Exit point (isStrictSiblings: false)
+ // isStrictSiblings: true
+ // isStrictSiblings: true
+ content|
// ← Starting position
+
+
+
+ |
// ← New paragraph inserted here
+
+
+
+// Second exit
+ // ← Exit point (isStrictSiblings: false)
+ // isStrictSiblings: true
+
+ |
// ← Starting position
+
+
+|
// ← New paragraph inserted here
+```
+
+### Custom Plugin Configuration
+
+Configure [`isStrictSiblings`](/docs/api/core/plate-plugin#isstrictsiblings) for custom plugins:
+
+```tsx
+// Table structure
+const CustomTablePlugin = createSlatePlugin({
+ key: 'table',
+ node: {
+ isElement: true,
+ // isStrictSiblings: false (default) - allows paragraph siblings
+ },
+});
+
+const CustomTableRowPlugin = createSlatePlugin({
+ key: 'tr',
+ node: {
+ isElement: true,
+ isStrictSiblings: true, // Only allows tr siblings
+ },
+});
+
+const CustomTableCellPlugin = createSlatePlugin({
+ key: 'td',
+ node: {
+ isElement: true,
+ isStrictSiblings: true, // Only allows td/th siblings
+ },
+});
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(utils)/forced-layout.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/(utils)/forced-layout.cn.mdx
new file mode 100644
index 0000000000..c08e2b4d75
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(utils)/forced-layout.cn.mdx
@@ -0,0 +1,53 @@
+---
+title: 强制布局
+---
+
+
+
+## 功能特性
+
+- 自动确保使用指定元素以维持文档结构(例如,第一个块必须是H1元素)。
+- 如需强制尾部块为特定类型,请参阅[尾部块](/docs/trailing-block)。
+
+
+
+## 使用方法
+
+```tsx
+import { NormalizeTypesPlugin } from 'platejs';
+
+const plugins = [
+ // ...其他插件
+ NormalizeTypesPlugin.configure({
+ options: {
+ rules: [{ path: [0], strictType: 'h1' }],
+ },
+ }),
+];
+```
+
+## 插件
+
+### NormalizeTypesPlugin
+
+
+
+
+用于规范化类型的规则对象数组。
+
+- **默认值:** `[]`
+
+
+
+
+
+ 规则应用的路径。
+
+
+ 强制指定路径节点的类型。
+
+
+ 如果未提供`strictType`,则插入节点的类型。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(utils)/forced-layout.mdx b/apps/www/content/docs/(plugins)/(functionality)/(utils)/forced-layout.mdx
new file mode 100644
index 0000000000..417a8d6343
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(utils)/forced-layout.mdx
@@ -0,0 +1,53 @@
+---
+title: Forced Layout
+---
+
+
+
+## Features
+
+- Automatically ensures the use of specified elements as required to maintain document structure (e.g., first block should be an H1 element).
+- To force a trailing block of a specific type, see [Trailing Block](/docs/trailing-block).
+
+
+
+## Usage
+
+```tsx
+import { NormalizeTypesPlugin } from 'platejs';
+
+const plugins = [
+ // ...otherPlugins
+ NormalizeTypesPlugin.configure({
+ options: {
+ rules: [{ path: [0], strictType: 'h1' }],
+ },
+ }),
+];
+```
+
+## Plugins
+
+### NormalizeTypesPlugin
+
+
+
+
+An array of rule objects for normalizing types.
+
+- **Default:** `[]`
+
+
+
+
+
+ Path where the rule applies.
+
+
+ Force the type of the node at the given path.
+
+
+ Type of inserted node if `strictType` is not provided.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(utils)/single-block.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/(utils)/single-block.cn.mdx
new file mode 100644
index 0000000000..d317f2bd55
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(utils)/single-block.cn.mdx
@@ -0,0 +1,59 @@
+---
+title: 单行/单块限制
+---
+
+
+
+
+
+## 功能特性
+
+- **SingleLinePlugin**: 将编辑器限制为单行文本,自动移除所有换行符
+- **SingleBlockPlugin**: 将编辑器限制为单个块,换行符转为软换行
+- 通过标准化处理防止创建多个块
+- 过滤粘贴内容中的换行字符
+- 适用于标题、标签、评论或受限文本输入场景
+
+
+
+## 手动使用
+
+
+
+### 添加插件
+
+```tsx
+import { SingleLinePlugin, SingleBlockPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ SingleLinePlugin, // 用于单行文本
+ // 或
+ SingleBlockPlugin, // 用于支持软换行的单块文本
+ ],
+});
+```
+
+
+
+## 插件说明
+
+### `SingleLinePlugin`
+
+通过移除所有换行符并合并多个块,将编辑器内容限制为单行文本的插件。
+
+**核心行为:**
+- 阻止`insertBreak`和`insertSoftBreak`操作
+- 过滤换行字符
+- 将多个块合并到首个块中(无分隔符)
+
+### `SingleBlockPlugin`
+
+在保留换行符的同时,将编辑器内容限制为单个块的插件。
+
+**核心行为:**
+- 将`insertBreak`转换为`insertSoftBreak`
+- 用`\n`分隔符合并多个块到首个块
+- 保留文本内容中现有的换行字符
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(utils)/single-block.mdx b/apps/www/content/docs/(plugins)/(functionality)/(utils)/single-block.mdx
new file mode 100644
index 0000000000..d3bd11cafc
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(utils)/single-block.mdx
@@ -0,0 +1,59 @@
+---
+title: Single Block
+---
+
+
+
+
+
+## Features
+
+- **SingleLinePlugin**: Restricts editor to a single line of text with all line breaks removed
+- **SingleBlockPlugin**: Restricts editor to a single block with line breaks converted to soft breaks
+- Prevents creation of multiple blocks through normalization
+- Filters out line break characters from pasted content
+- Suitable for titles, labels, comments, or constrained text inputs
+
+
+
+## Manual Usage
+
+
+
+### Add Plugins
+
+```tsx
+import { SingleLinePlugin, SingleBlockPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SingleLinePlugin, // For single-line text
+ // OR
+ SingleBlockPlugin, // For single-block text with soft breaks
+ ],
+});
+```
+
+
+
+## Plugins
+
+### `SingleLinePlugin`
+
+Plugin that restricts editor content to a single line of text by removing all line breaks and merging multiple blocks.
+
+**Key behaviors:**
+- Prevents `insertBreak` and `insertSoftBreak` operations
+- Filters out line break characters
+- Merges multiple blocks into the first block without separators
+
+### `SingleBlockPlugin`
+
+Plugin that restricts editor content to a single block while preserving line breaks.
+
+**Key behaviors:**
+- Converts `insertBreak` to `insertSoftBreak`
+- Merges multiple blocks into the first block with `\n` separators
+- Preserves existing line break characters in text content
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(utils)/trailing-block.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/(utils)/trailing-block.cn.mdx
new file mode 100644
index 0000000000..12edb5f34d
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(utils)/trailing-block.cn.mdx
@@ -0,0 +1,97 @@
+---
+title: 尾部块
+---
+
+
+
+
+
+## 功能特性
+
+- 确保文档末尾始终存在特定类型的块
+
+
+
+## 手动使用
+
+
+
+### 添加插件
+
+```tsx
+import { TrailingBlockPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ TrailingBlockPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+该插件开箱即用,具有合理的默认配置,但也可以针对特定用例进行配置:
+
+```tsx
+import { TrailingBlockPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ TrailingBlockPlugin.configure({
+ options: {
+ type: 'p', // 段落块
+ exclude: ['blockquote'], // 不在这些类型后添加
+ },
+ }),
+ ],
+});
+```
+
+**配置选项:**
+- `type`: 要插入的尾部块类型(默认为段落)
+- `exclude`: 不应触发尾部块插入的块类型数组
+- `allow`: 允许的块类型数组(与exclude互斥)
+- `filter`: 自定义函数用于确定何时添加尾部块
+
+
+
+## 插件
+
+### `TrailingBlockPlugin`
+
+确保在文档末尾或指定嵌套层级始终存在特定块类型的插件。
+
+**核心行为:**
+- 当最后一个节点不符合预期类型时自动添加尾部块
+- 通过编辑器规范化机制维护文档结构
+- 通过配置`level`选项支持嵌套结构
+- 防止空文档,确保至少存在一个块
+- 遵循过滤选项控制尾部块的添加时机
+
+
+
+
+ 应添加尾部节点的层级,第一层级为0。
+ - **默认值:** `0`
+
+
+ 要插入的尾部块类型。
+ - **默认值:** `'p'` (段落)
+
+
+ 过滤匹配这些类型的节点。只有这些类型会被视为有效的尾部块。
+ - **默认值:** `[]` (允许所有类型)
+
+
+ 过滤不匹配这些类型的节点。这些类型不会触发尾部块插入。
+ - **默认值:** `[]` (不排除任何类型)
+
+
+ 自定义过滤函数,用于确定节点是否应触发尾部块插入。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/(utils)/trailing-block.mdx b/apps/www/content/docs/(plugins)/(functionality)/(utils)/trailing-block.mdx
new file mode 100644
index 0000000000..caca56f255
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/(utils)/trailing-block.mdx
@@ -0,0 +1,97 @@
+---
+title: Trailing Block
+---
+
+
+
+
+
+## Features
+
+- Ensures a specific block type is always present at the end of the document
+
+
+
+## Manual Usage
+
+
+
+### Add Plugin
+
+```tsx
+import { TrailingBlockPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ TrailingBlockPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+The plugin works out of the box with sensible defaults, but can be configured for specific use cases:
+
+```tsx
+import { TrailingBlockPlugin } from 'platejs';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ TrailingBlockPlugin.configure({
+ options: {
+ type: 'p', // Paragraph block
+ exclude: ['blockquote'], // Don't add after these types
+ },
+ }),
+ ],
+});
+```
+
+**Configuration options:**
+- `type`: The block type to insert as the trailing block (defaults to paragraph)
+- `exclude`: Array of block types that should not trigger trailing block insertion
+- `allow`: Array of block types that are allowed (alternative to exclude)
+- `filter`: Custom function to determine when to add trailing blocks
+
+
+
+## Plugins
+
+### `TrailingBlockPlugin`
+
+Plugin that ensures a specific block type is always present at the end of the document or at a specified nesting level.
+
+**Key behaviors:**
+- Automatically adds a trailing block when the last node doesn't match the expected type
+- Works through editor normalization to maintain document structure
+- Supports nested structures by configuring the `level` option
+- Prevents empty documents by ensuring at least one block exists
+- Respects filtering options to control when trailing blocks are added
+
+
+
+
+ Level where the trailing node should be added, with the first level being 0.
+ - **Default:** `0`
+
+
+ Type of the trailing block to insert.
+ - **Default:** `'p'` (paragraph)
+
+
+ Filter nodes matching these types. Only these types will be considered valid trailing blocks.
+ - **Default:** `[]` (all types allowed)
+
+
+ Filter nodes not matching these types. These types will not trigger trailing block insertion.
+ - **Default:** `[]` (no types excluded)
+
+
+ Custom filter function to determine if a node should trigger trailing block insertion.
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/autoformat.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/autoformat.cn.mdx
new file mode 100644
index 0000000000..2d4ea0e23c
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/autoformat.cn.mdx
@@ -0,0 +1,399 @@
+---
+title: 自动格式化
+description: 通过短代码和类Markdown快捷方式实现文本自动格式化。
+docs:
+ - route: /docs/basic-blocks
+ title: 基础块元素
+ - route: /docs/basic-marks
+ title: 基础标记
+ - route: /docs/code-block
+ title: 代码块
+ - route: /docs/list
+ title: 列表
+---
+
+
+
+
+
+## 功能特性
+
+- 块级元素的Markdown风格快捷方式(如`#`转换为H1,`>`转换为引用块)
+- 行内标记格式化(如`**粗体**`,`*斜体*`,`~~删除线~~`)
+- 智能标点转换(如`--`转为`—`,`...`转为`…`)
+- 数学符号和分数
+- 法律符号和箭头
+- 支持通过删除操作撤销自动格式化
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的添加自动格式化方式是使用`AutoformatKit`,它包含了全面的块级、标记级和文本替换的格式化规则。
+
+
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { AutoformatKit } from '@/components/editor/plugins/autoformat-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...AutoformatKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/autoformat
+```
+
+### 添加插件
+
+```tsx
+import { AutoformatPlugin } from '@platejs/autoformat';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ AutoformatPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+自定义自动格式化规则:
+
+```tsx
+import { AutoformatPlugin } from '@platejs/autoformat';
+
+AutoformatPlugin.configure({
+ options: {
+ rules: [
+ // 块级规则
+ {
+ match: '# ',
+ mode: 'block',
+ type: 'h1',
+ },
+ {
+ match: '> ',
+ mode: 'block',
+ type: 'blockquote',
+ },
+ // 标记规则
+ {
+ match: '**',
+ mode: 'mark',
+ type: 'bold',
+ },
+ {
+ match: '*',
+ mode: 'mark',
+ type: 'italic',
+ },
+ ],
+ enableUndoOnDelete: true,
+ },
+});
+```
+
+### 高级配置
+
+导入预定义的规则集实现全面自动格式化:
+
+```tsx
+import { AutoformatPlugin } from '@platejs/autoformat';
+import {
+ autoformatArrow,
+ autoformatLegal,
+ autoformatMath,
+ autoformatPunctuation,
+ autoformatSmartQuotes,
+} from '@platejs/autoformat';
+
+AutoformatPlugin.configure({
+ options: {
+ enableUndoOnDelete: true,
+ rules: [
+ // 自定义块级规则
+ {
+ match: '# ',
+ mode: 'block',
+ type: 'h1',
+ },
+ // 预定义规则集
+ ...autoformatSmartQuotes,
+ ...autoformatPunctuation,
+ ...autoformatArrow,
+ ...autoformatLegal,
+ ...autoformatMath,
+ ].map((rule) => ({
+ ...rule,
+ // 在代码块中禁用自动格式化
+ query: (editor) =>
+ !editor.api.some({
+ match: { type: 'code_block' },
+ }),
+ })),
+ },
+});
+```
+
+- `rules`: 定义触发条件和格式化操作的规则数组
+- `enableUndoOnDelete`: 允许通过退格键撤销自动格式化
+- `query`: 根据上下文条件启用/禁用规则的函数
+
+### 使用正则表达式
+
+对于更复杂的匹配模式,可以使用正则表达式:
+
+```tsx
+import { AutoformatPlugin } from '@platejs/autoformat';
+import { toggleList } from '@platejs/list';
+
+AutoformatPlugin.configure({
+ options: {
+ rules: [
+ {
+ match: [String.raw`^\d+\.$ `, String.raw`^\d+\)$ `],
+ matchByRegex: true,
+ mode: 'block',
+ type: 'list',
+ format: (editor, { matchString }) => {
+ const number = Number(matchString.match(/\d+/)?.[0]) || 1;
+ toggleList(editor, {
+ listRestartPolite: number,
+ listStyleType: 'ol',
+ });
+ },
+ },
+ ],
+ },
+});
+```
+
+- `matchByRegex`: 启用正则模式匹配(替代字符串完全匹配)
+- 注意:正则模式仅适用于`mode: 'block'`且只在块起始位置生效(`triggerAtBlockStart: true`)
+
+
+
+## 插件
+
+### `AutoformatPlugin`
+
+基于输入模式实现文本自动格式化的插件。
+
+
+
+
+ 触发规则列表。可以是以下类型之一:`AutoformatBlockRule`、`AutoformatMarkRule`、`AutoformatTextRule`。均继承自`AutoformatCommonRule`。
+ - **默认值:** `[]`
+
+
+ 启用删除时撤销自动格式化功能。
+ - **默认值:** `false`
+
+
+
+
+## 预定义规则集
+
+可导入以下预定义规则集:
+
+| 名称 | 描述 |
+| :----------------------------- | :----------------------------------- |
+| `autoformatSmartQuotes` | 转换`"文本"`为`"文本"` |
+| | 转换`'文本'`为`'文本'` |
+| `autoformatPunctuation` | 转换`--`为`—` |
+| | 转换`...`为`…` |
+| | 转换`>>`为`»` |
+| | 转换`<<`为`«` |
+| `autoformatArrow` | 转换`->`为`→` |
+| | 转换`<-`为`←` |
+| | 转换`=>`为`⇒` |
+| | 转换`<=`和`≤=`为`⇐` |
+| `autoformatLegal` | 转换`(tm)`和`(TM)`为`™` |
+| | 转换`(r)`和`(R)`为`®` |
+| | 转换`(c)`和`(C)`为`©` |
+| `autoformatLegalHtml` | 转换`™`为`™` |
+| | 转换`®`为`®` |
+| | 转换`©`为`©` |
+| | 转换`§`为`§` |
+| `autoformatComparison` | 转换`!>`为`≯` |
+| | 转换`!<`为`≮` |
+| | 转换`>=`为`≥` |
+| | 转换`<=`为`≤` |
+| | 转换`!>=`为`≱` |
+| | 转换`!<=`为`≰` |
+| `autoformatEquality` | 转换`!=`为`≠` |
+| | 转换`==`为`≡` |
+| | 转换`!==`和`≠=`为`≢` |
+| | 转换`~=`为`≈` |
+| | 转换`!~=`为`≉` |
+| `autoformatFraction` | 转换`1/2`为`½` |
+| | 转换`1/3`为`⅓` |
+| | ... |
+| | 转换`7/8`为`⅞` |
+| `autoformatDivision` | 转换`//`为`÷` |
+| `autoformatOperation` | 转换`+-`为`±` |
+| | 转换`%%`为`‰` |
+| | 转换`%%%`和`‰%`为`‱` |
+| | 包含`autoformatDivision`规则 |
+| `autoformatSubscriptNumbers` | 转换`~0`为`₀` |
+| | 转换`~1`为`₁` |
+| | ... |
+| | 转换`~9`为`₉` |
+| `autoformatSubscriptSymbols` | 转换`~+`为`₊` |
+| | 转换`~-`为`₋` |
+| `autoformatSuperscriptNumbers` | 转换`^0`为`⁰` |
+| | 转换`^1`为`¹` |
+| | ... |
+| | 转换`^9`为`⁹` |
+| `autoformatSuperscriptSymbols` | 转换`^+`为`⁺` |
+| | 转换`^-`为`⁻` |
+| `autoformatMath` | 包含`autoformatComparison`规则 |
+| | `autoformatEquality`规则 |
+| | `autoformatOperation`规则 |
+| | `autoformatFraction`规则 |
+| | `autoformatSubscriptNumbers`规则 |
+| | `autoformatSubscriptSymbols`规则 |
+| | `autoformatSuperscriptNumbers`规则 |
+| | `autoformatSuperscriptSymbols`规则 |
+
+## 类型定义
+
+### `AutoformatCommonRule`
+
+自动格式化规则的通用接口结构,与模式无关。
+
+
+
+
+ 当触发字符和光标前的文本匹配时应用规则。
+
+ - 对于`mode: 'block'`: 在光标前查找结束匹配项
+ - 对于`mode: 'text'`: 在光标前查找结束匹配项。如果`format`是数组,还需查找起始匹配项
+ - 对于`mode: 'mark'`: 查找起始和结束匹配项
+ - 注意:`'_*'`、`['_*']`和`{ start: '_*', end: '*_' }`等效
+ - `MatchRange`:
+
+
+
+ 范围的起始点
+
+
+ 范围的结束点
+
+
+
+
+ 触发自动格式化的字符
+
+
+ 为true时,在自动格式化后插入触发字符
+ - **默认值:** `false`
+
+
+ 允许自动格式化的查询函数
+
+
+
+ `insertText`文本内容
+
+
+
+
+
+
+### `AutoformatBlockRule`
+
+块级模式自动格式化规则接口。
+
+
+
+
+ 块级模式:设置块类型或自定义格式
+
+
+ 自动格式化规则的匹配模式
+
+
+ 对于`mode: 'block'`:设置块类型。如果定义了`format`,则忽略此字段
+
+
+ 是否仅在块起始位置触发
+ - **默认值:** `true`
+
+
+ 是否允许上方存在相同块类型时触发
+ - **默认值:** `false`
+
+
+ 在`format`前调用的函数。用于重置选中块
+
+
+ 自定义格式化函数
+
+
+
+
+### `AutoformatMarkRule`
+
+标记模式自动格式化规则接口。
+
+
+
+
+ 标记模式:在匹配项之间插入标记
+
+
+ 要添加的标记(可多个)
+
+
+ 字符串可修剪时是否仍进行格式化
+
+
+
+
+### `AutoformatTextRule`
+
+文本模式自动格式化规则接口。
+
+
+
+
+ 文本模式:插入文本
+
+
+ 自动格式化规则的匹配模式
+
+
+ 文本替换内容或格式化函数
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/autoformat.mdx b/apps/www/content/docs/(plugins)/(functionality)/autoformat.mdx
new file mode 100644
index 0000000000..c026f9f506
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/autoformat.mdx
@@ -0,0 +1,399 @@
+---
+title: Autoformat
+description: Automatic text formatting via shortcodes and markdown-like shortcuts.
+docs:
+ - route: /docs/basic-blocks
+ title: Basic Elements
+ - route: /docs/basic-marks
+ title: Basic Marks
+ - route: /docs/code-block
+ title: Code Block
+ - route: /docs/list
+ title: List
+---
+
+
+
+
+
+## Features
+
+- Markdown-style shortcuts for blocks (e.g., `#` to H1, `>` for blockquote).
+- Inline mark formatting (e.g., `**bold**`, `*italic*`, `~~strikethrough~~`).
+- Smart punctuation conversion (e.g., `--` to `—`, `...` to `…`).
+- Mathematical symbols and fractions.
+- Legal symbols and arrows.
+- Undo support on delete to reverse autoformatting.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add autoformatting is with the `AutoformatKit`, which includes comprehensive formatting rules for blocks, marks, and text replacements.
+
+
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { AutoformatKit } from '@/components/editor/plugins/autoformat-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...AutoformatKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/autoformat
+```
+
+### Add Plugin
+
+```tsx
+import { AutoformatPlugin } from '@platejs/autoformat';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ AutoformatPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+Configure autoformat with custom rules:
+
+```tsx
+import { AutoformatPlugin } from '@platejs/autoformat';
+
+AutoformatPlugin.configure({
+ options: {
+ rules: [
+ // Block rules
+ {
+ match: '# ',
+ mode: 'block',
+ type: 'h1',
+ },
+ {
+ match: '> ',
+ mode: 'block',
+ type: 'blockquote',
+ },
+ // Mark rules
+ {
+ match: '**',
+ mode: 'mark',
+ type: 'bold',
+ },
+ {
+ match: '*',
+ mode: 'mark',
+ type: 'italic',
+ },
+ ],
+ enableUndoOnDelete: true,
+ },
+});
+```
+
+### Advanced Configuration
+
+Import predefined rule sets for comprehensive autoformatting:
+
+```tsx
+import { AutoformatPlugin } from '@platejs/autoformat';
+import {
+ autoformatArrow,
+ autoformatLegal,
+ autoformatMath,
+ autoformatPunctuation,
+ autoformatSmartQuotes,
+} from '@platejs/autoformat';
+
+AutoformatPlugin.configure({
+ options: {
+ enableUndoOnDelete: true,
+ rules: [
+ // Custom block rules
+ {
+ match: '# ',
+ mode: 'block',
+ type: 'h1',
+ },
+ // Predefined rule sets
+ ...autoformatSmartQuotes,
+ ...autoformatPunctuation,
+ ...autoformatArrow,
+ ...autoformatLegal,
+ ...autoformatMath,
+ ].map((rule) => ({
+ ...rule,
+ // Disable autoformat in code blocks
+ query: (editor) =>
+ !editor.api.some({
+ match: { type: 'code_block' },
+ }),
+ })),
+ },
+});
+```
+
+- `rules`: Array of autoformat rules defining triggers and formatting actions.
+- `enableUndoOnDelete`: Allows undoing autoformat by pressing backspace.
+- `query`: Function to conditionally enable/disable rules based on context.
+
+### Using Regex Patterns
+
+For more complex matching patterns, you can use regular expressions:
+
+```tsx
+import { AutoformatPlugin } from '@platejs/autoformat';
+import { toggleList } from '@platejs/list';
+
+AutoformatPlugin.configure({
+ options: {
+ rules: [
+ {
+ match: [String.raw`^\d+\.$ `, String.raw`^\d+\)$ `],
+ matchByRegex: true,
+ mode: 'block',
+ type: 'list',
+ format: (editor, { matchString }) => {
+ const number = Number(matchString.match(/\d+/)?.[0]) || 1;
+ toggleList(editor, {
+ listRestartPolite: number,
+ listStyleType: 'ol',
+ });
+ },
+ },
+ ],
+ },
+});
+```
+
+- `matchByRegex`: Enables regex pattern matching instead of string equality.
+- Note that Regex patterns only work with `mode: 'block'` and are applied at block start (`triggerAtBlockStart: true`).
+
+
+
+## Plugins
+
+### `AutoformatPlugin`
+
+Plugin for automatic text formatting based on typing patterns.
+
+
+
+
+ A list of triggering rules. Can be one of: `AutoformatBlockRule`, `AutoformatMarkRule`, `AutoformatTextRule`. Each extends `AutoformatCommonRule`.
+ - **Default:** `[]`
+
+
+ Enable undo on delete to reverse autoformatting.
+ - **Default:** `false`
+
+
+
+
+## Predefined Rules
+
+You can import the following predefined rule sets:
+
+| Name | Description |
+| :----------------------------- | :----------------------------------- |
+| `autoformatSmartQuotes` | Converts `"text"` to `"text"`. |
+| | Converts `'text'` to `'text'`. |
+| `autoformatPunctuation` | Converts `--` to `—`. |
+| | Converts `...` to `…`. |
+| | Converts `>>` to `»`. |
+| | Converts `<<` to `«`. |
+| `autoformatArrow` | Converts `->` to `→`. |
+| | Converts `<-` to `←`. |
+| | Converts `=>` to `⇒`. |
+| | Converts `<=` and `≤=` to `⇐`. |
+| `autoformatLegal` | Converts `(tm)` and `(TM)` to `™`. |
+| | Converts `(r)` and `(R)` to `®`. |
+| | Converts `(c)` and `(C)` to `©`. |
+| `autoformatLegalHtml` | Converts `™` to `™`. |
+| | Converts `®` to `®`. |
+| | Converts `©` to `©`. |
+| | Converts `§` to `§`. |
+| `autoformatComparison` | Converts `!>` to `≯`. |
+| | Converts `!<` to `≮`. |
+| | Converts `>=` to `≥`. |
+| | Converts `<=` to `≤`. |
+| | Converts `!>=` to `≱`. |
+| | Converts `!<=` to `≰`. |
+| `autoformatEquality` | Converts `!=` to `≠`. |
+| | Converts `==` to `≡`. |
+| | Converts `!==` and `≠=` to `≢`. |
+| | Converts `~=` to `≈`. |
+| | Converts `!~=` to `≉`. |
+| `autoformatFraction` | Converts `1/2` to `½`. |
+| | Converts `1/3` to `⅓`. |
+| | ... |
+| | Converts `7/8` to `⅞`. |
+| `autoformatDivision` | Converts `//` to `÷`. |
+| `autoformatOperation` | Converts `+-` to `±`. |
+| | Converts `%%` to `‰`. |
+| | Converts `%%%` and `‰%` to `‱`. |
+| | `autoformatDivision` rules. |
+| `autoformatSubscriptNumbers` | Converts `~0` to `₀`. |
+| | Converts `~1` to `₁`. |
+| | ... |
+| | Converts `~9` to `₉`. |
+| `autoformatSubscriptSymbols` | Converts `~+` to `₊`. |
+| | Converts `~-` to `₋`. |
+| `autoformatSuperscriptNumbers` | Converts `^0` to `⁰`. |
+| | Converts `^1` to `¹`. |
+| | ... |
+| | Converts `^9` to `⁹`. |
+| `autoformatSuperscriptSymbols` | Converts `^+` to `⁺`. |
+| | Converts `^-` to `⁻`. |
+| `autoformatMath` | `autoformatComparison` rules |
+| | `autoformatEquality` rules |
+| | `autoformatOperation` rules |
+| | `autoformatFraction` rules |
+| | `autoformatSubscriptNumbers` rules |
+| | `autoformatSubscriptSymbols` rules |
+| | `autoformatSuperscriptNumbers` rules |
+| | `autoformatSuperscriptSymbols` rules |
+
+## Types
+
+### `AutoformatCommonRule`
+
+An interface for the common structure of autoformat rules, regardless of their mode.
+
+
+
+
+ The rule applies when the trigger and the text just before the cursor matches.
+
+ - For `mode: 'block'`: lookup for the end match(es) before the cursor.
+ - For `mode: 'text'`: lookup for the end match(es) before the cursor. If `format` is an array, also lookup for the start match(es).
+ - For `mode: 'mark'`: lookup for the start and end matches.
+ - Note: `'_*'`, `['_*']` and `{ start: '_*', end: '*_' }` are equivalent.
+ - `MatchRange`:
+
+
+
+ The starting point of the range.
+
+
+ The ending point of the range.
+
+
+
+
+ Triggering character to autoformat.
+
+
+ If true, insert the triggering character after autoformatting.
+ - **Default:** `false`
+
+
+ A query function to allow autoformat.
+
+
+
+ `insertText` text.
+
+
+
+
+
+
+### `AutoformatBlockRule`
+
+An interface for autoformat rules for block mode.
+
+
+
+
+ Block mode: set block type or custom format.
+
+
+ Pattern to match for the autoformat rule.
+
+
+ For `mode: 'block'`: set block type. If `format` is defined, this field is ignored.
+
+
+ Whether trigger should be at block start.
+ - **Default:** `true`
+
+
+ Whether to allow autoformat with same block type above.
+ - **Default:** `false`
+
+
+ Function called before `format`. Used to reset selected block.
+
+
+ Custom formatting function.
+
+
+
+
+### `AutoformatMarkRule`
+
+An interface for autoformat rules for mark mode.
+
+
+
+
+ Mark mode: insert mark(s) between matches.
+
+
+ Mark(s) to add.
+
+
+ Whether to format when string can be trimmed.
+
+
+
+
+### `AutoformatTextRule`
+
+An interface for autoformat rules for text mode.
+
+
+
+
+ Text mode: insert text.
+
+
+ Pattern to match for the autoformat rule.
+
+
+ Text replacement or formatting function.
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/block-menu.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/block-menu.cn.mdx
new file mode 100644
index 0000000000..bc64889715
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/block-menu.cn.mdx
@@ -0,0 +1,173 @@
+---
+title: 块菜单
+docs:
+ - route: /docs/components/block-context-menu
+ title: 块上下文菜单
+
+ - route: https://pro.platejs.org/docs/components/block-menu
+ title: 块菜单
+
+ - route: https://pro.platejs.org/docs/components/block-context-menu
+ title: 块上下文菜单
+
+---
+
+
+
+
+
+## 功能特性
+
+- 右键点击块元素可打开上下文菜单
+- 支持对块元素执行多种操作:转换类型、复制或删除等
+- 可自定义菜单项和操作行为
+- 支持键盘导航
+- 与块选择功能集成
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用 `BlockMenuKit`,它包含预配置的 `BlockMenuPlugin` 以及 `BlockSelectionPlugin` 和它们的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`BlockContextMenu`](/docs/components/block-context-menu): 渲染上下文菜单界面
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BlockMenuKit } from '@/components/editor/plugins/block-menu-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...BlockMenuKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装依赖
+
+```bash
+npm install @platejs/selection
+```
+
+### 添加插件
+
+块菜单功能需要同时安装 `BlockSelectionPlugin` 和 `BlockMenuPlugin` 才能正常工作。
+
+```tsx
+import { BlockSelectionPlugin, BlockMenuPlugin } from '@platejs/selection/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ BlockSelectionPlugin.configure({
+ options: {
+ enableContextMenu: true,
+ },
+ }),
+ BlockMenuPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+通过上下文菜单组件配置块菜单:
+
+```tsx
+import { BlockSelectionPlugin, BlockMenuPlugin } from '@platejs/selection/react';
+import { createPlateEditor } from 'platejs/react';
+import { BlockContextMenu } from '@/components/ui/block-context-menu';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ BlockSelectionPlugin.configure({
+ options: {
+ enableContextMenu: true,
+ },
+ }),
+ BlockMenuPlugin.configure({
+ render: { aboveEditable: BlockContextMenu },
+ }),
+ ],
+});
+```
+
+- `BlockSelectionPlugin.options.enableContextMenu`: 启用上下文菜单功能
+- `BlockMenuPlugin.render.aboveEditable`: 指定 [`BlockContextMenu`](/docs/components/block-context-menu) 组件来渲染上下文菜单
+
+#### 控制上下文菜单行为
+
+通过 `data-plate-open-context-menu` 属性可控制特定元素是否触发上下文菜单:
+
+```tsx
+
+ {children}
+
+```
+
+此属性常用于防止在 ` ` 组件的空白区域右键时弹出菜单。
+
+
+
+## Plate Plus 专业版
+
+
+
+## 插件系统
+
+### `BlockMenuPlugin`
+
+用于管理块菜单状态和上下文菜单功能的插件
+
+## API 接口
+
+### `api.blockMenu.hide`
+
+隐藏块菜单
+
+### `api.blockMenu.show`
+
+为指定块显示菜单
+
+
+
+
+ 要显示菜单的块ID,或使用'context'表示上下文菜单
+
+
+ 菜单显示位置坐标。若未提供,则仅更新openId状态
+
+
+
+
+### `api.blockMenu.showContextMenu`
+
+为指定块显示上下文菜单并自动选中该块
+
+
+
+
+ 要显示上下文菜单的块ID
+
+
+ 菜单显示位置坐标
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/block-menu.mdx b/apps/www/content/docs/(plugins)/(functionality)/block-menu.mdx
new file mode 100644
index 0000000000..f5a1baabbd
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/block-menu.mdx
@@ -0,0 +1,168 @@
+---
+title: Block Menu
+docs:
+ - route: https://pro.platejs.org/docs/examples/block-menu
+ title: Plus
+ - route: /docs/components/block-context-menu
+ title: Block Context Menu
+---
+
+
+
+
+
+## Features
+
+- Right-click on blocks to open a context menu.
+- Perform actions on blocks such as turning into different types, duplicating, or deleting.
+- Customizable menu items and actions.
+- Keyboard navigation support.
+- Block selection integration.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the block menu is with the `BlockMenuKit`, which includes the pre-configured `BlockMenuPlugin` along with `BlockSelectionPlugin` and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`BlockContextMenu`](/docs/components/block-context-menu): Renders the context menu interface.
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BlockMenuKit } from '@/components/editor/plugins/block-menu-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BlockMenuKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/selection
+```
+
+### Add Plugins
+
+The block menu requires both `BlockSelectionPlugin` and `BlockMenuPlugin` to function properly.
+
+```tsx
+import { BlockSelectionPlugin, BlockMenuPlugin } from '@platejs/selection/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ BlockSelectionPlugin.configure({
+ options: {
+ enableContextMenu: true,
+ },
+ }),
+ BlockMenuPlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+Configure the block menu with a context menu component:
+
+```tsx
+import { BlockSelectionPlugin, BlockMenuPlugin } from '@platejs/selection/react';
+import { createPlateEditor } from 'platejs/react';
+import { BlockContextMenu } from '@/components/ui/block-context-menu';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ BlockSelectionPlugin.configure({
+ options: {
+ enableContextMenu: true,
+ },
+ }),
+ BlockMenuPlugin.configure({
+ render: { aboveEditable: BlockContextMenu },
+ }),
+ ],
+});
+```
+
+- `BlockSelectionPlugin.options.enableContextMenu`: Enables the context menu functionality.
+- `BlockMenuPlugin.render.aboveEditable`: Assigns [`BlockContextMenu`](/docs/components/block-context-menu) to render the context menu.
+
+#### Controlling Context Menu Behavior
+
+To control the opening of the context menu for specific elements, use the `data-plate-open-context-menu` attribute:
+
+```tsx
+
+ {children}
+
+```
+
+This is commonly used to prevent right-clicking on the padding of the ` ` component from opening the menu.
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `BlockMenuPlugin`
+
+Plugin for block menu state management and context menu functionality.
+
+## API
+
+### `api.blockMenu.hide`
+
+Hides the block menu.
+
+### `api.blockMenu.show`
+
+Shows the block menu for a specific block.
+
+
+
+
+ The ID of the block to show the menu for, or 'context' for the context menu.
+
+
+ The position to show the menu at. If not provided, only the openId is updated.
+
+
+
+
+### `api.blockMenu.showContextMenu`
+
+Shows the context menu for a specific block and automatically selects that block.
+
+
+
+
+ The ID of the block to show the context menu for.
+
+
+ The position to show the context menu at.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(functionality)/block-placeholder.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/block-placeholder.cn.mdx
new file mode 100644
index 0000000000..f5e8a9a693
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/block-placeholder.cn.mdx
@@ -0,0 +1,146 @@
+---
+title: 块占位符
+description: 当块为空时显示占位内容。
+---
+
+
+
+
+
+## 特性
+
+- 为空白块添加可自定义的占位文本
+- 根据块类型显示不同的占位内容
+- 使用查询函数配置可见性规则
+
+
+
+## 套件使用方式
+
+
+
+### 安装
+
+最快速添加块占位符的方式是使用 `BlockPlaceholderKit`,它包含预配置的 `BlockPlaceholderPlugin`。
+
+
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BlockPlaceholderKit } from '@/components/editor/plugins/block-placeholder-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...BlockPlaceholderKit,
+ ],
+});
+```
+
+
+
+## 手动使用方式
+
+
+
+### 安装
+
+块占位符功能已包含在 Plate 核心包中。
+
+```tsx
+import { BlockPlaceholderPlugin } from 'platejs/react';
+```
+
+### 添加插件
+
+```tsx
+import { BlockPlaceholderPlugin } from 'platejs/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ BlockPlaceholderPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+为不同块类型配置占位内容:
+
+```tsx
+import { KEYS } from 'platejs';
+import { BlockPlaceholderPlugin } from 'platejs/react';
+
+BlockPlaceholderPlugin.configure({
+ options: {
+ placeholders: {
+ [KEYS.p]: '输入内容...',
+ [KEYS.h1]: '输入标题...',
+ [KEYS.blockquote]: '输入引用...',
+ [KEYS.codeBlock]: '输入代码...',
+ },
+ },
+});
+```
+
+### 高级配置
+
+自定义外观和可见性规则:
+
+```tsx
+import { KEYS } from 'platejs';
+import { BlockPlaceholderPlugin } from 'platejs/react';
+
+BlockPlaceholderPlugin.configure({
+ options: {
+ className: 'before:absolute before:cursor-text before:opacity-30 before:content-[attr(placeholder)]',
+ placeholders: {
+ [KEYS.p]: '输入内容...',
+ },
+ query: ({ path }) => {
+ // 仅在最外层块显示占位符
+ return path.length === 1;
+ },
+ },
+});
+```
+
+- `placeholders`: 块类型与占位文本的映射
+- `className`: 应用于带占位符块的 CSS 类
+- `query`: 决定哪些块应显示占位符的函数
+
+
+
+## 插件
+
+### `BlockPlaceholderPlugin`
+
+用于在空白块中显示占位文本的插件。
+
+当满足以下所有条件时,插件会显示占位符:
+
+1. 块为空(不含内容)
+2. 编辑器不为空(有其他内容)
+3. 编辑器处于聚焦状态
+4. 块符合查询函数条件
+5. 块类型匹配 placeholders 映射中的键
+
+
+
+
+ 插件键与占位文本字符串的映射
+ - **默认值:** `{ [KEYS.p]: '输入内容...' }`
+
+
+ 决定块是否应显示占位符的函数
+ - **默认值:** 对最外层块返回 true (`path.length === 1`)
+
+
+ 应用于带占位符块的 CSS 类
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/block-placeholder.mdx b/apps/www/content/docs/(plugins)/(functionality)/block-placeholder.mdx
new file mode 100644
index 0000000000..cfc600f250
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/block-placeholder.mdx
@@ -0,0 +1,146 @@
+---
+title: Block Placeholder
+description: Show placeholder when a block is empty.
+---
+
+
+
+
+
+## Features
+
+- Add customizable placeholder text to empty blocks.
+- Show different placeholders based on block type.
+- Configurable visibility rules using query functions.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add block placeholders is with the `BlockPlaceholderKit`, which includes the pre-configured `BlockPlaceholderPlugin`.
+
+
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BlockPlaceholderKit } from '@/components/editor/plugins/block-placeholder-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BlockPlaceholderKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+Block placeholders are included in the core Plate package.
+
+```tsx
+import { BlockPlaceholderPlugin } from 'platejs/react';
+```
+
+### Add Plugin
+
+```tsx
+import { BlockPlaceholderPlugin } from 'platejs/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ BlockPlaceholderPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+Configure placeholders for different block types:
+
+```tsx
+import { KEYS } from 'platejs';
+import { BlockPlaceholderPlugin } from 'platejs/react';
+
+BlockPlaceholderPlugin.configure({
+ options: {
+ placeholders: {
+ [KEYS.p]: 'Type something...',
+ [KEYS.h1]: 'Enter heading...',
+ [KEYS.blockquote]: 'Enter quote...',
+ [KEYS.codeBlock]: 'Enter code...',
+ },
+ },
+});
+```
+
+### Advanced Configuration
+
+Customize appearance and visibility rules:
+
+```tsx
+import { KEYS } from 'platejs';
+import { BlockPlaceholderPlugin } from 'platejs/react';
+
+BlockPlaceholderPlugin.configure({
+ options: {
+ className: 'before:absolute before:cursor-text before:opacity-30 before:content-[attr(placeholder)]',
+ placeholders: {
+ [KEYS.p]: 'Type something...',
+ },
+ query: ({ path }) => {
+ // Only show placeholders in root-level blocks
+ return path.length === 1;
+ },
+ },
+});
+```
+
+- `placeholders`: Map of block types to placeholder text.
+- `className`: CSS classes applied to blocks with placeholders.
+- `query`: Function to determine which blocks should show placeholders.
+
+
+
+## Plugins
+
+### `BlockPlaceholderPlugin`
+
+Plugin for displaying placeholder text in empty blocks.
+
+The plugin shows placeholders when all of these conditions are met:
+
+1. The block is empty (contains no content)
+2. The editor is not empty (has other content)
+3. The editor is focused
+4. The block matches the query function
+5. The block type matches a key in the placeholders map
+
+
+
+
+ A map of plugin keys to placeholder text strings.
+ - **Default:** `{ [KEYS.p]: 'Type something...' }`
+
+
+ A function that determines whether a block should show a placeholder.
+ - **Default:** Returns true for root-level blocks (`path.length === 1`)
+
+
+ CSS class to apply to blocks with placeholders.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(functionality)/block-selection.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/block-selection.cn.mdx
new file mode 100644
index 0000000000..8e80637464
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/block-selection.cn.mdx
@@ -0,0 +1,490 @@
+---
+title: 块选择
+docs:
+ - route: /docs/components/block-selection
+ title: 块选择
+---
+
+
+
+
+
+块选择功能允许用户选择和操作整个文本块,而不是单个单词或字符。
+
+## 特性
+
+- 通过单一操作选择整个块
+- 使用鼠标拖动或键盘快捷键进行多块选择
+- 对选中块执行复制、剪切和删除操作
+- 快速选择的键盘快捷键:
+ - `Cmd+A`:选择所有块
+ - 方向键:选择上方或下方的块
+- 可自定义选中块的样式
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+添加块选择功能最快的方法是使用 `BlockSelectionKit`,它包含预配置的 `BlockSelectionPlugin` 和 [`BlockSelection`](/docs/components/block-selection) UI 组件。
+
+
+
+- [`BlockSelection`](/docs/components/block-selection):在选中的块周围渲染选择矩形
+
+### 添加套件
+
+`BlockSelectionKit` 默认启用上下文菜单,并提供默认的 `isSelectable` 逻辑来排除常见的不可选择块,如代码行和表格单元格。
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BlockSelectionKit } from '@/components/editor/plugins/block-selection-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...BlockSelectionKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/selection
+```
+
+### 添加插件
+
+```tsx
+import { BlockSelectionPlugin } from '@platejs/selection/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ BlockSelectionPlugin,
+ ],
+});
+```
+
+将此插件放在任何覆盖 `selectAll` - `Cmd+A`(代码块、表格、列等)的其他插件之前,以避免冲突。
+
+#### 从选择中排除块
+
+您可以使用 `options.isSelectable` 控制哪些块是可选择的。此函数接收一个元素及其路径,如果块可选择则应返回 `true`。
+
+例如,排除代码行、列和表格单元格:
+
+```tsx
+import { BlockSelectionPlugin } from '@platejs/selection/react';
+
+BlockSelectionPlugin.configure({
+ options: {
+ isSelectable: (element, path) => {
+ if (['code_line', 'column', 'td'].includes(element.type)) {
+ return false;
+ }
+ // 排除表格行内的块
+ if (editor.api.block({ above: true, at: path, match: { type: 'tr' } })) {
+ return false;
+ }
+ return true;
+ },
+ },
+});
+```
+
+#### 自定义滚动行为
+
+如果您的编辑器位于可滚动容器内,您可能需要配置选择区域的边界和滚动速度。
+
+1. 为滚动容器添加 `id`,例如 `id={editor.meta.uid}`
+2. 在容器上设置 `position: relative`
+3. 使用 `areaOptions` 配置边界和滚动行为
+
+```ts
+BlockSelectionPlugin.configure({
+ options: {
+ areaOptions: {
+ boundaries: `#${editor.meta.uid}`,
+ container: `#${editor.meta.uid}`,
+ behaviour: {
+ scrolling: {
+ // 推荐速度,接近原生
+ speedDivider: 0.8,
+ },
+ // 开始选择区域的阈值
+ startThreshold: 4,
+ },
+ },
+ },
+});
+```
+
+#### 全页选择
+
+您可以通过添加 `data-plate-selectable` 属性为 ` ` 组件之外的元素启用块选择。
+
+```tsx
+
+
+```
+
+为了防止在点击某些元素(例如工具栏按钮)时取消选择块,请添加 `data-plate-prevent-unselect` 属性。
+
+```tsx
+
+```
+
+要在点击可选择区域之外时重置选择,您可以使用点击处理程序或直接调用 API:
+
+```tsx
+// 1. 直接调用 API
+editor.api.blockSelection.deselect();
+
+// 2. 点击外部处理程序
+const handleClickOutside = (event: MouseEvent) => {
+ if (!(event.target as HTMLElement).closest('[data-plate-selectable]')) {
+ editor.api.blockSelection.deselect();
+ }
+};
+```
+
+
+
+## 样式
+
+### 选择区域
+
+通过定位添加到编辑器容器的 `.slate-selection-area` 类来设置选择区域的样式。
+
+```css
+/* 使用 Tailwind CSS 实用类的示例 */
+'[&_.slate-selection-area]:border [&_.slate-selection-area]:border-primary [&_.slate-selection-area]:bg-primary/10'
+```
+
+### 选中元素
+
+使用 `useBlockSelected` 钩子确定块是否被选中。您可以渲染一个视觉指示器,如 [`BlockSelection`](/docs/components/block-selection) 组件,该组件专为此目的设计。
+
+Plate UI 使用 `render.belowRootNodes` 为所有可选择块渲染此组件:
+
+```tsx
+render: {
+ belowRootNodes: (props) => {
+ if (!props.className?.includes('slate-selectable')) return null;
+
+ return ;
+ },
+},
+```
+
+## 插件
+
+### `BlockSelectionPlugin`
+
+块选择功能的插件。
+
+
+
+
+ 选择区域的选项。查看 [SelectionJS 文档](https://github.com/Simonwep/selection-js) 获取所有可用选项。
+
+```ts
+{
+ boundaries: [`#${editor.meta.uid}`],
+ container: [`#${editor.meta.uid}`],
+ selectables: [`#${editor.meta.uid} .slate-selectable`],
+ selectionAreaClass: 'slate-selection-area',
+}
+```
+
+
+
+ 启用或禁用块选择的上下文菜单。
+ - **默认值:** `false`
+
+
+ 指示块选择当前是否处于活动状态。
+ - **默认值:** `false`
+
+
+ 在选择时处理键盘按下事件的函数。
+
+
+ 在块选择期间查询节点的选项。
+ - **默认值:** `{ maxLevel: 1 }`
+
+
+ 当前选中块的 ID 集合。
+ - **默认值:** `new Set()`
+
+
+ (内部) 当前选择中锚块的 ID。用于基于 Shift 的选择。
+ - **默认值:** `null`
+
+
+ 确定块元素是否可选择的函数。
+ - **默认值:** `() => true`
+
+
+
+
+## API
+
+### `api.blockSelection.add`
+
+将一个或多个块添加到选择中。
+
+
+
+
+ 要选择的块的 ID。
+
+
+
+
+### `api.blockSelection.clear`
+
+将选中的 ID 集合重置为空集。
+
+### `api.blockSelection.delete`
+
+从选择中移除一个或多个块。
+
+
+
+
+ 要从选择中移除的块的 ID。
+
+
+
+
+### `api.blockSelection.deselect`
+
+取消选择所有块并将 `isSelecting` 标志设置为 false。
+
+### `api.blockSelection.focus`
+
+聚焦块选择阴影输入。此输入处理选中块的复制、删除和粘贴事件。
+
+### `api.blockSelection.getNodes`
+
+获取编辑器中的选中块。
+
+
+
+ 选中块的条目数组。
+
+
+
+### `api.blockSelection.has`
+
+检查一个或多个块是否被选中。
+
+
+
+
+ 要检查的块的 ID。
+
+
+
+
+ 块是否被选中。
+
+
+
+
+### `api.blockSelection.isSelectable`
+
+根据插件选项 `isSelectable` 检查给定路径的块是否可选择。
+
+
+
+
+ 要检查的块元素。
+
+
+ 块元素的路径。
+
+
+
+ 块是否可选择。
+
+
+
+### `api.blockSelection.moveSelection`
+
+将选择向上或向下移动到下一个可选择的块。
+
+向上移动时:
+- 从最顶部选中的块获取上一个可选择块
+- 将其设置为新锚点
+- 清除先前选择并仅选择此块
+向下移动时:
+- 从最底部选中的块获取下一个可选择块
+- 将其设置为新锚点
+- 清除先前选择并仅选择此块
+
+
+
+
+ 移动选择的方向。
+
+
+
+
+### `api.blockSelection.selectAll`
+
+选择编辑器中的所有可选择块。
+
+### `api.blockSelection.set`
+
+将选择设置为一个或多个块,清除任何现有选择。
+
+
+
+
+ 要选择的块的 ID。
+
+
+
+
+### `api.blockSelection.shiftSelection`
+
+基于锚块扩展或收缩选择。
+
+对于 `Shift+ArrowDown`:
+- 如果锚点是最顶部:通过添加最底部下方的块向下扩展
+- 否则:从最顶部收缩(除非最顶部是锚点)
+对于 `Shift+ArrowUp`:
+- 如果锚点是最底部:通过添加最顶部上方的块向上扩展
+- 否则:从最底部收缩(除非最底部是锚点)
+锚块始终保持选中状态。如果未设置锚点,则默认为:
+- `Shift+ArrowUp` 的最底部块
+- `Shift+ArrowDown` 的最顶部块
+
+
+
+
+ 扩展/收缩选择的方向。
+
+
+
+
+## 转换
+
+### `tf.blockSelection.duplicate`
+
+复制选中的块。
+
+### `tf.blockSelection.removeNodes`
+
+从编辑器中移除选中的节点。
+
+### `tf.blockSelection.select`
+
+选择 `getNodes()` 返回的编辑器中的节点并重置选中的 ID。
+
+### `tf.blockSelection.setNodes`
+
+在选中的节点上设置属性。
+
+
+
+
+ 要在选中节点上设置的属性。
+
+
+ 设置节点的选项。
+
+
+
+
+### `tf.blockSelection.setTexts`
+
+在选中的节点上设置文本属性。
+
+
+
+
+ 要在选中节点上设置的文本属性。
+
+
+ 设置文本节点的选项,不包括 'at' 属性。
+
+
+
+
+## 钩子
+
+### `useBlockSelectable`
+
+提供使块元素可选择的属性的钩子,包括上下文菜单行为。
+
+
+
+
+ 要应用于块元素的属性。
+
+
+ 选择功能所需的类。
+ - **默认值:** `'slate-selectable'`
+
+
+ 处理右键上下文菜单行为:
+ - 为选中的块打开上下文菜单
+ - 为 void 元素打开
+ - 为具有 `data-plate-open-context-menu="true"` 的元素打开
+ - 使用 Shift 键为多选添加块到选择
+
+
+
+
+
+
+### `useBlockSelected`
+
+
+
+ 上下文块是否被选中。
+
+
+
+### `useBlockSelectionNodes`
+
+
+
+ 选中块的条目数组。
+
+
+
+### `useBlockSelectionFragment`
+
+
+
+ 选中块的节点数组。
+
+
+
+### `useBlockSelectionFragmentProp`
+
+
+
+ 选中块的片段属性。
+
+
+
+### `useSelectionArea`
+
+初始化和管理选择区域功能。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/block-selection.mdx b/apps/www/content/docs/(plugins)/(functionality)/block-selection.mdx
new file mode 100644
index 0000000000..afcfe2ae21
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/block-selection.mdx
@@ -0,0 +1,491 @@
+---
+title: Block Selection
+docs:
+ - route: /docs/components/block-selection
+ title: Block Selection
+---
+
+
+
+
+
+The Block Selection feature allows users to select and manipulate entire text blocks, as opposed to individual words or characters.
+
+## Features
+
+- Select entire blocks with a single action.
+- Multi-block selection using mouse drag or keyboard shortcuts.
+- Copy, cut, and delete operations on selected blocks.
+- Keyboard shortcuts for quick selection:
+ - `Cmd+A`: Select all blocks.
+ - Arrow keys: Select the block above or below.
+- Customizable styling for selected blocks.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add Block Selection is with the `BlockSelectionKit`, which includes the pre-configured `BlockSelectionPlugin` and the [`BlockSelection`](/docs/components/block-selection) UI component.
+
+
+
+- [`BlockSelection`](/docs/components/block-selection): Renders the selection rectangle around selected blocks.
+
+### Add Kit
+
+The `BlockSelectionKit` enables the context menu by default and provides a default `isSelectable` logic to exclude common non-selectable blocks like code lines and table cells.
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BlockSelectionKit } from '@/components/editor/plugins/block-selection-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BlockSelectionKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/selection
+```
+
+### Add Plugin
+
+```tsx
+import { BlockSelectionPlugin } from '@platejs/selection/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ BlockSelectionPlugin,
+ ],
+});
+```
+
+Put this plugin before any other plugins overriding `selectAll` – `Cmd+A` (code block, table, column, etc.) to avoid any conflicts.
+
+#### Excluding Blocks from Selection
+
+You can control which blocks are selectable using `options.isSelectable`. This function receives an element and its path, and should return `true` if the block is selectable.
+
+For example, to exclude code lines, columns, and table cells:
+
+```tsx
+import { BlockSelectionPlugin } from '@platejs/selection/react';
+
+BlockSelectionPlugin.configure({
+ options: {
+ isSelectable: (element, path) => {
+ if (['code_line', 'column', 'td'].includes(element.type)) {
+ return false;
+ }
+ // Exclude blocks inside table rows
+ if (editor.api.block({ above: true, at: path, match: { type: 'tr' } })) {
+ return false;
+ }
+ return true;
+ },
+ },
+});
+```
+
+#### Customizing Scroll Behavior
+
+If your editor is inside a scrollable container, you may need to configure the selection area's boundaries and scroll speed.
+
+1. Add an `id` to your scroll container, e.g., `id={editor.meta.uid}`.
+2. Set `position: relative` on the container.
+3. Use the `areaOptions` to configure the boundaries and scrolling behavior.
+
+```ts
+BlockSelectionPlugin.configure({
+ options: {
+ areaOptions: {
+ boundaries: `#${editor.meta.uid}`,
+ container: `#${editor.meta.uid}`,
+ behaviour: {
+ scrolling: {
+ // Recommended speed, close to native
+ speedDivider: 0.8,
+ },
+ // Threshold to start selection area
+ startThreshold: 4,
+ },
+ },
+ },
+});
+```
+
+#### Full Page Selection
+
+You can enable block selection for elements outside the ` ` component by adding the `data-plate-selectable` attribute.
+
+```tsx
+
+
+```
+
+To prevent unselecting blocks when clicking on certain elements (e.g., a toolbar button), add the `data-plate-prevent-unselect` attribute.
+
+```tsx
+
+```
+
+To reset the selection when clicking outside selectable areas, you can use a click handler or call the API directly:
+
+```tsx
+// 1. Direct API call
+editor.api.blockSelection.deselect();
+
+// 2. Click outside handler
+const handleClickOutside = (event: MouseEvent) => {
+ if (!(event.target as HTMLElement).closest('[data-plate-selectable]')) {
+ editor.api.blockSelection.deselect();
+ }
+};
+```
+
+
+
+## Styling
+
+### Selection Area
+
+Style the selection area by targeting the `.slate-selection-area` class, which is added to the editor container.
+
+```css
+/* Example using Tailwind CSS utility classes */
+'[&_.slate-selection-area]:border [&_.slate-selection-area]:border-primary [&_.slate-selection-area]:bg-primary/10'
+```
+
+### Selected Element
+
+Use the `useBlockSelected` hook to determine if a block is selected. You can render a visual indicator, like the [`BlockSelection`](/docs/components/block-selection) component, which is designed for this purpose.
+
+Plate UI renders this component for all selectable blocks using `render.belowRootNodes`:
+
+```tsx
+render: {
+ belowRootNodes: (props) => {
+ if (!props.className?.includes('slate-selectable')) return null;
+
+ return ;
+ },
+},
+```
+
+## Plugins
+
+### `BlockSelectionPlugin`
+
+Plugin for block selection functionality.
+
+
+
+
+ Options for the selection area. See [SelectionJS docs](https://github.com/Simonwep/selection-js) for all available options.
+
+```ts
+{
+ boundaries: [`#${editor.meta.uid}`],
+ container: [`#${editor.meta.uid}`],
+ selectables: [`#${editor.meta.uid} .slate-selectable`],
+ selectionAreaClass: 'slate-selection-area',
+}
+```
+
+
+
+ Enables or disables the context menu for block selection.
+ - **Default:** `false`
+
+
+ Indicates whether block selection is currently active.
+ - **Default:** `false`
+
+
+ A function to handle the keydown event when selecting.
+
+
+ Options for querying nodes during block selection.
+ - **Default:** `{ maxLevel: 1 }`
+
+
+ A set of IDs for the currently selected blocks.
+ - **Default:** `new Set()`
+
+
+ (Internal) The ID of the anchor block in the current selection. Used for shift-based selection.
+ - **Default:** `null`
+
+
+ Function to determine if a block element is selectable.
+ - **Default:** `() => true`
+
+
+
+
+## API
+
+### `api.blockSelection.add`
+
+Adds one or more blocks to the selection.
+
+
+
+
+ The ID(s) of the block(s) to be selected.
+
+
+
+
+### `api.blockSelection.clear`
+
+Resets the set of selected IDs to an empty set.
+
+### `api.blockSelection.delete`
+
+Removes one or more blocks from the selection.
+
+
+
+
+ The ID(s) of the block(s) to remove from selection.
+
+
+
+
+### `api.blockSelection.deselect`
+
+Deselects all blocks and sets the `isSelecting` flag to false.
+
+### `api.blockSelection.focus`
+
+Focuses the block selection shadow input. This input handles copy, delete, and paste events for selected blocks.
+
+### `api.blockSelection.getNodes`
+
+Gets the selected blocks in the editor.
+
+
+
+ Array of selected block entries.
+
+
+
+### `api.blockSelection.has`
+
+Checks if one or more blocks are selected.
+
+
+
+
+ The ID(s) of the block(s) to check.
+
+
+
+
+ Whether the block(s) are selected.
+
+
+
+
+### `api.blockSelection.isSelectable`
+
+Checks if a block at a given path is selectable based on the `isSelectable` plugin option.
+
+
+
+
+ Block element to check.
+
+
+ Path to the block element.
+
+
+
+ Whether the block is selectable.
+
+
+
+### `api.blockSelection.moveSelection`
+
+Moves the selection up or down to the next selectable block.
+
+When moving up:
+- Gets the previous selectable block from the top-most selected block
+- Sets it as the new anchor
+- Clears previous selection and selects only this block
+When moving down:
+- Gets the next selectable block from the bottom-most selected block
+- Sets it as the new anchor
+- Clears previous selection and selects only this block
+
+
+
+
+ Direction to move selection.
+
+
+
+
+### `api.blockSelection.selectAll`
+
+Selects all selectable blocks in the editor.
+
+### `api.blockSelection.set`
+
+Sets the selection to one or more blocks, clearing any existing selection.
+
+
+
+
+ The ID(s) of the block(s) to be selected.
+
+
+
+
+### `api.blockSelection.shiftSelection`
+
+Expands or shrinks the selection based on the anchor block.
+
+For `Shift+ArrowDown`:
+- If anchor is top-most: Expands down by adding block below bottom-most
+- Otherwise: Shrinks from top-most (unless top-most is the anchor)
+For `Shift+ArrowUp`:
+- If anchor is bottom-most: Expands up by adding block above top-most
+- Otherwise: Shrinks from bottom-most (unless bottom-most is the anchor)
+The anchor block always remains selected. If no anchor is set, it defaults to:
+- Bottom-most block for `Shift+ArrowUp`
+- Top-most block for `Shift+ArrowDown`
+
+
+
+
+ Direction to expand/shrink selection.
+
+
+
+
+## Transforms
+
+### `tf.blockSelection.duplicate`
+
+Duplicates the selected blocks.
+
+### `tf.blockSelection.removeNodes`
+
+Removes the selected nodes from the editor.
+
+### `tf.blockSelection.select`
+
+Selects the nodes returned by `getNodes()` in the editor and resets selected IDs.
+
+### `tf.blockSelection.setNodes`
+
+Sets properties on the selected nodes.
+
+
+
+
+ Properties to set on selected nodes.
+
+
+ Options for setting nodes.
+
+
+
+
+### `tf.blockSelection.setTexts`
+
+Sets text properties on the selected nodes.
+
+
+
+
+ Text properties to set on selected nodes.
+
+
+ Options for setting text nodes, excluding the 'at' property.
+
+
+
+
+## Hooks
+
+### `useBlockSelectable`
+
+A hook that provides props for making a block element selectable, including context menu behavior.
+
+
+
+
+ Props to be spread on the block element.
+
+
+ Required class for selection functionality.
+ - **Default:** `'slate-selectable'`
+
+
+ Handles right-click context menu behavior:
+ - Opens context menu for selected blocks
+ - Opens for void elements
+ - Opens for elements with `data-plate-open-context-menu="true"`
+ - Adds block to selection with Shift key for multi-select
+
+
+
+
+
+
+### `useBlockSelected`
+
+
+
+ Whether the context block is selected.
+
+
+
+### `useBlockSelectionNodes`
+
+
+
+ Array of selected block entries.
+
+
+
+### `useBlockSelectionFragment`
+
+
+
+ Array of selected block nodes.
+
+
+
+### `useBlockSelectionFragmentProp`
+
+
+
+ Fragment prop for selected blocks.
+
+
+
+### `useSelectionArea`
+
+Initialize and manage selection area functionality.
+
diff --git a/apps/www/content/docs/(plugins)/(functionality)/caption.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/caption.cn.mdx
new file mode 100644
index 0000000000..c3daffe9e5
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/caption.cn.mdx
@@ -0,0 +1,213 @@
+---
+title: 标题组件
+description: 为图片、视频、文件等媒体元素添加标题说明。
+docs:
+ - route: /docs/components/caption
+ title: 标题组件
+---
+
+
+
+
+
+## 功能特性
+
+- 为图片、视频、音频文件等媒体元素添加标题说明
+- 通过方向键在区块内选择标题
+- 使用文本区域组件进行内联标题编辑
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用`MediaKit`套件,它包含预配置的`CaptionPlugin`以及媒体插件和它们的[Plate UI](/docs/installation/plate-ui)组件。
+
+
+
+- [`Caption`](/docs/components/caption): 为媒体元素渲染标题组件
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { MediaKit } from '@/components/editor/plugins/media-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...MediaKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/caption
+```
+
+### 添加插件
+
+```tsx
+import { CaptionPlugin } from '@platejs/caption/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ CaptionPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+配置哪些媒体插件应支持标题功能:
+
+```tsx
+import { KEYS } from 'platejs';
+import { CaptionPlugin } from '@platejs/caption/react';
+import {
+ AudioPlugin,
+ FilePlugin,
+ ImagePlugin,
+ MediaEmbedPlugin,
+ VideoPlugin,
+} from '@platejs/media/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ImagePlugin,
+ VideoPlugin,
+ AudioPlugin,
+ FilePlugin,
+ MediaEmbedPlugin,
+ CaptionPlugin.configure({
+ options: {
+ query: {
+ allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],
+ },
+ },
+ }),
+ ],
+});
+```
+
+- `query.allow`: 支持标题功能的插件键名数组
+
+
+
+## 插件
+
+### `CaptionPlugin`
+
+为媒体元素添加标题功能的插件。
+
+
+
+
+ 配置哪些插件支持标题功能
+
+
+ 可添加标题的区块插件键名
+
+
+
+
+ 标题末尾的聚焦路径
+ - **默认值:** `null`
+
+
+ 标题起始的聚焦路径
+ - **默认值:** `null`
+
+
+ 当前可见标题的ID
+ - **默认值:** `null`
+
+
+
+
+## 类型
+
+### `TCaptionElement`
+
+继承自`TElement`。
+
+
+
+
+ 标题值,由子节点数组构成
+
+
+
+
+## 组件
+
+### ``
+
+
+
+
+ 标题组件的配置选项
+
+
+ 标题组件的状态
+
+
+ 表示标题的字符串
+
+
+ 标题组件是否被选中
+
+
+ 标题组件是否处于只读模式
+
+
+
+
+
+
+ 标题组件是否处于只读模式
+
+
+
+
+
+### ``
+
+
+
+
+ 标题文本区域的状态
+
+
+ 文本区域元素的引用
+
+
+ 文本区域中显示的标题值
+
+
+ 更新标题值的函数
+
+
+ 标题组件是否处于只读模式
+
+
+ 标题元素
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/caption.mdx b/apps/www/content/docs/(plugins)/(functionality)/caption.mdx
new file mode 100644
index 0000000000..871917f051
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/caption.mdx
@@ -0,0 +1,213 @@
+---
+title: Caption
+description: Add captions to media elements like images, videos, and files.
+docs:
+ - route: /docs/components/caption
+ title: Caption
+---
+
+
+
+
+
+## Features
+
+- Add captions to images, videos, audio files, and other media elements.
+- Arrow navigation selects caption within a block.
+- Inline caption editing with textarea component.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add caption functionality is with the `MediaKit`, which includes the pre-configured `CaptionPlugin` along with media plugins and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`Caption`](/docs/components/caption): Renders caption components for media elements.
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { MediaKit } from '@/components/editor/plugins/media-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...MediaKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/caption
+```
+
+### Add Plugin
+
+```tsx
+import { CaptionPlugin } from '@platejs/caption/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CaptionPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+Configure which media plugins should support captions:
+
+```tsx
+import { KEYS } from 'platejs';
+import { CaptionPlugin } from '@platejs/caption/react';
+import {
+ AudioPlugin,
+ FilePlugin,
+ ImagePlugin,
+ MediaEmbedPlugin,
+ VideoPlugin,
+} from '@platejs/media/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ImagePlugin,
+ VideoPlugin,
+ AudioPlugin,
+ FilePlugin,
+ MediaEmbedPlugin,
+ CaptionPlugin.configure({
+ options: {
+ query: {
+ allow: [KEYS.img, KEYS.video, KEYS.audio, KEYS.file, KEYS.mediaEmbed],
+ },
+ },
+ }),
+ ],
+});
+```
+
+- `query.allow`: Array of plugin keys that support captions.
+
+
+
+## Plugins
+
+### `CaptionPlugin`
+
+Plugin for adding caption functionality to media elements.
+
+
+
+
+ Configuration for which plugins support captions.
+
+
+ Plugin keys of the blocks that can have captions.
+
+
+
+
+ Path to focus at the end of the caption.
+ - **Default:** `null`
+
+
+ Path to focus at the start of the caption.
+ - **Default:** `null`
+
+
+ ID of the currently visible caption.
+ - **Default:** `null`
+
+
+
+
+## Types
+
+### `TCaptionElement`
+
+Extends `TElement`.
+
+
+
+
+ Caption value as an array of descendant nodes.
+
+
+
+
+## Components
+
+### ``
+
+
+
+
+ Options for the caption component.
+
+
+ State for the caption component.
+
+
+ The string representing the caption.
+
+
+ Whether the caption component is selected.
+
+
+ Whether the caption component is in read-only mode.
+
+
+
+
+
+
+ Whether the caption component is in read-only mode.
+
+
+
+
+
+### ``
+
+
+
+
+ State for the caption textarea.
+
+
+ Reference to the textarea element.
+
+
+ The value of the caption displayed in the textarea.
+
+
+ Function to update the caption value.
+
+
+ Whether the caption component is in read-only mode.
+
+
+ The caption element.
+
+
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(functionality)/cursor-overlay.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/cursor-overlay.cn.mdx
new file mode 100644
index 0000000000..6399870227
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/cursor-overlay.cn.mdx
@@ -0,0 +1,190 @@
+---
+title: 光标覆盖层
+description: 当编辑器失去焦点时,为选中区域和光标位置提供视觉反馈。
+docs:
+ - route: /docs/components/cursor-overlay
+ title: 光标覆盖层
+---
+
+
+
+
+
+## 功能特性
+
+- 当其他元素获得焦点时保持选中高亮
+- 动态显示选中区域(例如AI流式输出时)
+- 拖拽操作时显示光标位置
+- 可自定义光标和选中区域的样式
+- 外部UI交互必备功能(如链接工具栏、AI组合框)
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的添加光标覆盖功能的方式是使用`CursorOverlayKit`,它包含预配置的`CursorOverlayPlugin`和[`CursorOverlay`](/docs/components/cursor-overlay) UI组件。
+
+
+
+- [`CursorOverlay`](/docs/components/cursor-overlay): 渲染光标和选中区域覆盖层
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CursorOverlayKit } from '@/components/editor/plugins/cursor-overlay-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...CursorOverlayKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/selection
+```
+
+### 添加插件
+
+```tsx
+import { CursorOverlayPlugin } from '@platejs/selection/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ CursorOverlayPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+通过组件配置光标覆盖层的渲染方式:
+
+```tsx
+import { CursorOverlayPlugin } from '@platejs/selection/react';
+import { CursorOverlay } from '@/components/ui/cursor-overlay';
+
+CursorOverlayPlugin.configure({
+ render: {
+ afterEditable: () => ,
+ },
+});
+```
+
+- `render.afterEditable`: 指定[`CursorOverlay`](/docs/components/cursor-overlay)在可编辑内容之后渲染
+
+### 编辑器容器设置
+
+光标覆盖层需要容器组件来确保正确定位。如果使用[Editor](/docs/components/editor)组件,会通过`EditorContainer`自动处理。
+
+自定义配置时,确保编辑器被带有唯一ID的容器包裹:
+
+```tsx
+import { PlateContainer } from 'platejs/react';
+
+export function EditorContainer(props: React.HTMLAttributes) {
+ return ;
+}
+```
+
+### 保持选中焦点
+
+当聚焦UI元素时,要维持编辑器的选中状态,需为这些元素添加`data-plate-focus="true"`属性:
+
+```tsx
+
+ {/* 工具栏内容 */}
+
+```
+
+这可以防止与工具栏按钮或其他UI元素交互时光标覆盖层消失。
+
+
+
+## 插件
+
+### `CursorOverlayPlugin`
+
+管理光标和选中区域覆盖层以提供视觉反馈的插件。
+
+
+
+
+ 包含光标状态及其唯一标识符的对象
+ - **默认值:** `{}`
+
+
+
+
+## API接口
+
+### `api.cursorOverlay.addCursor`
+
+添加指定键和状态的光标覆盖层
+
+
+
+
+ 光标的唯一标识符(如'blur'、'drag'、'custom')
+
+
+ 包含选中区域和可选样式数据的光标状态
+
+
+
+
+### `api.cursorOverlay.removeCursor`
+
+通过键移除光标覆盖层
+
+
+
+
+ 要移除的光标键名
+
+
+
+
+## 钩子
+
+### `useCursorOverlay`
+
+管理光标和选中区域覆盖层状态的钩子,提供实时光标位置和选中区域矩形。
+
+
+
+
+ 选中区域矩形的最小宽度(像素)。可用于使光标插入符更明显
+ - **默认值:** `1`
+
+
+ 容器调整大小时是否重新计算光标位置
+ - **默认值:** `true`
+
+
+
+
+
+ 包含对应选中区域矩形和样式数据的光标状态数组
+
+
+ 手动触发重新计算光标位置的函数
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/cursor-overlay.mdx b/apps/www/content/docs/(plugins)/(functionality)/cursor-overlay.mdx
new file mode 100644
index 0000000000..12895471fb
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/cursor-overlay.mdx
@@ -0,0 +1,190 @@
+---
+title: Cursor Overlay
+description: Visual feedback for selections and cursor positions when editor loses focus.
+docs:
+ - route: /docs/components/cursor-overlay
+ title: Cursor Overlay
+---
+
+
+
+
+
+## Features
+
+- Maintains selection highlight when another element is focused.
+- Dynamic selection display (e.g., during AI streaming).
+- Shows cursor position during drag operations.
+- Customizable styling for cursors and selections.
+- Essential for external UI interactions (e.g., link toolbar, AI combobox).
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add cursor overlay functionality is with the `CursorOverlayKit`, which includes the pre-configured `CursorOverlayPlugin` and the [`CursorOverlay`](/docs/components/cursor-overlay) UI component.
+
+
+
+- [`CursorOverlay`](/docs/components/cursor-overlay): Renders cursor and selection overlays.
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { CursorOverlayKit } from '@/components/editor/plugins/cursor-overlay-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...CursorOverlayKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/selection
+```
+
+### Add Plugin
+
+```tsx
+import { CursorOverlayPlugin } from '@platejs/selection/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CursorOverlayPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+Configure the cursor overlay with a component to render overlays:
+
+```tsx
+import { CursorOverlayPlugin } from '@platejs/selection/react';
+import { CursorOverlay } from '@/components/ui/cursor-overlay';
+
+CursorOverlayPlugin.configure({
+ render: {
+ afterEditable: () => ,
+ },
+});
+```
+
+- `render.afterEditable`: Assigns [`CursorOverlay`](/docs/components/cursor-overlay) to render after the editable content.
+
+### Editor Container Setup
+
+The cursor overlay requires a container component to ensure correct positioning. If you're using the [Editor](/docs/components/editor) component, this is handled automatically through `EditorContainer`.
+
+For custom setups, ensure your editor is wrapped with a container that has the editor's unique ID:
+
+```tsx
+import { PlateContainer } from 'platejs/react';
+
+export function EditorContainer(props: React.HTMLAttributes) {
+ return ;
+}
+```
+
+### Preserving Selection Focus
+
+To maintain the editor's selection state when focusing UI elements, add the `data-plate-focus="true"` attribute to those elements:
+
+```tsx
+
+ {/* toolbar content */}
+
+```
+
+This prevents the cursor overlay from disappearing when interacting with toolbar buttons or other UI elements.
+
+
+
+## Plugins
+
+### `CursorOverlayPlugin`
+
+Plugin that manages cursor and selection overlays for visual feedback.
+
+
+
+
+ Object containing cursor states with their unique identifiers.
+ - **Default:** `{}`
+
+
+
+
+## API
+
+### `api.cursorOverlay.addCursor`
+
+Adds a cursor overlay with the specified key and state.
+
+
+
+
+ Unique identifier for the cursor (e.g., 'blur', 'drag', 'custom').
+
+
+ The cursor state including selection and optional styling data.
+
+
+
+
+### `api.cursorOverlay.removeCursor`
+
+Removes a cursor overlay by its key.
+
+
+
+
+ The key of the cursor to remove.
+
+
+
+
+## Hooks
+
+### `useCursorOverlay`
+
+A hook that manages cursor and selection overlay states, providing real-time cursor positions and selection rectangles.
+
+
+
+
+ Minimum width in pixels for a selection rectangle. Useful for making cursor carets more visible.
+ - **Default:** `1`
+
+
+ Whether to recalculate cursor positions when the container is resized.
+ - **Default:** `true`
+
+
+
+
+
+ Array of cursor states with their corresponding selection rectangles and styling data.
+
+
+ Function to manually trigger a recalculation of cursor positions.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(functionality)/dnd.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/dnd.cn.mdx
new file mode 100644
index 0000000000..41aa746b3d
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/dnd.cn.mdx
@@ -0,0 +1,353 @@
+---
+title: 拖拽功能
+description: 通过拖拽区块来重新组织编辑器中的内容。
+docs:
+ - route: /docs/components/block-draggable
+ title: 可拖拽区块组件
+---
+
+
+
+
+
+## 功能特性
+
+- 通过拖拽区块实现内容移动和插入
+- 拖拽至视窗边缘时自动滚动
+- 支持文件拖放插入媒体区块
+- 可视化拖放指示器和拖拽手柄
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用 `DndKit` 套件,它包含预配置的 `DndPlugin`、React DnD 设置以及 [`BlockDraggable`](/docs/components/block-draggable) UI组件。
+
+
+
+- [`BlockDraggable`](/docs/components/block-draggable): 为区块渲染拖拽手柄和放置指示器
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { DndKit } from '@/components/editor/plugins/dnd-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...DndKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装依赖
+
+```bash
+npm install @platejs/dnd react-dnd react-dnd-html5-backend
+```
+
+### 添加插件
+
+```tsx
+import { DndPlugin } from '@platejs/dnd';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ DndPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+通过可拖拽组件和DnD provider配置拖放功能:
+
+```tsx
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
+import { DndPlugin } from '@platejs/dnd';
+
+DndPlugin.configure({
+ render: {
+ aboveSlate: ({ children }) => (
+ {children}
+ ),
+ },
+});
+```
+
+- `render.aboveNodes`: 分配 [`BlockDraggable`](/docs/components/block-draggable) 组件在区块上方渲染拖拽手柄
+- `render.aboveSlate`: 用 `DndProvider` 包裹编辑器。如果React树中已有 `DndProvider` 可移除此配置
+
+### 高级配置
+
+添加自动滚动和文件拖放处理:
+
+```tsx
+import { DndPlugin } from '@platejs/dnd';
+import { PlaceholderPlugin } from '@platejs/media/react';
+
+DndPlugin.configure({
+ options: {
+ enableScroller: true,
+ onDropFiles: ({ dragItem, editor, target }) => {
+ editor
+ .getTransforms(PlaceholderPlugin)
+ .insert.media(dragItem.files, { at: target, nextBlock: false });
+ },
+ },
+ render: {
+ aboveNodes: BlockDraggable,
+ aboveSlate: ({ children }) => (
+ {children}
+ ),
+ },
+});
+```
+
+- `enableScroller`: 启用拖拽至视窗边缘时的自动滚动
+- `onDropFiles`: 处理文件拖放,在目标位置插入媒体区块
+
+
+
+## 插件
+
+### `DndPlugin`
+
+实现编辑器内拖拽功能的插件。
+
+
+
+
+ 是否启用拖拽至视窗边缘自动滚动
+ - **默认值:** `false`
+
+
+ 当启用滚动时的 `Scroller` 组件属性
+
+
+ 文件拖放事件处理器
+
+
+ 目标元素ID
+
+
+ 包含拖放文件的对象
+
+
+ 编辑器实例
+
+
+ 文件应插入的路径
+
+
+
+
+ 当前拖放目标状态,包含目标元素ID和放置线方向
+
+
+
+
+## API
+
+### `focusBlockStartById`
+
+通过ID选中区块起始位置并聚焦编辑器。
+
+
+
+
+ 要聚焦的区块ID
+
+
+
+
+### `getBlocksWithId`
+
+返回带有ID的区块数组。
+
+
+
+ 获取节点条目的选项
+
+
+
+ 带有ID的区块数组
+
+
+
+### `selectBlockById`
+
+通过ID选中区块。
+
+
+
+
+ 要选中的区块ID
+
+
+
+
+## 钩子
+
+### `useDndNode`
+
+组合了 `useDragNode` 和 `useDropNode` 的自定义钩子,用于启用编辑器节点的拖拽功能。提供默认的拖拽预览效果,可自定义或禁用。
+
+
+
+
+ 要拖拽的节点
+
+
+ 拖拽项类型
+ - **默认值:** `'block'`
+
+
+ 节点拖拽引用
+
+
+ 拖拽方向
+ - **默认值:** `'vertical'`
+
+
+ 判断节点能否放置到当前位置的回调
+
+
+ 拖拽节点的预览选项
+
+
+ 拖拽节点的拖拽选项
+
+
+ 拖拽节点的放置选项
+
+
+ 节点放置时的处理函数
+
+
+
+
+
+ 节点是否正在被拖拽
+
+
+ 拖拽节点是否在放置目标上方
+
+
+ 可拖拽元素的拖拽引用
+
+
+
+
+### `useDragNode`
+
+使用 `react-dnd` 的 `useDrag` 钩子实现节点拖拽功能的自定义钩子。
+
+
+
+
+ 要拖拽的节点
+
+
+ 拖拽对象或其工厂函数
+
+
+
+
+### `useDraggable`
+
+使元素具备可拖拽行为的自定义钩子,提供可自定义或禁用的拖拽预览效果。
+
+
+
+
+ 要设为可拖拽的元素
+
+
+ 拖拽方向
+ - **默认值:** `'vertical'`
+
+
+ 拖拽项类型
+ - **默认值:** `'block'`
+
+
+ 元素放置时的处理函数
+
+
+
+
+
+ 拖拽源连接函数
+
+
+ 元素是否正在被拖拽
+
+
+ 可拖拽元素的引用
+
+
+
+
+### `useDropNode`
+
+使用 `react-dnd` 的 `useDrop` 钩子实现节点放置功能的自定义钩子。
+
+
+
+
+ 被拖拽节点的引用
+
+
+ 放置线依附的节点
+
+
+ 当前放置线的值
+
+
+ 放置线变化时的回调
+
+
+ 拦截放置处理的回调
+ - 返回 `true` 阻止默认行为
+ - 返回 `false` 执行默认行为
+
+
+
+
+### `useDropLine`
+
+返回元素的当前放置线状态。
+
+
+
+
+ 显示放置线的元素ID
+ - **默认值:** 当前元素ID
+
+
+ 按方向过滤放置线
+ - **默认值:** `'vertical'`
+
+
+
+
+
+ 当前放置线方向
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/dnd.mdx b/apps/www/content/docs/(plugins)/(functionality)/dnd.mdx
new file mode 100644
index 0000000000..2b893b2355
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/dnd.mdx
@@ -0,0 +1,355 @@
+---
+title: Drag & Drop
+description: Drag and drop blocks to reorganize content within the editor.
+docs:
+ - route: https://pro.platejs.org/docs/examples/dnd
+ title: Plus
+ - route: /docs/components/block-draggable
+ title: Block Draggable
+---
+
+
+
+
+
+## Features
+
+- Drag and drop blocks for content movement and insertion within the editor.
+- Automatic scrolling when dragging blocks near viewport edges.
+- File drop support for inserting media blocks.
+- Visual drop indicators and drag handles.
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add drag and drop functionality is with the `DndKit`, which includes the pre-configured `DndPlugin` along with React DnD setup and the [`BlockDraggable`](/docs/components/block-draggable) UI component.
+
+
+
+- [`BlockDraggable`](/docs/components/block-draggable): Renders drag handles and drop indicators for blocks.
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { DndKit } from '@/components/editor/plugins/dnd-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...DndKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/dnd react-dnd react-dnd-html5-backend
+```
+
+### Add Plugin
+
+```tsx
+import { DndPlugin } from '@platejs/dnd';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ DndPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+Configure drag and drop with a draggable component and DnD provider:
+
+```tsx
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
+import { DndPlugin } from '@platejs/dnd';
+
+DndPlugin.configure({
+ render: {
+ aboveSlate: ({ children }) => (
+ {children}
+ ),
+ },
+});
+```
+
+- `render.aboveNodes`: Assigns [`BlockDraggable`](/docs/components/block-draggable) to render drag handles above blocks.
+- `render.aboveSlate`: Wraps the editor with `DndProvider` and `HTML5Backend`. Remove this if you already have `DndProvider` in your React tree.
+
+### Advanced Configuration
+
+Add automatic scrolling and file drop handling:
+
+```tsx
+import { DndPlugin } from '@platejs/dnd';
+import { PlaceholderPlugin } from '@platejs/media/react';
+
+DndPlugin.configure({
+ options: {
+ enableScroller: true,
+ onDropFiles: ({ dragItem, editor, target }) => {
+ editor
+ .getTransforms(PlaceholderPlugin)
+ .insert.media(dragItem.files, { at: target, nextBlock: false });
+ },
+ },
+ render: {
+ aboveNodes: BlockDraggable,
+ aboveSlate: ({ children }) => (
+ {children}
+ ),
+ },
+});
+```
+
+- `enableScroller`: Enables automatic scrolling when dragging blocks near the viewport edges.
+- `onDropFiles`: Handles file drops by inserting them as media blocks at the target location.
+
+
+
+## Plugins
+
+### `DndPlugin`
+
+Plugin for drag and drop functionality within the editor.
+
+
+
+
+ Enables automatic scrolling when dragging blocks near viewport edges.
+ - **Default:** `false`
+
+
+ Props for the `Scroller` component when `enableScroller` is true.
+
+
+ Handler for file drop events.
+
+
+ ID of the target element.
+
+
+ Object containing the dropped files.
+
+
+ The editor instance.
+
+
+ Path where files should be inserted.
+
+
+
+
+ The current drop target state containing both the target element id and drop line direction.
+
+
+
+
+## API
+
+### `focusBlockStartById`
+
+Selects the start of a block by ID and focuses the editor.
+
+
+
+
+ The ID of the block to be focused.
+
+
+
+
+### `getBlocksWithId`
+
+Returns an array of blocks that have an ID.
+
+
+
+ The options for getting node entries.
+
+
+
+ Array of blocks that have an ID.
+
+
+
+### `selectBlockById`
+
+Selects a block by its ID.
+
+
+
+
+ The ID of the block to select.
+
+
+
+
+## Hooks
+
+### `useDndNode`
+
+A custom hook that combines the `useDragNode` and `useDropNode` hooks to enable dragging and dropping of a node from the editor. It provides a default preview for the dragged node, which can be customized or disabled.
+
+
+
+
+ The node to be dragged.
+
+
+ The type of drag item.
+ - **Default:** `'block'`
+
+
+ The ref of the node to be dragged.
+
+
+ The orientation of drag and drop.
+ - **Default:** `'vertical'`
+
+
+ Callback to determine if a node can be dropped at the current location.
+
+
+ The preview options for the dragged node.
+
+
+ The drag options for the dragged node.
+
+
+ The drop options for the dragged node.
+
+
+ Handler called when the node is dropped.
+
+
+
+
+
+ Whether the node is currently being dragged.
+
+
+ Whether the dragged node is over a drop target.
+
+
+ Drag reference for the draggable element.
+
+
+
+
+### `useDragNode`
+
+A custom hook that enables dragging of a node from the editor using the `useDrag` hook from `react-dnd`.
+
+
+
+
+ The node to be dragged.
+
+
+ Drag object or factory function that returns it.
+
+
+
+
+### `useDraggable`
+
+A custom hook that enables draggable behavior for a given element. It provides a preview for the element, which can be customized or disabled.
+
+
+
+
+ The element to make draggable.
+
+
+ Orientation of drag and drop.
+ - **Default:** `'vertical'`
+
+
+ Type of drag item.
+ - **Default:** `'block'`
+
+
+ Handler called when element is dropped.
+
+
+
+
+
+ Drag source connector function.
+
+
+ Whether element is being dragged.
+
+
+ Reference to draggable element.
+
+
+
+
+### `useDropNode`
+
+A custom hook that enables dropping a node on the editor. It uses the `useDrop` hook from `react-dnd` to handle the drop behavior.
+
+
+
+
+ Reference to the node being dragged.
+
+
+ The node to which the drop line is attached.
+
+
+ Current value of the drop line.
+
+
+ Callback when drop line changes.
+
+
+ Callback that intercepts drop handling.
+ - Returns `true` to prevent default behavior
+ - Returns `false` to run default behavior after
+
+
+
+
+### `useDropLine`
+
+Returns the current drop line state for an element.
+
+
+
+
+ Element ID to show drop line for.
+ - **Default:** Current element ID
+
+
+ Filter drop lines by orientation.
+ - **Default:** `'vertical'`
+
+
+
+
+
+ Current drop line direction.
+
+
+
diff --git a/apps/www/content/docs/(plugins)/(functionality)/find-replace.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/find-replace.cn.mdx
new file mode 100644
index 0000000000..87c55ca280
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/find-replace.cn.mdx
@@ -0,0 +1,22 @@
+---
+title: 查找
+description: 在文档中搜索并定位特定文本或区块。
+---
+
+
+ 功能开发中。
+
+
+- 支持全词匹配、部分词匹配或短语匹配(区分大小写)
+
+[//]: # '- 使用搜索输入框进行查找替换操作'
+
+
+
+## 安装
+
+```bash
+npm install @platejs/find-replace
+```
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/find-replace.mdx b/apps/www/content/docs/(plugins)/(functionality)/find-replace.mdx
new file mode 100644
index 0000000000..e25df656cf
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/find-replace.mdx
@@ -0,0 +1,22 @@
+---
+title: Find
+description: Search and locate specific text or blocks within your document.
+---
+
+
+ Work in progress.
+
+
+- Search for whole words, word parts, or phrases with case matching.
+
+[//]: # '- Use search input for find and replace operations.'
+
+
+
+## Installation
+
+```bash
+npm install @platejs/find-replace
+```
+
+
diff --git a/apps/www/content/docs/(plugins)/(functionality)/multi-select.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/multi-select.cn.mdx
new file mode 100644
index 0000000000..f068298782
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/multi-select.cn.mdx
@@ -0,0 +1,253 @@
+---
+title: 多选
+docs:
+ - route: /docs/components/tag-node
+ - route: /docs/components/select-editor
+---
+
+
+
+
+
+## 特性
+
+与传统的基于输入的多选不同,该组件构建在 Plate editor 之上,提供:
+
+- 完整的历史记录支持(撤销/重做)
+- 标签之间和标签内的原生光标导航
+- 选择一个或多个标签
+- 复制/粘贴标签
+- 拖放重新排序标签
+- 只读模式
+- 防止重复标签
+- 使用不区分大小写的匹配创建新标签
+- 搜索文本清理和空白修剪
+- 由 [cmdk](https://github.com/pacocoursey/cmdk) 提供支持的模糊搜索
+
+
+
+## 手动使用
+
+
+
+### 安装
+
+```bash
+npm install @platejs/tag
+```
+
+### 添加插件
+
+```tsx
+import { MultiSelectPlugin } from '@platejs/tag/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ MultiSelectPlugin, // 具有标签功能的多选编辑器
+ ],
+});
+```
+
+### 配置插件
+
+```tsx
+import { MultiSelectPlugin } from '@platejs/tag/react';
+import { createPlateEditor } from 'platejs/react';
+import { TagElement } from '@/components/ui/tag-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ MultiSelectPlugin.withComponent(TagElement),
+ ],
+});
+```
+
+- `MultiSelectPlugin`:扩展 TagPlugin 并将编辑器限制为仅包含标签元素
+- `withComponent`:分配 [`TagElement`](/docs/components/tag-node) 来渲染标签组件
+
+### 添加 SelectEditor
+
+
+
+### 基本示例
+
+```tsx
+import { MultiSelectPlugin } from '@platejs/tag/react';
+import { TagElement } from '@/components/ui/tag-node';
+import {
+ SelectEditor,
+ SelectEditorContent,
+ SelectEditorInput,
+ SelectEditorCombobox,
+ type SelectItem,
+} from '@/components/ui/select-editor';
+
+// 定义你的项目
+const ITEMS: SelectItem[] = [
+ { value: 'React' },
+ { value: 'TypeScript' },
+ { value: 'JavaScript' },
+];
+
+export default function MySelectEditor() {
+ const [value, setValue] = React.useState([ITEMS[0]]);
+
+ return (
+
+
+
+
+
+
+ );
+}
+```
+
+### 表单示例
+
+
+
+
+
+## 插件
+
+### TagPlugin
+
+用于单个标签功能的内联 void 元素插件。
+
+### MultiSelectPlugin
+
+`TagPlugin` 的扩展,将编辑器限制为仅包含标签元素,启用多选行为,具有自动文本清理和重复预防功能。
+
+## API
+
+### tf.insert.tag
+
+在当前选择处插入新的多选元素。
+
+
+
+
+ 多选元素的属性。
+
+
+
+
+
+ 多选元素的唯一值。
+
+
+
+
+### getSelectedItems
+
+获取编辑器中的所有标签项目。
+
+
+
+ 编辑器中的标签项目数组。
+
+
+
+### isEqualTags
+
+比较两组标签是否相等的工具函数,忽略顺序。
+
+
+
+
+ 要与当前编辑器标签比较的新标签。
+
+
+
+
+ 两组是否包含相同的值。
+
+
+
+## Hooks
+
+### useSelectedItems
+
+获取编辑器中当前选中的标签项目的 Hook。
+
+
+
+ 具有值和属性的选中标签项目数组。
+
+
+
+### useSelectableItems
+
+获取可选择的可用项目的 Hook,通过搜索过滤并排除已选中的项目。
+
+
+
+
+ 是否允许创建新项目。
+ - **默认值:** `true`
+
+
+ 项目的自定义过滤函数。
+
+
+ 可用项目数组。
+
+
+ 新项目的过滤函数。
+
+
+ 新项目在列表中的位置。
+ - **默认值:** `'end'`
+
+
+
+
+ 过滤后的可选项目数组。
+
+
+
+### useSelectEditorCombobox
+
+处理编辑器中组合框行为的 Hook,包括文本清理和项目选择。
+
+
+
+
+ 组合框是否打开。
+
+
+ 选择第一个组合框项目的函数。
+
+
+ 选中项目更改时的回调。
+
+
+
+
+## 类型
+
+### TTagElement
+
+```ts
+type TTagElement = TElement & {
+ value: string;
+ [key: string]: unknown;
+};
+```
+
+### TTagProps
+
+```ts
+type TTagProps = {
+ value: string;
+ [key: string]: unknown;
+};
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/multi-select.mdx b/apps/www/content/docs/(plugins)/(functionality)/multi-select.mdx
new file mode 100644
index 0000000000..551a34643e
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/multi-select.mdx
@@ -0,0 +1,253 @@
+---
+title: Multi Select
+docs:
+ - route: /docs/components/tag-node
+ - route: /docs/components/select-editor
+---
+
+
+
+
+
+## Features
+
+Unlike traditional input-based multi-selects, this component is built on top of Plate editor, providing:
+
+- Full history support (undo/redo)
+- Native cursor navigation between and within tags
+- Select one to many tags
+- Copy/paste tags
+- Drag and drop to reorder tags
+- Read-only mode
+- Duplicate tags prevention
+- Create new tags with case insensitive matching
+- Search text cleanup and whitespace trimming
+- Fuzzy search powered by [cmdk](https://github.com/pacocoursey/cmdk)
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/tag
+```
+
+### Add Plugins
+
+```tsx
+import { MultiSelectPlugin } from '@platejs/tag/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ MultiSelectPlugin, // Multi-select editor with tag functionality
+ ],
+});
+```
+
+### Configure Plugins
+
+```tsx
+import { MultiSelectPlugin } from '@platejs/tag/react';
+import { createPlateEditor } from 'platejs/react';
+import { TagElement } from '@/components/ui/tag-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ MultiSelectPlugin.withComponent(TagElement),
+ ],
+});
+```
+
+- `MultiSelectPlugin`: Extends TagPlugin and constrains the editor to only contain tag elements
+- `withComponent`: Assigns [`TagElement`](/docs/components/tag-node) to render tag components
+
+### Add SelectEditor
+
+
+
+### Basic Example
+
+```tsx
+import { MultiSelectPlugin } from '@platejs/tag/react';
+import { TagElement } from '@/components/ui/tag-node';
+import {
+ SelectEditor,
+ SelectEditorContent,
+ SelectEditorInput,
+ SelectEditorCombobox,
+ type SelectItem,
+} from '@/components/ui/select-editor';
+
+// Define your items
+const ITEMS: SelectItem[] = [
+ { value: 'React' },
+ { value: 'TypeScript' },
+ { value: 'JavaScript' },
+];
+
+export default function MySelectEditor() {
+ const [value, setValue] = React.useState([ITEMS[0]]);
+
+ return (
+
+
+
+
+
+
+ );
+}
+```
+
+### Form Example
+
+
+
+
+
+## Plugins
+
+### TagPlugin
+
+Inline void element plugin for individual tag functionality.
+
+### MultiSelectPlugin
+
+Extension of `TagPlugin` that constrains the editor to only contain tag elements, enabling multi-select behavior with automatic text cleanup and duplicate prevention.
+
+## API
+
+### tf.insert.tag
+
+Inserts new multi-select element at current selection.
+
+
+
+
+ Properties for multi-select element.
+
+
+
+
+
+ Unique value of multi-select element.
+
+
+
+
+### getSelectedItems
+
+Gets all tag items in the editor.
+
+
+
+ Array of tag items in editor.
+
+
+
+### isEqualTags
+
+Utility function to compare two sets of tags for equality, ignoring order.
+
+
+
+
+ New tags to compare against current editor tags.
+
+
+
+
+ Whether both sets contain same values.
+
+
+
+## Hooks
+
+### useSelectedItems
+
+Hook to get the currently selected tag items in the editor.
+
+
+
+ Array of selected tag items with values and properties.
+
+
+
+### useSelectableItems
+
+Hook to get the available items that can be selected, filtered by search and excluding already selected items.
+
+
+
+
+ Whether to allow creating new items.
+ - **Default:** `true`
+
+
+ Custom filter function for items.
+
+
+ Array of available items.
+
+
+ Filter function for new items.
+
+
+ Position of new items in list.
+ - **Default:** `'end'`
+
+
+
+
+ Filtered array of selectable items.
+
+
+
+### useSelectEditorCombobox
+
+Hook to handle combobox behavior in the editor, including text cleanup and item selection.
+
+
+
+
+ Whether combobox is open.
+
+
+ Function to select first combobox item.
+
+
+ Callback when selected items change.
+
+
+
+
+## Types
+
+### TTagElement
+
+```ts
+type TTagElement = TElement & {
+ value: string;
+ [key: string]: unknown;
+};
+```
+
+### TTagProps
+
+```ts
+type TTagProps = {
+ value: string;
+ [key: string]: unknown;
+};
+```
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/tabbable.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/tabbable.cn.mdx
new file mode 100644
index 0000000000..ec24ec4a0f
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/tabbable.cn.mdx
@@ -0,0 +1,212 @@
+---
+title: 可聚焦元素 (Tabbable)
+---
+
+
+
+
+
+## 功能特性
+
+- 确保编辑器中可聚焦元素之间的标签顺序一致
+- 管理空元素与外部DOM元素之间的焦点切换
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用 `TabbableKit`,它包含预配置的 `TabbablePlugin` 和智能查询逻辑,可避免与其他插件冲突。
+
+
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { TabbableKit } from '@/components/editor/plugins/tabbable-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...TabbableKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/tabbable
+```
+
+### 添加插件
+
+```tsx
+import { TabbablePlugin } from '@platejs/tabbable/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ TabbablePlugin,
+ ],
+});
+```
+
+### 配置插件
+
+```tsx
+import { TabbablePlugin } from '@platejs/tabbable/react';
+import { createPlateEditor } from 'platejs/react';
+import { KEYS } from 'platejs';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ TabbablePlugin.configure({
+ options: {
+ query: (event) => {
+ // 在列表或代码块中禁用
+ const inList = editor.api.some({ match: { type: KEYS.li } });
+ const inCodeBlock = editor.api.some({ match: { type: KEYS.codeBlock } });
+ return !inList && !inCodeBlock;
+ },
+ globalEventListener: true,
+ isTabbable: (tabbableEntry) =>
+ editor.api.isVoid(tabbableEntry.slateNode),
+ },
+ }),
+ ],
+});
+```
+
+- `options.query`: 根据编辑器状态动态启用/禁用插件的函数
+- `options.globalEventListener`: 为`true`时,将事件监听器添加到document而非编辑器
+- `options.isTabbable`: 判断哪些元素应包含在标签顺序中的函数
+
+
+
+## 高级用法
+
+### 与其他插件的冲突
+
+Tabbable插件可能会与处理`Tab`键的其他插件产生冲突,例如:
+
+- 列表插件
+- 代码块插件
+- 缩进插件
+
+使用`query`选项在`Tab`键应由其他插件处理时禁用Tabbable插件:
+
+```tsx
+query: (event) => {
+ const inList = editor.api.some({ match: { type: KEYS.li } });
+ const inCodeBlock = editor.api.some({ match: { type: KEYS.codeBlock } });
+ return !inList && !inCodeBlock;
+},
+```
+
+如果使用缩进插件,可以仅在选中特定类型节点(如void节点)时启用Tabbable插件:
+
+```tsx
+query: (event) => !!editor.api.some({
+ match: (node) => editor.api.isVoid(node),
+}),
+```
+
+### 非空Slate节点
+
+将为编辑器中的每个可聚焦DOM元素创建一个`TabbableEntry`,使用[tabbable](https://www.npmjs.com/package/tabbable) NPM包确定。然后使用`isTabbable`过滤可聚焦列表。
+
+默认情况下,`isTabbable`仅对void Slate节点内的entry返回true。可以覆盖`isTabbable`以支持其他类型Slate节点中包含的DOM元素:
+
+```tsx
+// 启用CUSTOM_ELEMENT内的可聚焦DOM元素
+isTabbable: (tabbableEntry) => (
+ tabbableEntry.slateNode.type === CUSTOM_ELEMENT ||
+ editor.api.isVoid(tabbableEntry.slateNode)
+),
+```
+
+### 编辑器外部的DOM元素
+
+某些情况下,可能需要允许用户从编辑器切换到外部渲染的DOM元素(如交互式弹出框)。
+
+为此,覆盖`insertTabbableEntries`返回`TabbableEntry`对象数组,每个对象对应一个要包含在可聚焦列表中的外部DOM元素。`TabbableEntry`的`slateNode`和`path`应引用当DOM元素可聚焦时用户光标所在的Slate节点。
+
+将`globalEventListener`选项设为`true`以确保Tabbable插件能将用户焦点返回到编辑器。
+
+例如,如果DOM元素在选中链接时出现,`slateNode`和`path`应为该链接的节点。
+
+```tsx
+// 将.my-popover内的按钮添加到可聚焦列表
+globalEventListener: true,
+insertTabbableEntries: (event) => {
+ const [selectedNode, selectedNodePath] = editor.api.node(editor.selection);
+
+ return [
+ ...document.querySelectorAll('.my-popover > button'),
+ ].map((domNode) => ({
+ domNode,
+ slateNode: selectedNode,
+ path: selectedNodePath,
+ }));
+},
+```
+
+## 插件
+
+### TabbablePlugin
+
+管理可聚焦元素间标签顺序的插件。
+
+
+
+
+ 动态启用/禁用插件。
+ - **默认值:** `() => true`
+
+
+ 将事件监听器添加到document而非编辑器。
+ - **默认值:** `false`
+
+
+ 添加编辑器外的额外可聚焦entry。
+ - **默认值:** `() => []`
+
+
+ 判断元素是否应可聚焦。
+ - **默认值:** `(tabbableEntry) => editor.api.isVoid(tabbableEntry.slateNode)`
+
+
+
+
+## 类型
+
+### TabbableEntry
+
+定义可聚焦entry的属性。
+
+
+
+
+ 表示可聚焦entry的HTML元素。
+
+
+ 对应的Slate节点。
+
+
+ 文档中Slate节点的路径。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/tabbable.mdx b/apps/www/content/docs/(plugins)/(functionality)/tabbable.mdx
new file mode 100644
index 0000000000..e3c9f26b60
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/tabbable.mdx
@@ -0,0 +1,212 @@
+---
+title: Tabbable
+---
+
+
+
+
+
+## Features
+
+- Ensures consistent tab order between tabbable elements in the editor
+- Manages focus transitions between void elements and external DOM elements
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the tabbable plugin is with the `TabbableKit`, which includes pre-configured `TabbablePlugin` with smart query logic to avoid conflicts with other plugins.
+
+
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { TabbableKit } from '@/components/editor/plugins/tabbable-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...TabbableKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/tabbable
+```
+
+### Add Plugin
+
+```tsx
+import { TabbablePlugin } from '@platejs/tabbable/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ TabbablePlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+```tsx
+import { TabbablePlugin } from '@platejs/tabbable/react';
+import { createPlateEditor } from 'platejs/react';
+import { KEYS } from 'platejs';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ TabbablePlugin.configure({
+ options: {
+ query: (event) => {
+ // Disable when in lists or code blocks
+ const inList = editor.api.some({ match: { type: KEYS.li } });
+ const inCodeBlock = editor.api.some({ match: { type: KEYS.codeBlock } });
+ return !inList && !inCodeBlock;
+ },
+ globalEventListener: true,
+ isTabbable: (tabbableEntry) =>
+ editor.api.isVoid(tabbableEntry.slateNode),
+ },
+ }),
+ ],
+});
+```
+
+- `options.query`: Function to dynamically enable/disable the plugin based on editor state.
+- `options.globalEventListener`: When `true`, adds event listener to document instead of editor.
+- `options.isTabbable`: Function to determine which elements should be included in tab order.
+
+
+
+## Advanced Usage
+
+### Conflicts with Other Plugins
+
+The Tabbable plugin may cause issues with other plugins that handle the `Tab` key, such as:
+
+- Lists
+- Code blocks
+- Indent plugin
+
+Use the `query` option to disable the Tabbable plugin when the `Tab` key should be handled by another plugin:
+
+```tsx
+query: (event) => {
+ const inList = editor.api.some({ match: { type: KEYS.li } });
+ const inCodeBlock = editor.api.some({ match: { type: KEYS.codeBlock } });
+ return !inList && !inCodeBlock;
+},
+```
+
+Alternatively, if you're using the Indent plugin, you can enable the Tabbable plugin only when a specific type of node is selected, such as voids:
+
+```tsx
+query: (event) => !!editor.api.some({
+ match: (node) => editor.api.isVoid(node),
+}),
+```
+
+### Non-void Slate Nodes
+
+One `TabbableEntry` will be created for each tabbable DOM element in the editor, as determined using the [tabbable](https://www.npmjs.com/package/tabbable) NPM package. The list of tabbables is then filtered using `isTabbable`.
+
+By default, `isTabbable` only returns true for entries inside void Slate nodes. You can override `isTabbable` to add support for DOM elements contained in other types of Slate node:
+
+```tsx
+// Enable tabbable DOM elements inside CUSTOM_ELEMENT
+isTabbable: (tabbableEntry) => (
+ tabbableEntry.slateNode.type === CUSTOM_ELEMENT ||
+ editor.api.isVoid(tabbableEntry.slateNode)
+),
+```
+
+### DOM Elements Outside the Editor
+
+In some circumstances, you may want to allow users to tab from the editor to a DOM element rendered outside the editor, such as an interactive popover.
+
+To do this, override `insertTabbableEntries` to return an array of `TabbableEntry` objects, one for each DOM element outside the editor that you want to include in the tabbable list. The `slateNode` and `path` of the `TabbableEntry` should refer to the Slate node the user's cursor will be inside when the DOM element should be tabbable to.
+
+Set the `globalEventListener` option to `true` to make sure the Tabbable plugin is able to return the user's focus to the editor.
+
+For example, if the DOM element appears when a link is selected, the `slateNode` and `path` should be that of the link.
+
+```tsx
+// Add buttons inside .my-popover to the list of tabbables
+globalEventListener: true,
+insertTabbableEntries: (event) => {
+ const [selectedNode, selectedNodePath] = editor.api.node(editor.selection);
+
+ return [
+ ...document.querySelectorAll('.my-popover > button'),
+ ].map((domNode) => ({
+ domNode,
+ slateNode: selectedNode,
+ path: selectedNodePath,
+ }));
+},
+```
+
+## Plugins
+
+### TabbablePlugin
+
+Plugin for managing tab order between tabbable elements.
+
+
+
+
+ Enable/disable plugin dynamically.
+ - **Default:** `() => true`
+
+
+ Add event listener to document instead of editor.
+ - **Default:** `false`
+
+
+ Add additional tabbable entries outside editor.
+ - **Default:** `() => []`
+
+
+ Determine if element should be tabbable.
+ - **Default:** `(tabbableEntry) => editor.api.isVoid(tabbableEntry.slateNode)`
+
+
+
+
+## Types
+
+### TabbableEntry
+
+Defines the properties of a tabbable entry.
+
+
+
+
+ HTML element representing tabbable entry.
+
+
+ Corresponding Slate node.
+
+
+ Path to Slate node in document.
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/toolbar.cn.mdx b/apps/www/content/docs/(plugins)/(functionality)/toolbar.cn.mdx
new file mode 100644
index 0000000000..bf3ccd4559
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/toolbar.cn.mdx
@@ -0,0 +1,244 @@
+---
+title: 工具栏
+description: 为编辑器提供固定和浮动工具栏。
+docs:
+ - route: /docs/components/fixed-toolbar
+ title: 固定工具栏
+ - route: /docs/components/floating-toolbar
+ title: 浮动工具栏
+---
+
+
+
+
+
+## 功能特性
+
+- **固定工具栏**: 持久固定在编辑器顶部的工具栏
+- **浮动工具栏**: 文本选中时出现的上下文工具栏
+- **可定制按钮**: 轻松添加、删除和重新排序工具栏按钮
+- **响应式设计**: 适配不同屏幕尺寸和内容
+- **插件集成**: 与Plate插件和UI组件无缝集成
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用`FixedToolbarKit`和`FloatingToolbarKit`,它们包含预配置的工具栏插件及其[Plate UI](/docs/installation/plate-ui)组件。
+
+
+
+
+- [`FixedToolbar`](/docs/components/fixed-toolbar): 在编辑器上方渲染固定工具栏
+- [`FixedToolbarButtons`](/docs/components/fixed-toolbar-buttons): 固定工具栏的预配置按钮集
+- [`FloatingToolbar`](/docs/components/floating-toolbar): 文本选中时渲染浮动工具栏
+- [`FloatingToolbarButtons`](/docs/components/floating-toolbar-buttons): 浮动工具栏的预配置按钮集
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { FixedToolbarKit } from '@/components/editor/plugins/fixed-toolbar-kit';
+import { FloatingToolbarKit } from '@/components/editor/plugins/floating-toolbar-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...FixedToolbarKit,
+ ...FloatingToolbarKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 创建插件
+
+```tsx
+import { createPlatePlugin } from 'platejs/react';
+import { FixedToolbar } from '@/components/ui/fixed-toolbar';
+import { FixedToolbarButtons } from '@/components/ui/fixed-toolbar-buttons';
+import { FloatingToolbar } from '@/components/ui/floating-toolbar';
+import { FloatingToolbarButtons } from '@/components/ui/floating-toolbar-buttons';
+
+const fixedToolbarPlugin = createPlatePlugin({
+ key: 'fixed-toolbar',
+ render: {
+ beforeEditable: () => (
+
+
+
+ ),
+ },
+});
+
+const floatingToolbarPlugin = createPlatePlugin({
+ key: 'floating-toolbar',
+ render: {
+ afterEditable: () => (
+
+
+
+ ),
+ },
+});
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ fixedToolbarPlugin,
+ floatingToolbarPlugin,
+ ],
+});
+```
+
+- `render.beforeEditable`: 在编辑器内容上方渲染[`FixedToolbar`](/docs/components/fixed-toolbar)
+- `render.afterEditable`: 在编辑器后渲染[`FloatingToolbar`](/docs/components/floating-toolbar)作为覆盖层
+
+### 自定义固定工具栏按钮
+
+`FixedToolbarButtons`组件包含固定工具栏的默认按钮集。
+
+
+
+可以通过编辑`components/ui/fixed-toolbar-buttons.tsx`来自定义。
+
+### 自定义浮动工具栏按钮
+
+同样地,可以通过编辑`components/ui/floating-toolbar-buttons.tsx`来自定义浮动工具栏。
+
+
+
+
+### 创建自定义按钮
+
+这个示例展示了一个向编辑器插入自定义文本的按钮。
+
+```tsx
+import { useEditorRef } from 'platejs/react';
+import { CustomIcon } from 'lucide-react';
+import { ToolbarButton } from '@/components/ui/toolbar';
+
+export function CustomToolbarButton() {
+ const editor = useEditorRef();
+
+ return (
+ {
+ // 自定义操作
+ editor.tf.insertText('自定义文本');
+ }}
+ tooltip="自定义操作"
+ >
+
+
+ );
+}
+```
+
+### 创建标记按钮
+
+对于切换粗体或斜体等标记,可以使用[`MarkToolbarButton`](/docs/components/mark-toolbar-button)组件。它会自动处理切换状态和操作。
+
+这个示例创建了一个"粗体"按钮。
+
+```tsx
+import { BoldIcon } from 'lucide-react';
+
+import { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';
+
+export function BoldToolbarButton() {
+ return (
+
+
+
+ );
+}
+```
+
+- `nodeType`: 指定要切换的标记类型(如`bold`、`italic`)
+- `tooltip`: 为按钮提供提示信息
+- `MarkToolbarButton`使用`useMarkToolbarButtonState`获取切换状态,使用`useMarkToolbarButton`获取`onClick`处理器和其他属性
+
+### 转换工具栏按钮
+
+[`TurnIntoToolbarButton`](/docs/components/turn-into-toolbar-button)提供下拉菜单将当前块转换为不同类型(标题、列表、引用等)。
+
+
+
+要添加新的块类型选项,编辑`turnIntoItems`数组:
+
+```tsx
+const turnIntoItems = [
+ // ... 现有项
+ {
+ icon: ,
+ keywords: ['custom', 'special'],
+ label: '自定义块',
+ value: 'custom-block',
+ },
+];
+```
+
+### 插入工具栏按钮
+
+[`InsertToolbarButton`](/docs/components/insert-toolbar-button)提供下拉菜单插入各种元素(块、列表、媒体、内联元素)。
+
+
+
+要添加新的可插入项,将其添加到`groups`数组的相应分组中:
+
+```tsx
+{
+ group: '基础块',
+ items: [
+ // ... 现有项
+ {
+ icon: ,
+ label: '自定义块',
+ value: 'custom-block',
+ },
+ ].map((item) => ({
+ ...item,
+ onSelect: (editor, value) => {
+ insertBlock(editor, value);
+ },
+ })),
+}
+```
+
+
+
+## 插件
+
+### `FixedToolbarKit`
+
+在编辑器内容上方渲染固定工具栏的插件。
+
+
+
+
+ 在编辑器内容前渲染固定工具栏。默认包含FixedToolbarButtons。
+
+
+
+
+### `FloatingToolbarKit`
+
+在文本选中时渲染浮动工具栏的插件。
+
+
+
+
+ 在编辑器后作为覆盖层渲染浮动工具栏。默认包含FloatingToolbarButtons。
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(functionality)/toolbar.mdx b/apps/www/content/docs/(plugins)/(functionality)/toolbar.mdx
new file mode 100644
index 0000000000..ce84967965
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(functionality)/toolbar.mdx
@@ -0,0 +1,250 @@
+---
+title: Toolbar
+description: Fixed and floating toolbars for your editor.
+docs:
+ - route: https://pro.platejs.org/docs/examples/floating-toolbar
+ title: Plus
+ - route: /docs/components/fixed-toolbar
+ title: Fixed Toolbar
+ - route: /docs/components/floating-toolbar
+ title: Floating Toolbar
+---
+
+
+
+
+
+## Features
+
+- **Fixed Toolbar**: Persistent toolbar that sticks to the top of the editor
+- **Floating Toolbar**: Contextual toolbar that appears on text selection
+- **Customizable Buttons**: Easily add, remove, and reorder toolbar buttons
+- **Responsive Design**: Adapts to different screen sizes and content
+- **Plugin Integration**: Seamless integration with Plate plugins and UI components
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add toolbar functionality is with the `FixedToolbarKit` and `FloatingToolbarKit`, which include pre-configured toolbar plugins along with their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+
+- [`FixedToolbar`](/docs/components/fixed-toolbar): Renders a persistent toolbar above the editor
+- [`FixedToolbarButtons`](/docs/components/fixed-toolbar-buttons): Pre-configured button set for the fixed toolbar
+- [`FloatingToolbar`](/docs/components/floating-toolbar): Renders a contextual toolbar on text selection
+- [`FloatingToolbarButtons`](/docs/components/floating-toolbar-buttons): Pre-configured button set for the floating toolbar
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { FixedToolbarKit } from '@/components/editor/plugins/fixed-toolbar-kit';
+import { FloatingToolbarKit } from '@/components/editor/plugins/floating-toolbar-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...FixedToolbarKit,
+ ...FloatingToolbarKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Create Plugins
+
+```tsx
+import { createPlatePlugin } from 'platejs/react';
+import { FixedToolbar } from '@/components/ui/fixed-toolbar';
+import { FixedToolbarButtons } from '@/components/ui/fixed-toolbar-buttons';
+import { FloatingToolbar } from '@/components/ui/floating-toolbar';
+import { FloatingToolbarButtons } from '@/components/ui/floating-toolbar-buttons';
+
+const fixedToolbarPlugin = createPlatePlugin({
+ key: 'fixed-toolbar',
+ render: {
+ beforeEditable: () => (
+
+
+
+ ),
+ },
+});
+
+const floatingToolbarPlugin = createPlatePlugin({
+ key: 'floating-toolbar',
+ render: {
+ afterEditable: () => (
+
+
+
+ ),
+ },
+});
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ fixedToolbarPlugin,
+ floatingToolbarPlugin,
+ ],
+});
+```
+
+- `render.beforeEditable`: Renders [`FixedToolbar`](/docs/components/fixed-toolbar) above the editor content
+- `render.afterEditable`: Renders [`FloatingToolbar`](/docs/components/floating-toolbar) as an overlay after the editor
+
+### Customizing Fixed Toolbar Buttons
+
+The `FixedToolbarButtons` component contains the default set of buttons for the fixed toolbar.
+
+
+
+To customize it, you can edit `components/ui/fixed-toolbar-buttons.tsx`.
+
+### Customizing Floating Toolbar Buttons
+
+Similarly, you can customize the floating toolbar by editing `components/ui/floating-toolbar-buttons.tsx`.
+
+
+
+
+### Creating Custom Button
+
+This example shows a button that inserts custom text into the editor.
+
+```tsx
+import { useEditorRef } from 'platejs/react';
+import { CustomIcon } from 'lucide-react';
+import { ToolbarButton } from '@/components/ui/toolbar';
+
+export function CustomToolbarButton() {
+ const editor = useEditorRef();
+
+ return (
+ {
+ // Custom action
+ editor.tf.insertText('Custom text');
+ }}
+ tooltip="Custom Action"
+ >
+
+
+ );
+}
+```
+
+### Creating Mark Button
+
+For toggling marks like bold or italic, you can use the [`MarkToolbarButton`](/docs/components/mark-toolbar-button) component. It simplifies the process by handling the toggle state and action automatically.
+
+This example creates a "Bold" button.
+
+```tsx
+import { BoldIcon } from 'lucide-react';
+
+import { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';
+
+export function BoldToolbarButton() {
+ return (
+
+
+
+ );
+}
+```
+
+- `nodeType`: Specifies the mark to toggle (e.g., `bold`, `italic`).
+- `tooltip`: Provides a helpful tooltip for the button.
+- The `MarkToolbarButton` uses `useMarkToolbarButtonState` to get the toggle state and `useMarkToolbarButton` to get the `onClick` handler and other props.
+
+### Turn Into Toolbar Button
+
+The [`TurnIntoToolbarButton`](/docs/components/turn-into-toolbar-button) provides a dropdown menu to convert the current block into different types (headings, lists, quotes, etc.).
+
+
+
+To add a new block type to the turn-into options, edit the `turnIntoItems` array:
+
+```tsx
+const turnIntoItems = [
+ // ... existing items
+ {
+ icon: ,
+ keywords: ['custom', 'special'],
+ label: 'Custom Block',
+ value: 'custom-block',
+ },
+];
+```
+
+### Insert Toolbar Button
+
+The [`InsertToolbarButton`](/docs/components/insert-toolbar-button) provides a dropdown menu to insert various elements (blocks, lists, media, inline elements).
+
+
+
+To add a new insertable item, add it to the appropriate group in the `groups` array:
+
+```tsx
+{
+ group: 'Basic blocks',
+ items: [
+ // ... existing items
+ {
+ icon: ,
+ label: 'Custom Block',
+ value: 'custom-block',
+ },
+ ].map((item) => ({
+ ...item,
+ onSelect: (editor, value) => {
+ insertBlock(editor, value);
+ },
+ })),
+}
+```
+
+
+
+## Plate Plus
+
+
+
+## Plugins
+
+### `FixedToolbarKit`
+
+Plugin that renders a fixed toolbar above the editor content.
+
+
+
+
+ Renders the fixed toolbar before the editor content. Contains FixedToolbarButtons by default.
+
+
+
+
+### `FloatingToolbarKit`
+
+Plugin that renders a floating toolbar that appears on text selection.
+
+
+
+
+ Renders the floating toolbar as an overlay after the editor. Contains FloatingToolbarButtons by default.
+
+
+
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/basic-marks.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/basic-marks.cn.mdx
new file mode 100644
index 0000000000..0df8c05e4a
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/basic-marks.cn.mdx
@@ -0,0 +1,93 @@
+---
+title: 基础标记
+description: 常用的文本样式功能。
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: 标记工具栏按钮
+---
+
+
+
+
+应用加粗格式来强调重要文本。
+
+
+
+应用斜体格式以实现强调或风格效果。
+
+
+
+为文本添加下划线格式。
+
+
+
+应用删除线格式表示已删除内容。
+
+
+
+格式化内联代码片段和技术术语。
+
+
+
+将文本格式化为下标用于数学表达式。
+
+
+
+将文本格式化为上标用于数学表达式。
+
+
+
+显示键盘快捷键和组合键。
+
+
+
+用背景色高亮重要文本。
+
+
+
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+`BasicMarksKit` 集成了加粗、斜体、下划线、删除线、代码、下标、上标、高亮和键盘键标记的插件,以及来自 [Plate UI](/docs/installation/plate-ui) 的相应 UI 组件。
+
+
+
+- [`CodeLeaf`](/docs/components/code-node): 渲染内联代码元素。
+- [`HighlightLeaf`](/docs/components/highlight-node): 渲染高亮文本元素。
+- [`KbdLeaf`](/docs/components/kbd-node): 渲染键盘快捷键元素。
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## 手动使用
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加工具栏按钮
+
+你可以将 [`MarkToolbarButton`](/docs/components/mark-toolbar-button) 添加到 [工具栏](/docs/toolbar) 来控制文本格式标记。
+
+如需单个插件的设置和配置,请参阅上方链接的特定插件文档页面。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/basic-marks.mdx b/apps/www/content/docs/(plugins)/(marks)/basic-marks.mdx
new file mode 100644
index 0000000000..d96f9597e1
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/basic-marks.mdx
@@ -0,0 +1,93 @@
+---
+title: Basic Marks
+description: Commonly used text styling features.
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+Apply bold formatting to emphasize important text.
+
+
+
+Apply italic formatting for emphasis or stylistic purposes.
+
+
+
+Apply underline formatting to text.
+
+
+
+Apply strikethrough formatting to indicate deleted content.
+
+
+
+Format inline code snippets and technical terms.
+
+
+
+Format text as subscript for mathematical expressions.
+
+
+
+Format text as superscript for mathematical expressions.
+
+
+
+Display keyboard shortcuts and key combinations.
+
+
+
+Highlight important text with background colors.
+
+
+
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The `BasicMarksKit` bundles plugins for Bold, Italic, Underline, Strikethrough, Code, Subscript, Superscript, Highlight, and Kbd marks, along with their respective UI components from [Plate UI](/docs/installation/plate-ui).
+
+
+
+- [`CodeLeaf`](/docs/components/code-node): Renders inline code elements.
+- [`HighlightLeaf`](/docs/components/highlight-node): Renders highlighted text elements.
+- [`KbdLeaf`](/docs/components/kbd-node): Renders keyboard shortcut elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to control text formatting marks.
+
+For individual plugin setup and configuration, see the specific plugin documentation pages linked above.
diff --git a/apps/www/content/docs/(plugins)/(marks)/bold.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/bold.cn.mdx
new file mode 100644
index 0000000000..3844edf6fb
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/bold.cn.mdx
@@ -0,0 +1,112 @@
+---
+title: 加粗
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: 标记工具栏按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 应用加粗格式以强调重要文本
+- 支持快捷键快速格式化 (`Cmd + B`)
+- 默认渲染为 `` HTML 元素
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的添加加粗插件方式是使用 `BasicMarksKit`,该套件包含预配置的 `BoldPlugin` 以及其他基础标记及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+在创建编辑器时将 `BoldPlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { BoldPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ BoldPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+你可以为 `BoldPlugin` 配置自定义快捷键。
+
+```tsx
+import { BoldPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ BoldPlugin.configure({
+ shortcuts: { toggle: 'mod+b' },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: 定义切换加粗格式的键盘[快捷键](/docs/plugin-shortcuts)
+
+### 添加工具栏按钮
+
+你可以将 [`MarkToolbarButton`](/docs/components/mark-toolbar-button) 添加到[工具栏](/docs/toolbar)来切换加粗格式。
+
+
+
+## 插件
+
+### `BoldPlugin`
+
+用于加粗文本格式化的插件。默认渲染为 `` HTML 元素。
+
+## 转换方法
+
+### `tf.bold.toggle`
+
+切换选中文本的加粗格式。
+
+默认快捷键: `Cmd + B`
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/bold.mdx b/apps/www/content/docs/(plugins)/(marks)/bold.mdx
new file mode 100644
index 0000000000..3621361d43
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/bold.mdx
@@ -0,0 +1,112 @@
+---
+title: Bold
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Apply bold formatting to emphasize important text
+- Keyboard shortcut support for quick formatting (`Cmd + B`)
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the bold plugin is with the `BasicMarksKit`, which includes pre-configured `BoldPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `BoldPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { BoldPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ BoldPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `BoldPlugin` with custom keyboard shortcuts.
+
+```tsx
+import { BoldPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ BoldPlugin.configure({
+ shortcuts: { toggle: 'mod+b' },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle bold formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle bold formatting.
+
+
+
+## Plugins
+
+### `BoldPlugin`
+
+Plugin for bold text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.bold.toggle`
+
+Toggles the bold formatting for the selected text.
+
+Default Shortcut: `Cmd + B`
diff --git a/apps/www/content/docs/(plugins)/(marks)/code.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/code.cn.mdx
new file mode 100644
index 0000000000..29df430505
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/code.cn.mdx
@@ -0,0 +1,115 @@
+---
+title: 代码格式
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: 标记工具栏按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 格式化内联代码片段和技术术语
+- 支持快捷键快速格式化
+- 默认渲染为 `` HTML 元素
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的方式是使用 `BasicMarksKit` 来添加代码插件,该套件包含预配置的 `CodePlugin` 以及其他基础标记及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`CodeLeaf`](/docs/components/code-node): 渲染内联代码元素
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+在创建编辑器时,将 `CodePlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { CodePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ CodePlugin,
+ ],
+});
+```
+
+### 配置插件
+
+你可以为 `CodePlugin` 配置自定义组件和键盘快捷键。
+
+```tsx
+import { CodePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { CodeLeaf } from '@/components/ui/code-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ CodePlugin.configure({
+ node: { component: CodeLeaf },
+ shortcuts: { toggle: { keys: 'mod+e' } },
+ }),
+ ],
+});
+```
+
+- `node.component`: 指定 [`CodeLeaf`](/docs/components/code-node) 来渲染内联代码元素
+- `shortcuts.toggle`: 定义用于切换代码格式的键盘[快捷键](/docs/plugin-shortcuts)
+
+### 添加工具栏按钮
+
+你可以在[工具栏](/docs/toolbar)中添加 [`MarkToolbarButton`](/docs/components/mark-toolbar-button) 来切换代码格式。
+
+
+
+## 插件
+
+### `CodePlugin`
+
+用于内联代码文本格式化的插件。默认渲染为 `` HTML 元素。
+
+## 转换操作
+
+### `tf.code.toggle`
+
+切换所选文本的代码格式。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/code.mdx b/apps/www/content/docs/(plugins)/(marks)/code.mdx
new file mode 100644
index 0000000000..2b9b660507
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/code.mdx
@@ -0,0 +1,115 @@
+---
+title: Code
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Format inline code snippets and technical terms
+- Keyboard shortcut support for quick formatting
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the code plugin is with the `BasicMarksKit`, which includes pre-configured `CodePlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`CodeLeaf`](/docs/components/code-node): Renders inline code elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `CodePlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { CodePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CodePlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `CodePlugin` with a custom component and keyboard shortcuts.
+
+```tsx
+import { CodePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { CodeLeaf } from '@/components/ui/code-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CodePlugin.configure({
+ node: { component: CodeLeaf },
+ shortcuts: { toggle: { keys: 'mod+e' } },
+ }),
+ ],
+});
+```
+
+- `node.component`: Assigns [`CodeLeaf`](/docs/components/code-node) to render inline code elements.
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle code formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle code formatting.
+
+
+
+## Plugins
+
+### `CodePlugin`
+
+Plugin for inline code text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.code.toggle`
+
+Toggles the code formatting for the selected text.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/highlight.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/highlight.cn.mdx
new file mode 100644
index 0000000000..8435b7c791
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/highlight.cn.mdx
@@ -0,0 +1,115 @@
+---
+title: 高亮标记
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: 标记工具栏按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 使用背景色高亮重要文本
+- 支持快捷键快速格式化
+- 默认渲染为 `` HTML 元素
+
+
+
+## 套件使用方式
+
+
+
+### 安装
+
+最快捷添加高亮插件的方式是使用 `BasicMarksKit`,它包含预配置的 `HighlightPlugin` 以及其他基础标记及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+- [`HighlightLeaf`](/docs/components/highlight-node): 渲染高亮文本元素
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## 手动使用方式
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+在创建编辑器时将 `HighlightPlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { HighlightPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ HighlightPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+你可以使用自定义组件和快捷键来配置 `HighlightPlugin`。
+
+```tsx
+import { HighlightPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { HighlightLeaf } from '@/components/ui/highlight-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件
+ HighlightPlugin.configure({
+ node: { component: HighlightLeaf },
+ shortcuts: { toggle: { keys: 'mod+shift+h' } },
+ }),
+ ],
+});
+```
+
+- `node.component`: 分配 [`HighlightLeaf`](/docs/components/highlight-node) 来渲染高亮文本元素
+- `shortcuts.toggle`: 定义用于切换高亮格式的键盘[快捷键](/docs/plugin-shortcuts)
+
+### 添加工具栏按钮
+
+你可以将 [`MarkToolbarButton`](/docs/components/mark-toolbar-button) 添加到你的[工具栏](/docs/toolbar)中来切换高亮格式。
+
+
+
+## 插件
+
+### `HighlightPlugin`
+
+用于高亮文本格式化的插件。默认渲染为 `` HTML 元素。
+
+## 转换方法
+
+### `tf.highlight.toggle`
+
+切换选中文本的高亮格式。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/highlight.mdx b/apps/www/content/docs/(plugins)/(marks)/highlight.mdx
new file mode 100644
index 0000000000..4f83b43ace
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/highlight.mdx
@@ -0,0 +1,115 @@
+---
+title: Highlight
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Highlight important text with background colors
+- Keyboard shortcut support for quick formatting
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the highlight plugin is with the `BasicMarksKit`, which includes pre-configured `HighlightPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`HighlightLeaf`](/docs/components/highlight-node): Renders highlighted text elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `HighlightPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { HighlightPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ HighlightPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `HighlightPlugin` with a custom component and keyboard shortcuts.
+
+```tsx
+import { HighlightPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { HighlightLeaf } from '@/components/ui/highlight-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ HighlightPlugin.configure({
+ node: { component: HighlightLeaf },
+ shortcuts: { toggle: { keys: 'mod+shift+h' } },
+ }),
+ ],
+});
+```
+
+- `node.component`: Assigns [`HighlightLeaf`](/docs/components/highlight-node) to render highlighted text elements.
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle highlight formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle highlight formatting.
+
+
+
+## Plugins
+
+### `HighlightPlugin`
+
+Plugin for highlight text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.highlight.toggle`
+
+Toggles the highlight formatting for the selected text.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/italic.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/italic.cn.mdx
new file mode 100644
index 0000000000..4119c93a02
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/italic.cn.mdx
@@ -0,0 +1,112 @@
+---
+title: 斜体
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: 标记工具栏按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 应用斜体格式以实现强调或风格化目的
+- 支持快捷键快速格式化(`Cmd + I`)
+- 默认渲染为 `` HTML 元素
+
+
+
+## 套件使用方式
+
+
+
+### 安装
+
+添加斜体插件最快的方式是使用 `BasicMarksKit`,它包含预配置的 `ItalicPlugin` 以及其他基础标记及其 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## 手动使用方式
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+在创建编辑器时,将 `ItalicPlugin` 包含到你的 Plate 插件数组中。
+
+```tsx
+import { ItalicPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ItalicPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+你可以为 `ItalicPlugin` 配置自定义快捷键。
+
+```tsx
+import { ItalicPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ItalicPlugin.configure({
+ shortcuts: { toggle: 'mod+i' },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: 定义用于切换斜体格式的键盘[快捷键](/docs/plugin-shortcuts)。
+
+### 添加工具栏按钮
+
+你可以将 [`MarkToolbarButton`](/docs/components/mark-toolbar-button) 添加到你的[工具栏](/docs/toolbar)中来切换斜体格式。
+
+
+
+## 插件
+
+### `ItalicPlugin`
+
+用于斜体文本格式化的插件。默认渲染为 `` HTML 元素。
+
+## 转换方法
+
+### `tf.italic.toggle`
+
+切换所选文本的斜体格式。
+
+默认快捷键: `Cmd + I`
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/italic.mdx b/apps/www/content/docs/(plugins)/(marks)/italic.mdx
new file mode 100644
index 0000000000..e0c7865d7d
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/italic.mdx
@@ -0,0 +1,112 @@
+---
+title: Italic
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Apply italic formatting for emphasis or stylistic purposes
+- Keyboard shortcut support for quick formatting (`Cmd + I`)
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the italic plugin is with the `BasicMarksKit`, which includes pre-configured `ItalicPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `ItalicPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { ItalicPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ItalicPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `ItalicPlugin` with custom keyboard shortcuts.
+
+```tsx
+import { ItalicPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ItalicPlugin.configure({
+ shortcuts: { toggle: 'mod+i' },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle italic formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle italic formatting.
+
+
+
+## Plugins
+
+### `ItalicPlugin`
+
+Plugin for italic text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.italic.toggle`
+
+Toggles the italic formatting for the selected text.
+
+Default Shortcut: `Cmd + I`
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/kbd.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/kbd.cn.mdx
new file mode 100644
index 0000000000..a639d33372
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/kbd.cn.mdx
@@ -0,0 +1,111 @@
+---
+title: Kbd
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Display keyboard shortcuts and key combinations
+- Keyboard shortcut support for quick formatting
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the kbd plugin is with the `BasicMarksKit`, which includes pre-configured `KbdPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`KbdLeaf`](/docs/components/kbd-node): Renders keyboard shortcut elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `KbdPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { KbdPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ KbdPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `KbdPlugin` with a custom component.
+
+```tsx
+import { KbdPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { KbdLeaf } from '@/components/ui/kbd-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ KbdPlugin.withComponent(KbdLeaf),
+ ],
+});
+```
+
+- `withComponent`: Assigns [`KbdLeaf`](/docs/components/kbd-node) to render keyboard shortcut elements.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle kbd formatting.
+
+
+
+## Plugins
+
+### `KbdPlugin`
+
+Plugin for keyboard input text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.kbd.toggle`
+
+Toggles the kbd formatting for the selected text.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/kbd.mdx b/apps/www/content/docs/(plugins)/(marks)/kbd.mdx
new file mode 100644
index 0000000000..a639d33372
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/kbd.mdx
@@ -0,0 +1,111 @@
+---
+title: Kbd
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Display keyboard shortcuts and key combinations
+- Keyboard shortcut support for quick formatting
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the kbd plugin is with the `BasicMarksKit`, which includes pre-configured `KbdPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+- [`KbdLeaf`](/docs/components/kbd-node): Renders keyboard shortcut elements.
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `KbdPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { KbdPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ KbdPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `KbdPlugin` with a custom component.
+
+```tsx
+import { KbdPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+import { KbdLeaf } from '@/components/ui/kbd-node';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ KbdPlugin.withComponent(KbdLeaf),
+ ],
+});
+```
+
+- `withComponent`: Assigns [`KbdLeaf`](/docs/components/kbd-node) to render keyboard shortcut elements.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle kbd formatting.
+
+
+
+## Plugins
+
+### `KbdPlugin`
+
+Plugin for keyboard input text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.kbd.toggle`
+
+Toggles the kbd formatting for the selected text.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/strikethrough.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/strikethrough.cn.mdx
new file mode 100644
index 0000000000..a09e81c1a7
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/strikethrough.cn.mdx
@@ -0,0 +1,110 @@
+---
+title: 删除线
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: 标记工具栏按钮
+---
+
+
+
+
+
+## 功能特性
+
+- 应用删除线格式标记已删除或过时内容
+- 支持快捷键快速格式化
+- 默认渲染为 `` HTML 元素
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+最快捷的添加删除线插件方式是使用 `BasicMarksKit`,它包含预配置的 `StrikethroughPlugin` 以及其他基础标记和它们的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+### 添加套件
+
+将套件添加到插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+在创建编辑器时将 `StrikethroughPlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ StrikethroughPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+您可以自定义 `StrikethroughPlugin` 的键盘快捷键。
+
+```tsx
+import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ StrikethroughPlugin.configure({
+ shortcuts: { toggle: { keys: 'mod+shift+x' } },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: 定义切换删除线格式的键盘[快捷键](/docs/plugin-shortcuts)
+
+### 添加工具栏按钮
+
+您可以在[工具栏](/docs/toolbar)中添加 [`MarkToolbarButton`](/docs/components/mark-toolbar-button) 来切换删除线格式。
+
+
+
+## 插件
+
+### `StrikethroughPlugin`
+
+删除线文本格式插件。默认渲染为 `` HTML 元素。
+
+## 转换方法
+
+### `tf.strikethrough.toggle`
+
+切换所选文本的删除线格式。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/strikethrough.mdx b/apps/www/content/docs/(plugins)/(marks)/strikethrough.mdx
new file mode 100644
index 0000000000..86b5c687fe
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/strikethrough.mdx
@@ -0,0 +1,110 @@
+---
+title: Strikethrough
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Apply strikethrough formatting to indicate deleted or outdated content
+- Keyboard shortcut support for quick formatting
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the strikethrough plugin is with the `BasicMarksKit`, which includes pre-configured `StrikethroughPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `StrikethroughPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ StrikethroughPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `StrikethroughPlugin` with custom keyboard shortcuts.
+
+```tsx
+import { StrikethroughPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ StrikethroughPlugin.configure({
+ shortcuts: { toggle: { keys: 'mod+shift+x' } },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle strikethrough formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle strikethrough formatting.
+
+
+
+## Plugins
+
+### `StrikethroughPlugin`
+
+Plugin for strikethrough text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.strikethrough.toggle`
+
+Toggles the strikethrough formatting for the selected text.
diff --git a/apps/www/content/docs/(plugins)/(marks)/subscript.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/subscript.cn.mdx
new file mode 100644
index 0000000000..83b1a187d0
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/subscript.cn.mdx
@@ -0,0 +1,110 @@
+---
+title: Subscript
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Format text as subscript for mathematical expressions or chemical formulas
+- Keyboard shortcut support for quick formatting
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the subscript plugin is with the `BasicMarksKit`, which includes pre-configured `SubscriptPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `SubscriptPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { SubscriptPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SubscriptPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `SubscriptPlugin` with custom keyboard shortcuts.
+
+```tsx
+import { SubscriptPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SubscriptPlugin.configure({
+ shortcuts: { toggle: { keys: 'mod+comma' } },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle subscript formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle subscript formatting.
+
+
+
+## Plugins
+
+### `SubscriptPlugin`
+
+Plugin for subscript text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.subscript.toggle`
+
+Toggles the subscript formatting for the selected text.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/subscript.mdx b/apps/www/content/docs/(plugins)/(marks)/subscript.mdx
new file mode 100644
index 0000000000..83b1a187d0
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/subscript.mdx
@@ -0,0 +1,110 @@
+---
+title: Subscript
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Format text as subscript for mathematical expressions or chemical formulas
+- Keyboard shortcut support for quick formatting
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the subscript plugin is with the `BasicMarksKit`, which includes pre-configured `SubscriptPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `SubscriptPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { SubscriptPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SubscriptPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `SubscriptPlugin` with custom keyboard shortcuts.
+
+```tsx
+import { SubscriptPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SubscriptPlugin.configure({
+ shortcuts: { toggle: { keys: 'mod+comma' } },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle subscript formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle subscript formatting.
+
+
+
+## Plugins
+
+### `SubscriptPlugin`
+
+Plugin for subscript text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.subscript.toggle`
+
+Toggles the subscript formatting for the selected text.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/superscript.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/superscript.cn.mdx
new file mode 100644
index 0000000000..4d230795c1
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/superscript.cn.mdx
@@ -0,0 +1,110 @@
+---
+title: 上标
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: 标记工具栏按钮
+---
+
+
+
+
+
+## 特性
+
+- 将文本格式化为上标,适用于数学表达式或脚注
+- 支持键盘快捷键快速格式化
+- 默认渲染为 `` HTML 元素
+
+
+
+## 套件使用方式
+
+
+
+### 安装
+
+最快捷添加上标插件的方式是使用 `BasicMarksKit`,它包含预配置的 `SuperscriptPlugin` 以及其他基础标记和它们的 [Plate UI](/docs/installation/plate-ui) 组件。
+
+
+
+### 添加套件
+
+将套件添加到你的插件中:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## 手动使用方式
+
+
+
+### 安装
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### 添加插件
+
+在创建编辑器时,将 `SuperscriptPlugin` 包含到 Plate 插件数组中。
+
+```tsx
+import { SuperscriptPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ SuperscriptPlugin,
+ ],
+});
+```
+
+### 配置插件
+
+你可以为 `SuperscriptPlugin` 配置自定义键盘快捷键。
+
+```tsx
+import { SuperscriptPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ SuperscriptPlugin.configure({
+ shortcuts: { toggle: { keys: 'mod+period' } },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: 定义用于切换上标格式的键盘[快捷键](/docs/plugin-shortcuts)。
+
+### 添加工具栏按钮
+
+你可以向[工具栏](/docs/toolbar)添加 [`MarkToolbarButton`](/docs/components/mark-toolbar-button) 来切换上标格式。
+
+
+
+## 插件
+
+### `SuperscriptPlugin`
+
+用于上标文本格式化的插件。默认渲染为 `` HTML 元素。
+
+## 转换操作
+
+### `tf.superscript.toggle`
+
+为选中的文本切换上标格式。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/superscript.mdx b/apps/www/content/docs/(plugins)/(marks)/superscript.mdx
new file mode 100644
index 0000000000..f53fd338c8
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/superscript.mdx
@@ -0,0 +1,110 @@
+---
+title: Superscript
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Format text as superscript for mathematical expressions or footnotes
+- Keyboard shortcut support for quick formatting
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the superscript plugin is with the `BasicMarksKit`, which includes pre-configured `SuperscriptPlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `SuperscriptPlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { SuperscriptPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SuperscriptPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `SuperscriptPlugin` with custom keyboard shortcuts.
+
+```tsx
+import { SuperscriptPlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ SuperscriptPlugin.configure({
+ shortcuts: { toggle: { keys: 'mod+period' } },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle superscript formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle superscript formatting.
+
+
+
+## Plugins
+
+### `SuperscriptPlugin`
+
+Plugin for superscript text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.superscript.toggle`
+
+Toggles the superscript formatting for the selected text.
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/underline.cn.mdx b/apps/www/content/docs/(plugins)/(marks)/underline.cn.mdx
new file mode 100644
index 0000000000..979c2ec359
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/underline.cn.mdx
@@ -0,0 +1,112 @@
+---
+title: Underline
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Apply underline formatting to text
+- Keyboard shortcut support for quick formatting (`Cmd + U`)
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the underline plugin is with the `BasicMarksKit`, which includes pre-configured `UnderlinePlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `UnderlinePlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { UnderlinePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ UnderlinePlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `UnderlinePlugin` with custom keyboard shortcuts.
+
+```tsx
+import { UnderlinePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ UnderlinePlugin.configure({
+ shortcuts: { toggle: 'mod+u' },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle underline formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle underline formatting.
+
+
+
+## Plugins
+
+### `UnderlinePlugin`
+
+Plugin for underline text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.underline.toggle`
+
+Toggles the underline formatting for the selected text.
+
+Default Shortcut: `Cmd + U`
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(marks)/underline.mdx b/apps/www/content/docs/(plugins)/(marks)/underline.mdx
new file mode 100644
index 0000000000..979c2ec359
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(marks)/underline.mdx
@@ -0,0 +1,112 @@
+---
+title: Underline
+docs:
+ - route: /docs/components/mark-toolbar-button
+ title: Mark Toolbar Button
+---
+
+
+
+
+
+## Features
+
+- Apply underline formatting to text
+- Keyboard shortcut support for quick formatting (`Cmd + U`)
+- Renders as `` HTML element by default
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add the underline plugin is with the `BasicMarksKit`, which includes pre-configured `UnderlinePlugin` along with other basic marks and their [Plate UI](/docs/installation/plate-ui) components.
+
+
+
+### Add Kit
+
+Add the kit to your plugins:
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { BasicMarksKit } from '@/components/editor/plugins/basic-marks-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...BasicMarksKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/basic-nodes
+```
+
+### Add Plugin
+
+Include `UnderlinePlugin` in your Plate plugins array when creating the editor.
+
+```tsx
+import { UnderlinePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ UnderlinePlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+You can configure the `UnderlinePlugin` with custom keyboard shortcuts.
+
+```tsx
+import { UnderlinePlugin } from '@platejs/basic-nodes/react';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ UnderlinePlugin.configure({
+ shortcuts: { toggle: 'mod+u' },
+ }),
+ ],
+});
+```
+
+- `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle underline formatting.
+
+### Add Toolbar Button
+
+You can add [`MarkToolbarButton`](/docs/components/mark-toolbar-button) to your [Toolbar](/docs/toolbar) to toggle underline formatting.
+
+
+
+## Plugins
+
+### `UnderlinePlugin`
+
+Plugin for underline text formatting. Renders as `` HTML element by default.
+
+## Transforms
+
+### `tf.underline.toggle`
+
+Toggles the underline formatting for the selected text.
+
+Default Shortcut: `Cmd + U`
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(serializing)/csv.cn.mdx b/apps/www/content/docs/(plugins)/(serializing)/csv.cn.mdx
new file mode 100644
index 0000000000..bdbb465b13
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(serializing)/csv.cn.mdx
@@ -0,0 +1,140 @@
+---
+title: Serializing CSV
+---
+
+
+
+
+
+## Features
+
+- Convert CSV content to Plate table format
+- Configurable error tolerance for parsing malformed CSV data
+
+
+ Converting a Plate value to CSV is not yet supported.
+
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/csv
+```
+
+### Add Plugin
+
+```tsx
+import { CsvPlugin } from '@platejs/csv';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CsvPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+```tsx
+import { CsvPlugin } from '@platejs/csv';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CsvPlugin.configure({
+ options: {
+ errorTolerance: 0.1,
+ parseOptions: {
+ header: true,
+ skipEmptyLines: true,
+ delimiter: ',',
+ },
+ },
+ }),
+ ],
+});
+```
+
+- `options.errorTolerance`: Percentage of rows that can contain errors (default: `0.25` for 25%)
+- `options.parseOptions`: Configuration passed to PapaParse library for CSV parsing
+- `options.parseOptions.header`: Treat first row as headers (default: `true`)
+
+
+
+## Usage
+
+### CSV to Plate
+
+Use the API method to deserialize CSV data:
+
+```tsx
+// Deserialize CSV string to Plate format
+const csvData = `Name,Age,City
+John,30,New York
+Jane,25,Boston`;
+
+const nodes = editor.api.csv.deserialize({ data: csvData });
+```
+
+## Plugins
+
+### CsvPlugin
+
+Plugin for CSV deserialization functionality.
+
+
+
+
+ The tolerance for errors in the CSV data, represented as a percentage in decimal form. This value is calculated as the ratio of errors to the total number of rows.
+
+ - **Default:** `0.25` (This indicates that up to 25% of the rows can contain errors.)
+
+
+ Options to be passed to the PapaParse library for parsing CSV data.
+
+ - **Default:** `{ header: true }` (Indicating that the first row of the CSV data should be treated as a header.)
+ - See [PapaParse documentation](https://www.papaparse.com/docs#config) for more details about these options.
+
+
+
+
+## API
+
+### api.csv.deserialize
+
+Takes a CSV (Comma Separated Values) string and converts it into a Plate compatible format. This function uses the `papaparse` library to parse the CSV data.
+
+
+
+
+ The CSV data string to be deserialized.
+
+
+ Percentage in decimal form, from 0 to ∞, 0 for no errors allowed. Percentage is based on number of errors compared to number of rows.
+ - **Default:** `0.25`
+
+
+ Options to be passed to the PapaParse library for parsing CSV data.
+ - **Default:** `{ header: true }`
+ - See [PapaParse documentation](https://www.papaparse.com/docs#config)
+
+
+
+
+ Array of `Descendant` nodes representing the CSV data in Plate format. Returns `undefined` if parsing fails.
+
+
+
+Creates a table representation of the CSV data:
+- Headers (if present) become the first row
+- Each CSV row becomes a table row
+- Uses plugins: `TablePlugin`, `TableCellHeaderPlugin`, `TableRowPlugin`, and `TableCellPlugin`
diff --git a/apps/www/content/docs/(plugins)/(serializing)/csv.mdx b/apps/www/content/docs/(plugins)/(serializing)/csv.mdx
new file mode 100644
index 0000000000..bdbb465b13
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(serializing)/csv.mdx
@@ -0,0 +1,140 @@
+---
+title: Serializing CSV
+---
+
+
+
+
+
+## Features
+
+- Convert CSV content to Plate table format
+- Configurable error tolerance for parsing malformed CSV data
+
+
+ Converting a Plate value to CSV is not yet supported.
+
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/csv
+```
+
+### Add Plugin
+
+```tsx
+import { CsvPlugin } from '@platejs/csv';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CsvPlugin,
+ ],
+});
+```
+
+### Configure Plugin
+
+```tsx
+import { CsvPlugin } from '@platejs/csv';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ CsvPlugin.configure({
+ options: {
+ errorTolerance: 0.1,
+ parseOptions: {
+ header: true,
+ skipEmptyLines: true,
+ delimiter: ',',
+ },
+ },
+ }),
+ ],
+});
+```
+
+- `options.errorTolerance`: Percentage of rows that can contain errors (default: `0.25` for 25%)
+- `options.parseOptions`: Configuration passed to PapaParse library for CSV parsing
+- `options.parseOptions.header`: Treat first row as headers (default: `true`)
+
+
+
+## Usage
+
+### CSV to Plate
+
+Use the API method to deserialize CSV data:
+
+```tsx
+// Deserialize CSV string to Plate format
+const csvData = `Name,Age,City
+John,30,New York
+Jane,25,Boston`;
+
+const nodes = editor.api.csv.deserialize({ data: csvData });
+```
+
+## Plugins
+
+### CsvPlugin
+
+Plugin for CSV deserialization functionality.
+
+
+
+
+ The tolerance for errors in the CSV data, represented as a percentage in decimal form. This value is calculated as the ratio of errors to the total number of rows.
+
+ - **Default:** `0.25` (This indicates that up to 25% of the rows can contain errors.)
+
+
+ Options to be passed to the PapaParse library for parsing CSV data.
+
+ - **Default:** `{ header: true }` (Indicating that the first row of the CSV data should be treated as a header.)
+ - See [PapaParse documentation](https://www.papaparse.com/docs#config) for more details about these options.
+
+
+
+
+## API
+
+### api.csv.deserialize
+
+Takes a CSV (Comma Separated Values) string and converts it into a Plate compatible format. This function uses the `papaparse` library to parse the CSV data.
+
+
+
+
+ The CSV data string to be deserialized.
+
+
+ Percentage in decimal form, from 0 to ∞, 0 for no errors allowed. Percentage is based on number of errors compared to number of rows.
+ - **Default:** `0.25`
+
+
+ Options to be passed to the PapaParse library for parsing CSV data.
+ - **Default:** `{ header: true }`
+ - See [PapaParse documentation](https://www.papaparse.com/docs#config)
+
+
+
+
+ Array of `Descendant` nodes representing the CSV data in Plate format. Returns `undefined` if parsing fails.
+
+
+
+Creates a table representation of the CSV data:
+- Headers (if present) become the first row
+- Each CSV row becomes a table row
+- Uses plugins: `TablePlugin`, `TableCellHeaderPlugin`, `TableRowPlugin`, and `TableCellPlugin`
diff --git a/apps/www/content/docs/(plugins)/(serializing)/docx.cn.mdx b/apps/www/content/docs/(plugins)/(serializing)/docx.cn.mdx
new file mode 100644
index 0000000000..5252c60c49
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(serializing)/docx.cn.mdx
@@ -0,0 +1,115 @@
+---
+title: DOCX 序列化
+---
+
+
+
+
+
+## 功能特性
+
+- 将粘贴的 DOCX 内容转换为 Plate 格式
+- 清理并规范化 DOCX HTML 内容以确保与 Plate 兼容
+- 支持从 Word 文档保留列表样式和嵌套缩进
+
+
+ 目前暂不支持将 Plate 值转换回 DOCX 格式。
+
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+添加 DOCX 导入功能的最快方式是使用 `DocxKit`,它包含预配置的 `DocxPlugin` 和 `JuicePlugin`,用于处理 DOCX 内容和 CSS 处理。
+
+
+
+### 添加套件
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { DocxKit } from '@/components/editor/plugins/docx-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ ...DocxKit,
+ ],
+});
+```
+
+
+
+## 手动配置
+
+
+
+### 安装依赖
+
+```bash
+npm install @platejs/docx @platejs/juice
+```
+
+### 添加插件
+
+```tsx
+import { DocxPlugin } from '@platejs/docx';
+import { JuicePlugin } from '@platejs/juice';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ DocxPlugin,
+ JuicePlugin,
+ ],
+});
+```
+
+### 配置插件
+
+```tsx
+import { DocxPlugin } from '@platejs/docx';
+import { JuicePlugin } from '@platejs/juice';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...其他插件,
+ DocxPlugin, // 处理 DOCX 内容转换
+ JuicePlugin, // 将 CSS 属性内联到 style 属性中
+ ],
+});
+```
+
+- `DocxPlugin`: 处理粘贴的 DOCX 内容并转换为 Plate 格式
+- `JuicePlugin`: 将 CSS 属性内联到 `style` 属性中以提高兼容性
+
+
+
+## 使用说明
+
+### DOCX 转 Plate
+
+当用户从 Microsoft Word 粘贴内容时,DOCX 插件会自动:
+
+1. 检测剪贴板中的 DOCX 内容
+2. 清理并规范化 HTML 结构
+3. 保留缩进和列表格式
+4. 将 DOCX 特定元素转换为 Plate 格式
+
+该插件与粘贴功能无缝协作 - 安装后无需额外代码。
+
+## 插件说明
+
+### DocxPlugin
+
+用于在粘贴操作中处理 DOCX 内容的插件。
+
+### JuicePlugin
+
+用于将 CSS 属性内联到 HTML 元素中的插件。将外部 CSS 样式转换为内联的 `style` 属性。这对于 DOCX 处理至关重要,因为它能确保从 Word 文档粘贴内容时保留样式信息。
\ No newline at end of file
diff --git a/apps/www/content/docs/(plugins)/(serializing)/docx.mdx b/apps/www/content/docs/(plugins)/(serializing)/docx.mdx
new file mode 100644
index 0000000000..bc4e338b77
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(serializing)/docx.mdx
@@ -0,0 +1,115 @@
+---
+title: Serializing Docx
+---
+
+
+
+
+
+## Features
+
+- Convert pasted DOCX content to Plate format
+- Clean and normalize DOCX HTML content for Plate compatibility
+- Support for list styles and nested indentation from Word documents
+
+
+ Converting a Plate value to DOCX is not yet supported.
+
+
+
+
+## Kit Usage
+
+
+
+### Installation
+
+The fastest way to add DOCX import functionality is with the `DocxKit`, which includes pre-configured `DocxPlugin` and `JuicePlugin` for handling DOCX content and CSS processing.
+
+
+
+### Add Kit
+
+```tsx
+import { createPlateEditor } from 'platejs/react';
+import { DocxKit } from '@/components/editor/plugins/docx-kit';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ ...DocxKit,
+ ],
+});
+```
+
+
+
+## Manual Usage
+
+
+
+### Installation
+
+```bash
+npm install @platejs/docx @platejs/juice
+```
+
+### Add Plugins
+
+```tsx
+import { DocxPlugin } from '@platejs/docx';
+import { JuicePlugin } from '@platejs/juice';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ DocxPlugin,
+ JuicePlugin,
+ ],
+});
+```
+
+### Configure Plugins
+
+```tsx
+import { DocxPlugin } from '@platejs/docx';
+import { JuicePlugin } from '@platejs/juice';
+import { createPlateEditor } from 'platejs/react';
+
+const editor = createPlateEditor({
+ plugins: [
+ // ...otherPlugins,
+ DocxPlugin, // Handles DOCX content transformation
+ JuicePlugin, // Inlines CSS properties into style attributes
+ ],
+});
+```
+
+- `DocxPlugin`: Processes pasted DOCX content and converts it to Plate format
+- `JuicePlugin`: Inlines CSS properties into the `style` attribute for better compatibility
+
+
+
+## Usage
+
+### DOCX to Plate
+
+When users paste content from Microsoft Word, the DOCX plugin automatically:
+
+1. Detects DOCX content in the clipboard
+2. Cleans and normalizes the HTML structure
+3. Preserves indentation and list formatting
+4. Converts DOCX-specific elements to Plate format
+
+The plugin works seamlessly with the paste functionality - no additional code is needed once installed.
+
+## Plugins
+
+### DocxPlugin
+
+Plugin for processing DOCX content during paste operations.
+
+### JuicePlugin
+
+Plugin for inlining CSS properties into HTML elements. Converts external CSS styles to inline `style` attributes. This is essential for DOCX processing as it ensures styling information is preserved when content is pasted from Word documents.
diff --git a/apps/www/content/docs/(plugins)/(serializing)/html.cn.mdx b/apps/www/content/docs/(plugins)/(serializing)/html.cn.mdx
new file mode 100644
index 0000000000..700847b3b5
--- /dev/null
+++ b/apps/www/content/docs/(plugins)/(serializing)/html.cn.mdx
@@ -0,0 +1,421 @@
+---
+title: HTML
+description: 将Plate内容转换为HTML及反向转换。
+toc: true
+---
+
+本指南涵盖将Plate编辑器内容转换为HTML(`serializeHtml`)以及将HTML解析回Plate格式(`editor.api.html.deserialize`)的操作。
+
+
+
+## 套件使用
+
+
+
+### 安装
+
+启用HTML序列化的最快方式是使用`BaseEditorKit`,它包含预配置的基础插件,支持大多数常见元素和标记的HTML转换。
+
+
+
+### 添加套件
+
+```tsx
+import { createSlateEditor, serializeHtml } from 'platejs';
+import { BaseEditorKit } from '@/components/editor/editor-base-kit';
+
+const editor = createSlateEditor({
+ plugins: BaseEditorKit,
+ value: [
+ { type: 'h1', children: [{ text: 'Hello World' }] },
+ { type: 'p', children: [{ text: '此内容将被序列化为HTML。' }] },
+ ],
+});
+
+// 序列化为HTML
+const html = await serializeHtml(editor);
+```
+
+### 示例
+
+查看完整的服务端HTML生成示例:
+
+
+
+
+
+## Plate转HTML
+
+将Plate编辑器内容(Plate节点)转换为HTML字符串。这通常在服务端完成。
+
+[查看服务端示例](/docs/examples/slate-to-html)
+
+
+ 在服务端环境(Node.js, RSC)中使用`serializeHtml`或其他Plate工具时,**不得**从任何`platejs*`包的`/react`子路径导入。始终使用基础导入(例如使用`@platejs/basic-nodes`而非`@platejs/basic-nodes/react`)。
+
+ 这意味着服务端编辑器实例应使用`platejs`中的`createSlateEditor`,而非`platejs/react`中的`usePlateEditor`或`createPlateEditor`。
+
+
+
+
+### 基础用法
+
+提供服务端编辑器实例并在编辑器创建时配置Plate组件。
+
+```tsx title="lib/generate-html.ts"
+import { createSlateEditor, serializeHtml } from 'platejs'; // 基础导入
+// 导入基础插件(不从/react路径导入)
+import { BaseHeadingPlugin } from '@platejs/basic-nodes';
+// 导入用于渲染的静态组件
+import { ParagraphElementStatic } from '@/components/ui/paragraph-node-static';
+import { HeadingElementStatic } from '@/components/ui/heading-node-static';
+// 对于带样式的静态输出,可以使用像EditorStatic这样的包装器
+import { EditorStatic } from '@/components/ui/editor-static';
+
+// 将插件键映射到其静态渲染组件
+const components = {
+ p: ParagraphElementStatic, // 'p'是段落的默认键
+ h1: HeadingElementStatic,
+ // ... 为所有元素和标记添加映射
+};
+
+// 创建带组件的服务端编辑器实例
+const editor = createSlateEditor({
+ plugins: [
+ BaseHeadingPlugin, // 标题基础插件
+ // ... 添加与内容相关的所有其他基础插件
+ ],
+ components,
+});
+
+async function getMyHtml() {
+ // 示例:在服务端编辑器上设置内容
+ editor.children = [
+ { type: 'h1', children: [{text: '我的标题'}] },
+ { type: 'p', children: [{text: '我的内容。'}] }
+ ];
+
+ const html = await serializeHtml(editor, {
+ // 可选:使用像EditorStatic这样的自定义包装器进行样式设置
+ // editorComponent: EditorStatic,
+ // props: { variant: 'none', className: 'p-4 m-4 border' },
+ });
+
+ return html;
+}
+```
+
+### 序列化HTML的样式设置
+
+`serializeHtml`仅返回编辑器内容本身的HTML。如果使用带样式的组件(如`EditorStatic`或具有特定类的自定义静态组件),必须确保最终显示HTML的上下文中包含必要的CSS。
+
+这通常意味着将序列化的HTML包装在包含样式表的完整HTML文档中:
+
+```tsx title="lib/generate-full-html-document.ts"
+// ...(来自generate-html.ts的先前设置)
+
+async function getFullHtmlDocument() {
+ const editorHtmlContent = await getMyHtml(); // 来自之前的示例
+
+ const fullHtml = `
+
+
+
+
+
+
+ 序列化内容
+
+
+
+ ${editorHtmlContent}
+
+
+ `;
+ return fullHtml;
+}
+```
+
+
+ 序列化过程将Plate节点转换为静态HTML。交互功能(React事件处理程序、客户端钩子)或依赖浏览器API的组件在序列化输出中将无法工作。
+
+
+### 使用静态组件
+
+对于服务端序列化,**必须**使用组件的静态版本(无仅客户端代码,无React钩子如`useEffect`或`useState`)。
+
+参考[静态渲染指南](/docs/static)获取为Plate元素和标记创建服务端安全静态组件的详细说明。
+
+```tsx title="components/ui/paragraph-node-static.tsx"
+import React from 'react';
+import type { SlateElementProps } from 'platejs';
+
+// 示例静态段落组件
+export function ParagraphElementStatic(props: SlateElementProps) {
+ return (
+
+ {props.children}
+
+ );
+}
+```
+
+
+
+---
+
+## HTML转Plate
+
+HTML反序列化器允许将HTML内容(字符串或DOM元素)转换回Plate格式。这支持往返转换,在存在对应插件规则的情况下保留结构、格式和属性。
+
+
+
+### 基础用法
+
+在客户端Plate编辑器上下文中使用`editor.api.html.deserialize`。
+
+```tsx title="components/my-html-importer.tsx"
+import { PlateEditor, usePlateEditor } from 'platejs/react'; // 客户端专用的React导入
+// 导入表示HTML内容所需的所有Plate插件
+import { HeadingPlugin } from '@platejs/basic-nodes/react';
+// ... 以及粗体、斜体、表格、列表等的插件
+
+function MyHtmlImporter({ htmlString }: { htmlString: string }) {
+ const editor = usePlateEditor({
+ plugins: [
+ HeadingPlugin, // 用于