Parchment是Quill的文档模型。是一个和DOM树对应的平行树结构,给内容编辑器Quill提供有用的功能。node
一个Parchment 树是由Blots构成。Blot是一个DOM节点的对应物。Blots能够提供结构,格式化,或内容。Attributor能够提供轻量级的格式化信息。git
Parchment tree是DOM tree的对应,两者关系紧密。github
LinkBlotdom
import Parchment from 'parchment'; class LinkBlot extends Parchment.Inline { static create(url) { let node = super.create(); node.setAttribute('href', url); node.setAttribute('target', '_blank'); node.setAttribute('title', node.textContent); return node; } static formats(domNode) { return domNode.getAttribute('href') || true; } format(name, value) { if (name === 'link' && value) { this.domNode.setAttribute('href', value); } else { super.format(name, value); } } formats() { let formats = super.formats(); formats['link'] = LinkBlot.formats(this.domNode); return formats; } } LinkBlot.blotName = 'link'; LinkBlot.tagName = 'A'; Parchment.register(LinkBlot);
为何必要?
为了提供一致的编辑体验,你须要一致的数据和可预测的行为。可是DOM在这两方面都不完美。因此现代的编辑器经过管理本身的文档模型来表示内容。编辑器
它的价值?
提供一致的数据和可预测的行为ide
首先须要定义出一套基础抽象节点类型, 一套基础的Attributor源码分析
ParentBlot, ContainerBlot, LeafBlot, EmbedBlot, ScrollBlot, BlockBlot, InlineBlot, TextBlot Attributor ClassAttributor StyleAttributor
而后会依赖于这些基础节点类型,来构造出一些实际节点类型。Quill中定义了一些实际节点优化
BlockBlot => Block EmbedBlot => BlockEmbed EmbedBlot => Break ContainerBlot => Container EmbedBlot => Cursor EmbedBlot => Embed InlineBlot => Inline ScrollBlot => Scroll TextBlot => Text
如何与DOM创建关系?
新建Blot时会调用static create
方法建立dom节点,并设置blot.domNode = dom
。 即创建关系。ui
目录结构this
- src - attributor - attributor.ts - class.ts - store.ts - style.ts - blot - abstract - blot.ts - container.ts - format.ts - leaf.ts - shadow.ts - block.ts - embed.ts - inline.ts - scroll.ts - text.ts - collection - linked-list.ts - linked.node.ts - parchment.ts - registry.ts
节点Blot
属性Attributor
注册中心
类型常量Scope
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); let quill = new Quill('#editor-container'); $('#bold-button').click(function() { quill.format('bold', true); }); $('#italic-button').click(function() { quill.format('italic', true); });
<div class="ql-toolbar ql-snow"></div> <div id="editor-container" class="ql-container ql-snow"> <div class="ql-editor" data-gramm="false" contenteditable="true"></div> <div class="ql-editor" data-gramm="false" contenteditable="true"></div> <div class="ql-tooltip ql-hidden"></div> </div>
3.给toolbar上的icon绑定事件,click触发时执行quill的格式化方法(里面会作一些判断,看是否有selection, 进行对应的格式化)。
this.editor.formatText -> [this.scroll.formatAt, this.update(delta)] -> scrollBlot.formatAt -> parent.formatAt -> inline.formatAt -> inline.format(DOM修改)
quill.format('bold', true)
本质上会找到BoldBlot,而后执行它的format方法(格式化选中部分),同步更新delta, 真实的修改DOM,给selection添加strong标签。
delta同步和DOM同步是彼此独立的,delta同步相对简单一些(但会作一些组合优化)
const delta = new Delta().retain(index).retain(length, clone(formats)); return this.update(delta);`
未选中内容是对光标进行格式化。 this.selection.format -> this.cursor.format
。即会对光标后新写的内容对应格式化。
class DividerBlot extends BlockEmbed { } DividerBlot.blotName = 'divider'; DividerBlot.tagName = 'hr'; $('#divider-button').click(function() { let range = quill.getSelection(true); quill.insertText(range.index, '\n', Quill.sources.USER); quill.insertEmbed(range.index + 1, 'divider', true, Quill.sources.USER); quill.setSelection(range.index + 2, Quill.sources.SILENT); });
insertEmbed基本流程:quill.insertEmbed -> this.editor.insertEmbed -> [this.scroll.insertAt, this.update(delta)] -> 建立DOM,插到指定位置
一样的,建立DOM,更新delta都会进行。