让我们一起完成WYSIWYG(所见即所得)的markdown编辑器「二」

吐槽君 分类:javascript

书接上回

我们在上篇文章中提过,我们将以Slate为编辑器框架搭建markdown编辑器所见即所得。

开头先贴出我目前完成的部分:
让我们一起完成WYSIWYG(所见即所得)的markdown编辑器「二」

强烈推荐remixicon,风格统一,icon量多,用过的开发都说好,适合我们这些视觉审美差的开发者。

目前我完成了标题的所见即所得以及表格的所见即所得;图片目前完成了百分之五十,未来想支持多种功能,例如横屏滑动,九宫格布局....

slate.js的前世今生

slate简介

slate.js是一个「非常轻量」的编辑器框架,它没有集成任何功能,只提供了一个插件扩展机制让开发者去实现自己想要的功能,可插拔性高,轻量化操作,同时,它与「视图」无关,它将视图层独立封装为slate-react,Slate.js自己定义了一套脱离UI实现的数据模型,本文采用的slatejs版本号为:0.57.1.

它像极了angular

说它像angular不是说框架层,而是Slate的历程和angular相似,经历了一次大的改版,去除了之前的很多API以及语言选型从JavaScript转为了typescript。

在github上能搜到的利用slatejs做的编辑器,很大一部分是建立在最初版本的slatejs:0.4x.x上的,就连Slate.js官方文档为了照顾使用之前版本的开发者,也会出一个main版本和0.47版本的文档。

slate架构简介

slate作为一个编辑器框架,他的分层设计明显,仓库下包含四个模块:

  • slate:核心,他定义了数据模型(model),操作模型的方法和编辑器实例本身
  • slate-react: 以插件的形式提供了DOM渲染和用户交互能力,包括光标,快捷键等等。。。
  • slate-history:以插件的形式提供 undo/redo 能力
  • slate-hyperscript:让 用户能够使用jsx的语法创建slate的数据,项目中没有使用到,故暂不会介绍此处

state(model)

这是slate的核心区域,它提供的API在官方文档上不够全面,但是在github上提示的很全面。

工欲善其事,必先利其器

要想使用slate构建编辑器,那必须要了解他的构造。

model结构

state它是以树形结构来创建和存储文档内容的,树形结构的节点类型为Node:

export type Node = Editor | Element | Text

export interface Element {
  children: Node[]
  [key: string]: unknown
}

export interface Text {
  text: string
  [key: string]: unknown
}
 
  • Element 类型含有 children 属性,可以作为其他 Node 的父节点
  • Editor 可以看作是一种特殊的 Element ,它既是编辑器实例类型,也是文档树的根节点
  • Text 类型是树的叶子结点,包含文字信息

他不会限制我们传入的数据类型,这让我们可以自行扩展Node的属性,但是相应的Node类型中必须包含的是children,例如我们定义了一个image类型的Node节点:

export const imageNode = (str: string, link: string): Node => {
    return {
        type: "image",
        url: link,
        desc: str,
        children: [{ text: str }]
    }
};

// 跟踪编辑器中 value 的值。
const [value, setValue] = useState([
   imageNode('ceshi', 'https://baidu.com'),
] as Node[]);
 

那么为什么slate要采用树形结构来描述文档内容呢?

  • 富文本文档本来就含有层次信息,例如,paragraph。text等,用树形结构描述符合我们开发者的直觉感官
  • 文本和属性信息存在一处,方便我们获取文字的同时获取属性信息
  • 方便递归操作

editor

我们可以实时拿到editor对象,如截图所示:

让我们一起完成WYSIWYG(所见即所得)的markdown编辑器「二」

通过截图,我们可以知道editor提供children去存储我们的Node节点,同时提供了很多全局的方法供我们去使用,例如insertNodes 插入节点 removeNode删除节点等等

光标和选区 selection

有了model还需要selection选区,slate的选区采用的是Path+offset的设计。

Path是一个number类型的数组:

export type Path = number[]
 

它代表的是一个Node和他的祖先节点,以及在各自的上一级祖先节点的children数组中的index:

让我们一起完成WYSIWYG(所见即所得)的markdown编辑器「二」

例如上图就是第一行的第一个元素。

offset 则是对于 Text 类型的节点而言,代表光标在文本串中的 index 位置。

Path 加上 offet 即构成了 Point 类型,即可表示 model 中的一个位置。

export interface Point {
  path: Path
  offset: number
}
 

两个 Point 类型即可组合为一个 Range,表示选区。


export interface Range {
  anchor: Point // 选区开始的位置
  focus: Point // 选区结束的位置
}

 

如何对model进行变更---Transforms

Transforms了很多的方法,这些方法大致分成四种类型:

export const Transforms = {
  ...GeneralTransforms,
  ...NodeTransforms,
  ...SelectionTransforms,
  ...TextTransforms,
}
 
  • NodeTransforms:对 Node 的操作方法
  • SelectionTransforms:对选区的操作方法
  • TextTransforms:对文本操作方法
  • GeneralTransforms:它并不生成 Operation 而是对 Operation 进行处理,只有它能直接修改 model,其他 transforms 最终都会转换成 GeneralTransforms 中的一种。

具体还是需要去查阅文档,此处只是相当于一次汇总,但实际上例如插件化等并没有讲解。

回复

我来回复
  • 暂无回复内容