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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
let Inline = Quill .import ('blots/inline' );class BoldBlot extends Inline { }BoldBlot .blotName = 'bold' ;BoldBlot .tagName = 'strong' ;class ItalicBlot
extends Inline { }ItalicBlot .blotName = 'italic' ;ItalicBlot .tagName = 'em' ;Quill .register (BoldBlot );Quill .register (ItalicBlot );quill.insertText (0 , 'Test' , { bold : true }); quill.formatText (0 , 4 , 'italic' , true );
类似地,我们定义一个Link Blot。它相比bold,italic不同的是,它需要一个string而不是boolean初始化。因此需要定义
create
和
format
两个函数。其中create在构造Blot时使用,value即输入的href,formats将用户的format字段和真实DOM的字段相关联。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
class LinkBlot extends Inline { static create (value ) { let node = super .create (); node.setAttribute ('href' , value); node.setAttribute ('target' , '_blank' ); return node; } static formats (node ) { return node.getAttribute ('href' ); } } LinkBlot .blotName = 'link' ;LinkBlot .tagName = 'a' ;Quill .register (LinkBlot );
定义引用这样的块级元素时,对应地继承Block Blot即可。和inline Blot不同的是,Block Blot无法嵌套,在对已有的块级元素应用时会替换而不是嵌套绑定在元素上。以Header元素为例,可以指定tagName为一个数组,可以在format时使用1、2的方式指定具体哪种tag。
1 2 3 4 5 6 7 8 9
class HeaderBlot extends Block { static formats (node ) { return HeaderBlot .tagName .indexOf (node.tagName ) + 1 ; } } HeaderBlot .blotName = 'header' ;HeaderBlot .tagName = ['H1' , 'H2' ];
类似的,可以在插入embed Blot,这种类型效果是插入在元素中间的新的tag。如Image。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
let BlockEmbed = Quill .import ('blots/block/embed' );class ImageBlot extends BlockEmbed { static create (value ) { let node = super .create (); node.setAttribute ('alt' , value.alt ); node.setAttribute ('src' , value.url ); return node; } static value (node ) { return { alt : node.getAttribute ('alt' ), url : node.getAttribute ('src' ) }; } } ImageBlot .blotName = 'image' ;ImageBlot .tagName = 'img' ;let range = quill.getSelection (true );quill.insertText (range.index , '\n' , Quill .sources .USER ); quill.insertEmbed (range.index + 1 , 'image' , { alt : 'Quill Cloud' , url : 'https://quilljs.com/0.20/assets/images/cloud.png' }, Quill .sources .USER ); quill.setSelection (range.index + 2 , Quill .sources .SILENT );
简单的例子
展示了module的大致骨架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Quill .register ('modules/counter' , function (quill, options ) { var container = document .querySelector (options.container ); quill.on ('text-change' , function () { var text = quill.getText (); if (options.unit === 'word' ) { container.innerText = text.split (/\s+/ ).length + ' words' ; } else { container.innerText = text.length + ' characters' ; } }); }); var quill = new Quill ('#editor' , { modules : { counter : { container : '#counter' , unit : 'word' } } });
只需要定义一个可以接收quill对象的函数即可,在函数内部利用quill事件监听即可完成应用层的建设。
Toolbar
和
Clipboard
是Quill内置的两个module,对你构建自己的文本编辑器有很大的借鉴意义。
Toolbar用来定制工具栏上的按钮,是自定义编辑器(尤其是业务相关的编辑器)逃不开的一部分。它有几个基本配置:
container
,放置工具栏的DOM容器
handler
,点击ToolBar图标时注册的函数,传入Blot的value,通过调用Quill的API完成功能。也可以通过下面方式注册。
1 2 3
var toolbar = quill.getModule ('toolbar' );toolbar.addHandler ('image' , showImageUI);
在遇到从别的文本编辑器拷贝内容过来的情况时,需要修改ClipBoard Module中
addMatcher
的定义。这个方法向ClipBoard中注册了新的Matcher匹配拷贝过来的HTML文本,将之转换为对应的Blot。如:
1 2 3 4 5 6 7 8
quill.clipboard .addMatcher (Node .TEXT_NODE , function (node, delta ) { return new Delta ().insert (node.data ); }); quill.clipboard .addMatcher ('.custom-class' , function (node, delta ) { return delta.compose (new Delta ().retain (delta.length (), { bold : true })); });
或者在configuration中,注入新定义的matcher即可。
1 2 3 4 5 6 7 8 9 10
var quill = new Quill ('#editor' , { modules : { clipboard : { matchers : [ ['B' , customMatcherA], [Node .TEXT_NODE , customMatcherB] ] } } });
Quill文档
利用javascript搭建富文本编辑器