使用quill搭建文本编辑器
quill可以是一个文本编辑器JS库或是文本编辑器构建库。它提供了结构化数据方式用脱离语言的方式描述编辑器内容,同时预置了内置插件,支持自定义插件,有助于在此基础上进行和业务相关编辑器开发。
准备工作
关于contenteditable
属性,selection
对象和range
对象的介绍,可以参考这篇文章。
配置
1 | const editor = new Quill("#container"); |
quill通常使用上面的方式初始化。在初始化时,支持用丰富的配置项定义生成的编辑器。下面是一个例子。
1 | const options = { |
Quill支持的配置项有:
bounds
,Quill UI元素的限制范围,默认document.body
debug
,是Quill.debug
的快捷方式,用于打印调试信息,默认级别为warn
formats
,Quill中允许出现的格式,默认为所有格式,它和toolbar是解耦的modules
,注册在Quill中的功能模块和与之对应的配置信息,Quill会有默认配置placeholder
,提示信息readOnly
,是否可写scrollContainer
,编辑器滚动条的父级,默认为编辑器本身strict
,版本更新配置,默认为true
theme
,整体外观,默认为snow
,bubble
可选
支持的文本格式
分为行内,块级,嵌入式三大类。
- 行内:加粗/背景色/字体颜色/字体/行内代码/斜体/下划线/删除线/链接/字体大小/上、下标
- 块级:引用/标题/行首缩进/有序、无序列表/对齐/文本方向/代码块
- 嵌入式:音频/视频/公式
API
按照由浅入深,分为修改内容,修改格式,选取,编辑器本身,事件,数据模型操作,拓展几大类。涉及到内容修改的都会返回代表更改的delta。
修改内容
涉及到修改时,最后一个参数都可以选择user
,api
,silent
。user
类型下,disabled时会没有效果。
deleteText
,输入起始点和长度,删除特定范围的内容,返回delta类型数据。如quill.deleteText(6, 4)
getContents
,获取delta格式的编辑器内容,如quill.getContents()
getLength
,获取文本长度,quill默认会有一个空行,所以默认返回1getText
,获取文本内容,跳过非文本如音视频元素,如quill.getText(0, 10)
insertEmbed
,输入位置,类型,值,插入嵌入式内容,如quill.insertEmbed(10, 'image', 'https://quilljs.com/images/cloud.png')
insertText
,插入文本,可带格式。如下1
2
3
4
5
6quill.insertText(0, 'Hello', 'bold', true);
quill.insertText(5, 'Quill', {
'color': '#ffff00',
'italic': true
});setContent
,输入delta,重设编辑器内容,需以\n
结尾。如下:1
2
3
4
5quill.setContents([
{ insert: 'Hello ' },
{ insert: 'World!', attributes: { bold: true } },
{ insert: '\n' }
]);setText
,设置文本,返回代表改变的deltaupdateContent
,输入delta,更新内容,返回代表更新的delta
修改格式
format
,设置用户当前所选的文本格式,如quill.format('color', 'red');
formatLine
,设置给定选择当前整行样式,使用类似format
的方法设置样式,也支持直接传入格式对象。类似quill.formatLine(1, 2, { 'align': 'right'})
formatText
,设置给定范围内文本格式,类似formatLine
getFormat
,获取给定范围内的格式,没有输入时,返回当前选择的格式removeFormat
,移除范围内样式
选取
getBounds(index, length = 0)
,返回的top、width、height、left相对于编辑器容器而言getSelection(focus = false)
,返回用户的选取范围,由index、length组成setSelection(index, length = 0)
,设置选区范围,会自动focus,输入null会自动blur
编辑器本身
blur
,失焦disable
,禁用enable(enabled = false)
,启用focus
,聚焦hasFocus
,是否聚焦update
,同步用户改动,协同工作时常用
事件
事件通过on
方法绑定在quill对象上。
text-change
,quill内的内容改变时触发,回调函数可以获取delta、oldContent,source。通常来自’user’,source
为’silent’时,该事件不会触发。selection-change
,回调函数可以获取range,oldRange,sourceeditor-change
,上述两个事件触发时触发,即使source为silent
除了on
方法,还有once
用于绑定一次和off
方法解绑。
数据模型操作
find
,寻找DOM节点对应的quill或Blot对象getIndex
,返回文档开头到给定Blot的偏移量getLeafBlot
,返回给定位置的BlotgetLine
,返回给定位置整行的BlotgetLines
,返回给定范围的Blot
拓展
debug
,设置调试信息级别,info | log | warn | error
import
,导出quill相关库,输入相对于quill的路径1
2
3
4
5var Parchment = Quill.import('parchment');
var Delta = Quill.import('delta');
var Toolbar = Quill.import('modules/toolbar');
var Link = Quill.import('formats/link');register
,注册module到quill中,有下面几种用法1
2
3Quill.register(format: Attributor | BlotDefinintion, supressWarning: Boolean = false)
Quill.register(path: String, def: any, supressWarning: Boolean = false)
Quill.register(defs: { [String]: any }, supressWarning: Boolean = false)addContainer
,新增容器并返回enable/disable
,启用、禁用编辑器
delta
delta是quill中最重要的概念。据介绍所说,quill是“第一个”使用delta(结构化数据)这个概念的。不同于其他大多数文本编辑器需要反复执行修改编辑器中的HTML文档。quill维护一个delta数组,使用JSON数据的方式描述了文档的内容。
使用delta一词,并没有问题,因为可以理解成文档本身是由空内容 + delta一点点得到的。delta主要有两个特性:
- 权威性,delta和对应的生成结果是一一对应的,没有歧义
- 压缩性,delta中描述的操作是经过压缩后的
delta中的操作可以分为增、删、修改格式,分别对应insert
,delete
和retain
操作。对文本编辑器的一次改动(真实世界中的改动行为)只可能涉及上述三种行为的一种(Quill并不允许Ctrl多处选中)。其中retain的意义类似于光标的移动,它使得这三种操作并不需要使用index描述,便于Quill做优化和压缩。
delta的操作实际上是对parchment进行的,它类似于vdom,使用JS的数据结构对文本编辑器中可能出现的各元素进行了抽象,称作Blot。Blot有scroll,inline、block、text,break几种。父Blot下必须包含至少一个子Blot,而所有的Blot都包含在一个scroll Blot下。文本编辑器中特定格式的文本块都用特定的Blot表示,每个这样的Blot都必须继承自上面的一种Blot类型。就像通过下面的方式继承了Blot,就可以使对应的行内元素得到对应的编辑器样式元素对应起来,并使用在后面的编辑器里。
1 | let Inline = Quill.import('blots/inline'); |
类似地,我们定义一个Link Blot。它相比bold,italic不同的是,它需要一个string而不是boolean初始化。因此需要定义create
和format
两个函数。其中create在构造Blot时使用,value即输入的href,formats将用户的format字段和真实DOM的字段相关联。
1 | class LinkBlot extends Inline { |
定义引用这样的块级元素时,对应地继承Block Blot即可。和inline Blot不同的是,Block Blot无法嵌套,在对已有的块级元素应用时会替换而不是嵌套绑定在元素上。以Header元素为例,可以指定tagName为一个数组,可以在format时使用1、2的方式指定具体哪种tag。
1 | class HeaderBlot extends Block { |
类似的,可以在插入embed Blot,这种类型效果是插入在元素中间的新的tag。如Image。
1 | let BlockEmbed = Quill.import('blots/block/embed'); |
modules
quill中的module位于quill的应用层。可以通过定制modules,利用quill的功能;或是更改quill内置module,修改quill本身的行为和功能。clipboard、keyboard、history三个module是quill默认加载的。用户完全可以根据业务需求定义自己的module。官网给了简单的例子展示了module的大致骨架:
1 | Quill.register('modules/counter', function(quill, options) { |
只需要定义一个可以接收quill对象的函数即可,在函数内部利用quill事件监听即可完成应用层的建设。
Toolbar和ClipBoard
Toolbar和Clipboard是Quill内置的两个module,对你构建自己的文本编辑器有很大的借鉴意义。
Toolbar用来定制工具栏上的按钮,是自定义编辑器(尤其是业务相关的编辑器)逃不开的一部分。它有几个基本配置:
container
,放置工具栏的DOM容器handler
,点击ToolBar图标时注册的函数,传入Blot的value,通过调用Quill的API完成功能。也可以通过下面方式注册。
1 | // Handlers can also be added post initialization |
在遇到从别的文本编辑器拷贝内容过来的情况时,需要修改ClipBoard Module中addMatcher
的定义。这个方法向ClipBoard中注册了新的Matcher匹配拷贝过来的HTML文本,将之转换为对应的Blot。如:
1 | quill.clipboard.addMatcher(Node.TEXT_NODE, function(node, delta) { |
或者在configuration中,注入新定义的matcher即可。
1 | var quill = new Quill('#editor', { |