Skip to content
Draft

blog #4503

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
**/index.ts
**/*.mdx
**/__tests__/**/*.html
**/.contentlayer
**/__registry__
**/og
apps/www/src/app/globals.css
98 changes: 98 additions & 0 deletions apps/www/content/blogs/affinity.cn.mdx
Original file line number Diff line number Diff line change
@@ -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 这样的顶级编辑器,也没有很好地解决这个问题

<Image src="/blog/affinity/1.gif" alt="代码块光标亲和力演示" width={1000} height={1000} />

类似的问题也存在于 加粗(bold)文本中。

当我的光标落在加粗和普通文本之间的边界时,编辑器该如何判断:

我接下来要输入的是加粗的内容,还是普通样式的内容?

大多数编辑器的处理是:继承左侧的样式,即便用户希望输入的是普通文本。

<Image src="/blog/affinity/2.gif" alt="加粗文本光标亲和力演示" width={1000} height={1000} />

## 解决方案

不过在 plate v49 中,只需要一行配置,你就可以彻底解决光标在块级元素边界"跳进跳出不可控"的问题:

```tsx
createSlatePlugin({
rules: { selection: { affinity: 'hard' } },
})
```

这样设置之后,当你使用方向键在 code 标记(如 const a = 1;)周围移动光标时,系统会明确区分:

从外部移入 → 第一次停在边缘;
再按一次 → 才进入 code 内部。

这就像是给光标加了一道"缓冲层",避免误触样式,让输入变得更精准、更符合预期。

如下图所示,code 的左右两侧各有一个独立的光标位置,不再是传统编辑器中的"边界即跳转"。

<Image src="/blog/affinity/3.gif" alt="光标亲和力演示" width={1000} height={1000} />

### Affinity: 'directional'

不过回到 加粗文本(bold) 的情况,事情又有点不同。

由于 bold 的左右两边 没有任何 padding,当你光标靠近边界时,第一下箭头其实已经起作用了,但用户看不到任何视觉反馈,这就造成了一种错觉:

"我按下了左箭头,但光标好像没动?"

这也意味着,如果我们在 bold 上使用 affinity: 'hard',反而会让用户觉得键盘"失灵"了。

为了解决这个问题,Plate.js 提供了另一种策略 还是一行代码:

```ts
rules: { selection: { affinity: 'directional' } },
```

使用 affinity: 'directional' 后,光标行为将根据移动方向智能判断:

* 从右往左离开 text → 新输入会继承 普通样式;
* 从左往右离开 bold → 输入将是bold样式。

<Image src="/blog/affinity/4.gif" alt="光标亲和力演示" width={1000} height={1000} />

这个策略利用了用户的操作意图,让输入行为更加自然和可预测,同时避免了视觉"卡顿"。

不过默认我们只为 Link 开启了 directional 策略,详情见 [Rules](https://platejs.org/docs/plugin-rules#rulesselection)

## 最后

最重要的是:
这一切的控制权,完全掌握在你手里。

无论是 bold、italic、code,还是 link——
你都可以为每一个样式(Mark),甚至每一个内联元素,指定最适合的光标行为策略。

是选择 hard,让光标拥有明确的边界感?
还是选择 directional,根据方向智能判断输入样式?
还是干脆保持默认行为,沿用编辑器的标准策略?

选择权在你手上。每一种策略,都只需一行配置即可启用。

Plate.js 给你的不只是功能,而是掌控感。
98 changes: 98 additions & 0 deletions apps/www/content/blogs/affinity.mdx
Original file line number Diff line number Diff line change
@@ -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.

<Image src="/blog/affinity/1.gif" alt="Code block cursor affinity demo" width={1000} height={1000} />

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.

<Image src="/blog/affinity/2.gif" alt="Bold text cursor affinity demo" width={1000} height={1000} />

## 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.

<Image src="/blog/affinity/3.gif" alt="Cursor affinity demo" width={1000} height={1000} />

### 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.

<Image src="/blog/affinity/4.gif" alt="Cursor affinity demo" width={1000} height={1000} />

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.
50 changes: 50 additions & 0 deletions apps/www/content/blogs/mutiple-nodes-dnd.cn.mdx
Original file line number Diff line number Diff line change
@@ -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 功能,选中多个块后一起拖动:

![block-selection](/blog/multiple-nodes-dnd/1.gif)

- **方式二:利用编辑器原生 selection**
直接用编辑器的原生 selection 选中多块内容,再一次性拖拽:

![selection](/blog/multiple-nodes-dnd/2.gif)

---

### 2. 修复 margin-top 区域无法 drop 的问题

- 此前,当目标块上方存在 `margin-top` 时,用户无法在该区域完成 drop 操作,影响了拖拽的流畅性:

![issue](/blog/multiple-nodes-dnd/3.gif)

- 在新版本中,这一问题已**被修复**。现在,drop 区域能够正确识别 margin,用户可以在 margin-top 区域顺利完成拖拽和插入,体验更加自然:

![fixed issue](/blog/multiple-nodes-dnd/4.gif)



## 展望

本次更新,让整体拖拽体验更加顺滑自然。但要实现“随心所欲”的拖拽,还需进一步完善:

- 支持在编辑器外 drop
- 优化 hover 表格边框时 drop line 的位置

49 changes: 49 additions & 0 deletions apps/www/content/blogs/mutiple-nodes-dnd.mdx
Original file line number Diff line number Diff line change
@@ -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:

![block-selection](/blog/multiple-nodes-dnd/1.gif)

- **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:

![selection](/blog/multiple-nodes-dnd/2.gif)

---

### 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:

![issue](/blog/multiple-nodes-dnd/3.gif)

- 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:

![fixed issue](/blog/multiple-nodes-dnd/4.gif)



## 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
66 changes: 66 additions & 0 deletions apps/www/content/docs/(guides)/controlled.cn.mdx
Original file line number Diff line number Diff line change
@@ -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)` 函数。

<Callout type="warning" title="性能考量">
使用 `editor.tf.setValue` 会在每次调用时重新渲染所有节点,因此应谨慎使用。
如果频繁调用或处理大型文档,可能会影响性能。
</Callout>

或者,您可以使用 `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 <div>加载中…</div>;

return (
<Plate editor={editor}>
<EditorContainer>
<Editor />
</EditorContainer>
</Plate>
);
}
```

<ComponentPreview name="controlled-demo" padding="md" />
Loading