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都會進行。