目录
- 如何开发富文本编辑器框架(一) 了解Leaf、Text、Mark
- 如何开发富文本编辑器框架(二)——了解选区
- 如何开发富文本编辑器框架(三)——了解Decoration、Inline、Block、Document模型
- [如何开发富文本编辑器框架(四)——History实现redo、undo]
- [如何开发富文本编辑器框架(五)——借助Schema自动校验内容]
- [如何开发富文本编辑器框架(六)——高扩展性的插件系统]
- [如何开发富文本编辑器框架(七)——神奇的 Change ]
- ...
正文
上次介绍了 Leaf、Text、Mark 模型。今天介绍 Point、Range、Selection 模型。
Point
interface PointInterface {
key: string;
offset: number;
}
Point
是 点 的意思。一个 Range
(范围) 由2个 Point
组成,即起点和终点。Point
的 key
是某个Text
节点的key,这个key是在单独的作用域里维护的,是自增的,每创建一个 Node
节点就会产生一个 key
赋予 Node
节点。
generate-key.ts
/**
* 自增索引
* @type {Number}
*/
let n: number;
/**
* 全局的key生成函数
*
* @type {Function}
*/
let generate: () => string;
/**
* 生成一个key
*
* @return {String}
*/
function generateKey(): string {
return generate();
}
/**
* 自定义唯一key生成函数
*
* @param {Function} func
*/
function setKeyGenerator(func): void {
generate = func;
}
/**
* 重置key
*/
function resetKeyGenerator(): void {
n = 0;
generate = () => `${n++}`;
}
/**
* 初始化
*/
resetKeyGenerator();
export default generateKey;
export { setKeyGenerator, resetKeyGenerator };
创建一个 Text
节点
new Text({
key: generateKey(),
leaves: []
})
Point
的 offset
可以理解相对于 text 节点的位置偏移量。上篇文章里,我举了个 markdown
的例子。
有的文字加粗,有的斜体。
这段文本对应的结构是
Text({
key: 1,
leaves: [
Leaf({
text: '有的文字',
marks: []
}),
Leaf({
text: '加粗',
marks: ['bold']
}),
Leaf({
text: ',有的',
marks: []
}),
Leaf({
text: '斜体',
marks: ['italics']
}),
]
})
此时,有同学产生了一个需求,需要把 加粗 前的 文字
也加粗。那我们该怎么定这个位置?
Range 、Selection
注意:Range 是范围,Selection 是选区,范围没有方向,选区才有方向。从前往后拖选文字,和从后往前拖选文字,selection 不同,但 range 是相同的。
interface RangeInterface {
start: PointInterface, // 起点
end: PointInterface // 终点
}
interface SelectionInterface {
anchor: PointInterface; // 光标开始的点
focus: PointInterface; // 光标结束的点
}
如果是从后往前拖选,anchor 对应的dom节点在后,focus 对应的节点在前面。
通常来说,一个 selection
只会对应一个 range
。
Selection API 最初由 Netscape 创建,并允许多个区域(例如,允许用户从
<table>
中选择列)。但是,Gecko以外的浏览器没有实现多个区域,而且规范还要求选择的内容始终(仅)具有一个范围(允许多个区域可能引起不必要的兼容性问题,例如同时从多处输入)。
回到我们的例子,加粗 两个字的范围是 key = 1, offset 4 ~ 6;文字
的范围是 offset 2~ 4。
我们手动选中了 文字,这是一个浏览器的原生操作,但是怎么告诉框架你选中了数据上的这个 文字
。事实上,框架会监听 onSelect
事件,在事件回调函数里调用原生方法 window.getSelection()
获取起始点的dom信息来更新自己维护的 selection
。(参阅 Selection mdn)
const window = getWindow(event.target);
const { value } = change;
const { document, schema } = value;
const native = window.getSelection(); // 原生选区
// 如果没有范围,slate 自动失去焦点
if (!native.rangeCount) {
change.blur();
return;
}
// 将原生的range 转换成 slate 的 range
let range = findRange(native, value);
if (!range) return;
...
...
// 创建 Slate 的选区
let selection = document.createSelection(range);
selection = selection.setIsFocused(true);
自此文字选中了,框架从它的数据上也知道你选中了哪个范围。接下来我们就要开始加粗文字了。
由于, Slate 是基于受控的 contennteditable 实现的,所以所有会破坏dom结构的事件都会被它拦截掉。Slate
在 onKeyDown
中拦截默认行为,我们根据之前产生的 selection
的 anchor
和 focus
, 找到对应的 Text
节点,接下来就是操作数据(大家都会的对象、数组操作),最后把新的 state
交给 Slate
来更新渲染。
选区的内容,差不多就讲到这里了,如果哪里写的不对或写的不够详尽的,可以在评论里指出,我会修改的。