240 lines
7.2 KiB
Markdown
240 lines
7.2 KiB
Markdown
# [recycle] Rust Block Editor Interaction Samples v1
|
||
|
||
> 更新时间:2026-04-18
|
||
>
|
||
> 主要来源:
|
||
> - `/mnt/Data1T/mnote-next/docs/architecture/p1-human-stable-interface.md`
|
||
> - `/mnt/Data1T/mnote-next/apps/web/app/components/editor/BlockEditor.tsx`
|
||
> - `/mnt/Data1T/mnote-next/apps/web/app/components/editor/*.test.mjs`
|
||
>
|
||
> 定位说明:
|
||
> - 这些样例是内部回归样本
|
||
> - 用来冻结“人层已稳定语义”
|
||
> - 明确 **不作为主 benchmark**
|
||
|
||
## 1. 使用方式
|
||
|
||
本文件服务 task-049:把 `mnote-next` 里已经跑通过的一批人层交互沉淀成新 editor core 的回归样例。
|
||
|
||
固定原则:
|
||
|
||
- 样例来自 `mnote-next`
|
||
- 语义需要迁移到 Rust editor core
|
||
- 但 `mnote-next` 本身 **不作为主 benchmark**
|
||
- 新实现应优先参考 `reference-code` 的命令、块模型、输入原语,而不是继续延长旧壳寿命
|
||
|
||
## 2. 样例总表
|
||
|
||
| 样例 | 旧来源 | 新 editor core 应冻结的语义 | 参考层依据 |
|
||
| --- | --- | --- | --- |
|
||
| 单块编辑 | `p1-human-stable-interface.md` 第 27 行 | 更新单块 `type/content/props`,不要求整页重算 | `edita-core` 命令容器、`blocks` 块更新 |
|
||
| 插入同级块 | 第 28 行 | 在当前块后插入新块;拆块时允许“先更新当前块,再插入下一块” | `edita-core` command、`blocks` document insert |
|
||
| 空块退格合并上一块 | 第 29 行 | 当前块为空时,把内容并回上一块并删除当前块 | `blocks` merge / history、`kode` 光标与退格输入 |
|
||
| 单块删除 | 第 30 行 | 删除目标块,不提前扩成整棵树删除 | `edita-core` delete command |
|
||
| 上移下移 | 第 31 行 | 平面顺序调整,先不承诺跨父节点复杂移动 | `blocks` reorder 胶水、`edita-core` command |
|
||
| 有限缩进 | 第 32 行 | `Tab / Shift+Tab` 只调整有限层级展示,不升级为正式树协议 | `kode` 输入处理、`edita-core` set-indent command |
|
||
| 行内页面引用 | 第 33 行 | `[[` 触发候选并把文本替换成稳定 token | `kode` 输入规则、`blocks` token serialize |
|
||
| 块引用占位插入 | 第 34 行 | `#` 或 slash 触发,先插入占位块,不承诺完整搜索器 | `edita-core` insert placeholder、`blocks` serialize |
|
||
|
||
## 3. 回归样例明细
|
||
|
||
### 3.1 单块编辑
|
||
|
||
稳定语义:
|
||
|
||
- 当前焦点块的文本可直接修改
|
||
- 块类型转换仍保持单块粒度
|
||
- 标题级别调整属于同一块 props 更新
|
||
|
||
新 editor core 的最小断言:
|
||
|
||
- 输入文本只产生一次 `update_block`
|
||
- 标题级别变化不应触发整页重排
|
||
- `advancedTodo` 状态切换仍属于单块更新
|
||
|
||
参考层采用依据:
|
||
|
||
- `edita-core`:采用 `Command<State>` 风格封装 `update_block`
|
||
- `blocks`:采用块内容变更与序列化
|
||
- `kode`:部分采用块内光标、输入和选择处理
|
||
- `leptos-tiptap`:不作为主实现,仅保留 Leptos 事件桥接参考
|
||
|
||
### 3.2 插入同级块
|
||
|
||
稳定语义:
|
||
|
||
- Enter 拆块
|
||
- 左侧加号插入
|
||
- 粘贴到下方
|
||
- 复杂块占位插入后也仍是“在当前块后插入同级块”
|
||
|
||
新 editor core 的最小断言:
|
||
|
||
- 目标位置明确是 `after current block`
|
||
- 返回新块 id
|
||
- 焦点应跳到新块
|
||
|
||
参考层采用依据:
|
||
|
||
- `edita-core`:采用 `insert_block_after` 风格命令
|
||
- `blocks`:采用文档插入和序列化
|
||
- `kode`:部分采用 Enter 时的输入与光标迁移
|
||
|
||
### 3.3 空块退格合并上一块
|
||
|
||
稳定语义:
|
||
|
||
- 当前块为空
|
||
- 按 `Backspace`
|
||
- 若上一块允许合并,则把当前块内容并入上一块并删除当前块
|
||
|
||
新 editor core 的最小断言:
|
||
|
||
- 合并后焦点回到上一块末尾
|
||
- 不应残留空块
|
||
- 若块类型不兼容,则明确返回 no-op
|
||
|
||
参考层采用依据:
|
||
|
||
- `blocks`:采用 merge / history 的思路
|
||
- `kode`:部分采用退格、选择、光标偏移处理
|
||
- `edita-core`:采用“命令执行后改写状态”的方式,不把逻辑写死在 DOM 事件里
|
||
|
||
### 3.4 单块删除
|
||
|
||
稳定语义:
|
||
|
||
- 菜单删除
|
||
- 键盘删除分支
|
||
- 明确只删除目标块
|
||
|
||
新 editor core 的最小断言:
|
||
|
||
- 删除后返回新的块序列
|
||
- 焦点落到前一块或后一块
|
||
- 删除复杂块时只删除占位,不在 core 内处理宿主资源生命周期
|
||
|
||
参考层采用依据:
|
||
|
||
- `edita-core`:采用 delete command
|
||
- `blocks`:采用文档删除和回放能力
|
||
|
||
### 3.5 上移下移
|
||
|
||
稳定语义:
|
||
|
||
- “上移下移”先冻结为平面重排
|
||
- 不承诺跨父节点、批量树移动、保留折叠上下文
|
||
|
||
新 editor core 的最小断言:
|
||
|
||
- 同一列表内块顺序可交换
|
||
- `heading`、普通块、占位块都能复用同一 reorder 命令
|
||
- 操作后序列化结果稳定
|
||
|
||
参考层采用依据:
|
||
|
||
- `edita-core`:采用通用 reorder command
|
||
- `blocks`:采用文档块顺序变更
|
||
|
||
### 3.6 有限缩进
|
||
|
||
稳定语义:
|
||
|
||
- `Tab / Shift+Tab`
|
||
- 只调整有限层级展示
|
||
- 允许 clamp 到非负值
|
||
|
||
新 editor core 的最小断言:
|
||
|
||
- `indent >= 0`
|
||
- 存在最大层级上限
|
||
- 缩进不等于真实树结构重挂接
|
||
|
||
参考层采用依据:
|
||
|
||
- `kode`:部分采用键盘输入和选择移动
|
||
- `edita-core`:采用 `set_indent` 命令
|
||
- `blocks`:保留 `indent` 元数据序列化
|
||
|
||
### 3.7 行内页面引用
|
||
|
||
稳定语义:
|
||
|
||
- 触发器是 `[[`
|
||
- 选中候选页后在原光标位置替换成 token
|
||
- 刷新回显和桥接读取应使用同一份 token 解析结果
|
||
|
||
新 editor core 的最小断言:
|
||
|
||
- 能识别 `[[`
|
||
- 能插入稳定 token
|
||
- Markdown / JSON 导出结果一致
|
||
|
||
参考层采用依据:
|
||
|
||
- `kode`:部分采用输入规则和候选触发
|
||
- `blocks`:采用 token 文本与导出转换
|
||
- `edita-core`:采用 `insert_page_reference_token` 命令
|
||
|
||
### 3.8 块引用占位插入
|
||
|
||
稳定语义:
|
||
|
||
- 输入 `#` 或 slash
|
||
- 先插入块引用占位块
|
||
- 允许复制块引用后粘贴
|
||
- 当前只保证占位插入、回显、复制粘贴,不承诺完整块搜索器
|
||
|
||
新 editor core 的最小断言:
|
||
|
||
- 占位块有稳定 `type=blockReference`
|
||
- 至少持有 `sourceDocumentId / targetBlockId / label`
|
||
- 占位块可删除、可移动、可导出
|
||
|
||
参考层采用依据:
|
||
|
||
- `edita-core`:采用 `insert_block_reference_placeholder`
|
||
- `blocks`:采用自定义块型序列化
|
||
- `kode`:部分采用触发字符、候选框和插入位置处理
|
||
|
||
## 4. 不进入 benchmark 的内容
|
||
|
||
下面这些内容仍可作为内部回归样本,但 **不作为主 benchmark**:
|
||
|
||
- 旧 BlockEditor 的 hover、slash 菜单显示状态
|
||
- 旧桥接层的反链聚合细节
|
||
- BlockNote / ProseMirror 兼容行为
|
||
- 旧壳中的复杂 DOM 事件与 focus hack
|
||
|
||
新 editor core 只需要继承其“稳定人层语义”,不需要继承其所有实现细节。
|
||
|
||
## 5. 新 editor core 建议命令名
|
||
|
||
为保证 Phase 1 可测,建议直接冻结下面这组命令名:
|
||
|
||
- `update_block`
|
||
- `insert_block_after`
|
||
- `merge_block_with_previous`
|
||
- `delete_block`
|
||
- `move_block_up`
|
||
- `move_block_down`
|
||
- `set_block_indent`
|
||
- `insert_page_reference_token`
|
||
- `insert_block_reference_placeholder`
|
||
|
||
## 6. 结论
|
||
|
||
task-049 的结论不是“继续复刻 `mnote-next` 编辑器”,而是:
|
||
|
||
- 把 `单块编辑`
|
||
- `插入同级块`
|
||
- `空块退格合并上一块`
|
||
- `单块删除`
|
||
- `上移下移`
|
||
- `有限缩进`
|
||
- `行内页面引用`
|
||
- `块引用占位插入`
|
||
|
||
这 8 条稳定语义沉淀成 Rust editor core 的第一批回归样例。
|