目录
- 如何开发富文本编辑器框架(一) 了解Leaf、Text、Mark
- 如何开发富文本编辑器框架(二)——了解选区
- 如何开发富文本编辑器框架(三)——了解Decoration、Inline、Block、Document模型
- [如何开发富文本编辑器框架(四)——History实现redo、undo]
- [如何开发富文本编辑器框架(五)——借助Schema自动校验内容]
- [如何开发富文本编辑器框架(六)——高扩展性的插件系统]
- [如何开发富文本编辑器框架(七)——神奇的 Change ]
- ...
Document、Block、Inline模型
19年10月份改版之前,Slate
里是这几个 model,之后改版取消了这些,统一使用新的 Element
模型。
Document
、Block
、Inline
都是一个 Node
的子类。
new Node({
type: string;
object: 'document' | 'block' | 'inline';
data: Map<string, any>;
isVoid: boolean;
})
type
是节点类型,比如 div
p
span
等。
data
是 immutable
里的 Map
对象,里面可以存放自定义的属性,比如存放 img
的 src
,table
的 border
等。
isVoid
是用来标记该节点是否是闭合标签,或者你理解为有没有子节点。比如🏞 <img />
的 isVoid = true
。虽然说是没有子节点,但实际上,我们会在数据结构里给它加一个空的 Text
的子节点,以便于选区操作(图片定位、图片选中等效果)。
Slate 原先采用的是 immutable
的数据结构,后来在很多使用者和贡献者的建议下,改用的 immer
。如果你自己开发框架的话,你可以考虑就用普通的对象、数组来实现,或者用 immer
。 immutable
, 一方面学习成本比较高,对开发者和使用者同样适用。另一方面,某些场景下内存使用以及性能上似乎也有问题, 部分内存无法回收。
这三个节点类型是父子级的关系。Document
的直接子节点只允许是 Block
节点,Block
的子节点不能存在 Document
节点,Inline
的子节点不能存在 Document
和 Block
节点。
这样的好处是,便于内置的 Schema
对文档 “净化”,你的文档不会出现 <span><p>1111</p></span>
这样的 html 片段。相反,缺点在我看来就是强约定的限制。很多时候我们的 html 片段没有我们想象的那么“优雅”,Schema
的“净化”可能导致不可预知的bug。而且实际使用中,有很多html标签我们没法界定它是 Block
节点还是 Inline
节点,比如 <img />
,有的时候我们希望它是 Block
节点,独占一行。有的时候我们又希望它是Inline
节点,可以图文同行显示。这大概也是最新的 Slate 统一使用 Element
来表示节点的原因(Slate: 你觉得img
是 inline
节点,那就 inline
节点吧,我不关心了🙃)。
Decoration
Decoration
的作用有些类似 Mark
,也是文本修饰,是一个 text-level 层级的model,只作用于文本。
new Decoration({
anchor: Point;
focus: Point;
mark: Mark;
})
和之前讲的 Mark
的区别在于,它主要是针对的是“看见”的那一部分 Leaf
节点的的渲染方式,并不会更改文档数据。从官方demo来看,主要是通过改变样式来做搜索的高亮显示,关键字显示🚥等。
新版的 Decoration
移除了mark
, 改成了用户自定义的type,如bold
、highlight
。
new Decoration({
anchor: Point;
focus: Point;
[type: string]: string;
})
// 根据关键字,提取 Decorations
const getDecorations = (node) => {
const ranges = [];
if (search && Text.isText(node)) {
const { text } = node
const parts = text.split(search) // 如果 Text 节点的文本包含 关键字 内容,则提取Decorations
let offset = 0
parts.forEach((part, i) => {
if (i !== 0) {
ranges.push({
anchor: { path, offset: offset - search.length },
focus: { path, offset },
highlight: true,
})
}
offset = offset + part.length + search.length
})
}
return ranges
}
框架会根据 Decoration
的 anchor
和 focus
,找到对应的 Text
节点,并切割它的 leaves
。之后,你可以覆盖 Leaf
的渲染方式,得到预期的高亮显示。
// 自定义Leaf的渲染方式
const Leaf = ({ attributes, children, leaf }) => {
return (
<span
{...attributes}
className={css`
font-weight: ${leaf.bold && 'bold'};
background-color: ${leaf.highlight && '#ffeeba'};
`}
>
{children}
</span>
)
}
// 参考新版的 Slate 的demo
<Editable renderLeaf={props => <Leaf {...props} />} />
// 渲染 Text 节点的代码大致类似这样
const Text = ({node, attributes}) => {
const { leaves } = node;
const decs = getDecorations(node);
cosnt leaves = node.getLeaves(decs); // 如果存在descorations,则分割内部的leaves。
const children = leaves.map((leaf) => {
const child = renderLeaf(leaf);
return child
});
return (
<span {...attributes}>
{children}
</span>
);
}
差不多这几个模型就介绍到这里,如有不正确的地方,👏评论指出。