editor:编辑器
编辑一侧的全部内容:EditorCore(配置与节点工厂)、EditorComponent(React 组件与树操作)、
插件系统、Slate 节点工具函数,以及编辑器状态 hooks。
EditorCore
EditorCore 是编辑一侧的入口对象:应用启动时构造一次,之后全程共用。 三个构造参数与教程第 3 章的用法一致:
new EditorCore({
renderers: EditorRendererDict, // {group: {一级概念名: 渲染器}, ...}
default_renderers: EditorDefaultRendererDict, // 每种节点类型一个默认渲染器
printer: Printer, // 概念定义从这里查询
})
它的方法分两类。一类是节点工厂(create_ 系列):按二级概念名创建各种新节点,
idx、占位子节点这些细节都会自动填好,程序化构造文档内容时用它们,而不要手写对象字面量。
另一类是查询:概念清单、渲染器、关联的印刷器。
| 方法 | 说明 |
|---|---|
create_text(text?) / create_paragraph(text?) | 创建文本、段落节点 |
create_group(name, relation?) | 按二级概念名创建组节点,relation 缺省为 "separating" |
create_inline(name, text?) / create_support(name) / create_structure(name, relation?) / create_abstract(name) | 创建其余各类概念节点,同样按二级概念名 |
get_sec_concept_list(type) / get_fst_concept_list(type) | 列出某节点种类下注册的二级、一级概念名 |
get_first_renderer(type, fst?) / get_second_renderer(type, sec?) / get_node_renderer(node) | 渲染器查询,查不到时返回默认渲染器 |
get_printer() | 取得关联的印刷器 |
get_meta_param(node) | 取得节点一级概念的元参数 |
EditorComponent
编辑器的 React 组件(类组件)。实际使用中你通常挂载的是它的包装
DefaultEditorComponent(见默认实现那页),
两者共享同一套 props:
interface EditorComponentProps {
editorcore: EditorCore
plugin?: EditorPlugin // 自定义插件,叠加在内置插件之外
init_rootchildren?: AbstractNode["children"] // 初始文档内容
init_rootproperty?: Omit<AbstractNode, "children"> // 初始根节点属性
onUpdate?: (v: any) => void // 树每次更新时回调
onKeyDown? / onKeyUp?: (e) => void // 键盘事件回调
onFocusChange?: (editor?) => void // 光标位置变化回调
}
| 实例方法 | 说明 |
|---|---|
get_root(): AbstractNode | 把当前编辑内容组装成一棵完整的文档树,保存文档的入口 |
get_core() / get_slate() | 取得 EditorCore、底层的 Slate 编辑器对象 |
get_cur_node() / get_cur_concept_node() | 取得光标所在的节点、光标所在的最近的概念节点 |
get_node_by_path(path) | 按路径取节点 |
new_concept_node(type, sec_name) | 在当前位置插入指定二级概念的新节点(侧边栏插入按钮的底层实现) |
set_root(property) | 修改根节点的属性 |
add_apply_callback(cb) | 注册一个在下一次 Slate apply 之后执行的回调 |
树操作方法
以下方法也在 EditorComponent 上(通过 mixin 混入,源码在 treeopmixin),
是对 Slate Transforms 的概念级封装。写插件和自定义按钮时主要打交道的就是它们:
| 方法 | 说明 |
|---|---|
set_node(node, partial) / set_node_by_path(path, partial) | 修改节点属性 |
auto_set_parameter(node, parameters) | 修改节点参数,自动处理代理节点的情形 |
add_nodes(nodes, path) / add_nodes_here(nodes) | 插入节点:指定路径,或当前光标处 |
add_nodes_before(nodes, target) / add_nodes_after(nodes, target) | 在目标节点前、后插入 |
delete_concept_node(node) / delete_node_by_path(path) / delete_nodes_by_paths(paths) | 删除节点。批量版本按显式路径删除,不依赖当前选区(在 effect 等无焦点场景也能工作),无效路径会被静默忽略 |
unwrap_node(node) | 删除节点但保留其子节点 |
move_concept_node(node, posto) / move_node_by_path(from, to) | 移动节点 |
replace_nodes(father, nodes) | 替换某节点的全部子节点 |
wrap_selected_nodes(node, opts) / wrap_nodes(node, from, to, opts) | 把当前选区、指定范围包进一个新节点 |
编辑渲染器
编辑渲染器本质上就是一个 React 组件,接收编辑器、节点和子内容三样东西:
type EditorRenderer<NT extends Node = Node> =
(props: EditorRendererProps<NT>) => React.ReactElement
interface EditorRendererProps<NT> {
editor: EditorComponent
node: NT
children: React.ReactNode // Slate 提供的子内容,渲染器必须把它输出出去
}
这是编辑渲染器的底层形态。日常开发中很少直接手写, 一般用默认实现提供的工厂函数生成,那些工厂已经处理好了按钮、参数编辑等公共部分。
插件系统
插件是一个包装 Slate 编辑器的函数:
type EditorPlugin =
(editor: EditorComponent, slate: SlateReact.ReactEditor) => SlateReact.ReactEditor
插件接收编辑器和 Slate 编辑器对象,返回改造后的 Slate 编辑器, 与 Slate 生态的插件写法一致。编写方法见教程第 7 章。相关导出:
with_ytext_plugins:内置插件链,挂载编辑器组件时自动应用,不需要手动调用。 包含九个插件:元参数应用(行内、无内容节点的处理)、四类子节点约束、relation 修复、抽象节点修复、参数修复。set_normalize_status({initializing?, pasting?})与get_normalize_status(key): 读写规范化状态。文档装载和粘贴过程中树会经历中间状态, 自定义插件应当在initializing为 true 时跳过自己的检查,否则会干扰装载。
Slate 节点工具函数
在插件和按钮里检查 Slate 节点时的常用函数,都以 slate_ 开头:
| 函数 | 说明 |
|---|---|
slate_is_concept(node, type?) | 是否为概念节点,第二个参数可以限定节点种类 |
slate_is_paragraph / slate_is_text | 是否为段落、文本节点 |
slate_get_node_type(node) | 取节点类型字符串 |
slate_is_same_concept_node(a, b) | 按 idx 判断两个引用是否指向同一个节点 |
slate_concept_node2path(root, node) | 求节点在树中的路径 |
slate_concept_father(root, node) / slate_concept_father_path(root, path) | 求最近的概念祖先节点 |
slate_idx_to_node(root, idx) | 按 idx 查找节点 |
编辑器状态 hooks
在编辑器之外的组件(比如你放在页面其他位置的工具栏)想感知编辑器状态时,用这些 hooks:
| Hook | 说明 |
|---|---|
useEditor() | 在编辑渲染器内部取当前的 EditorComponent;不在编辑器上下文内调用会抛错 |
useCurEditor() | 在任意位置取当前编辑器,基于 Zustand 实现,随编辑器版本刷新 |
useCurConceptNode() | 取光标所在的概念节点,节点变化时触发重渲染 |
useCurConceptNodeIdxParam() | 同上,但只在节点的 idx 或参数变化时才触发重渲染,开销更小 |
useEditorState | 底层的 Zustand store,需要更精细的订阅控制时使用 |