quill parchment

概念介紹

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

如何構造出一套文檔模型?如何與DOM創建關係?

首先須要定義出一套基礎抽象節點類型, 一套基礎的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

類圖完整版

圖片描述

類圖簡易版

圖片描述

在parchment.ts中對外導出的有四類東西。

  • 節點Blot

    • ParentBlot 【父級節點】能對子節點進行增,刪,改,移動,查
    • ContainerBlot 【容器節點】
    • LeafBlot 【葉節點】
    • EmbedBlot 嵌入式節點 【可格式化的葉節點】
    • ScrollBlot root【文檔的根節點,不可格式化】
    • BlockBlot 塊級 【可格式化的父級節點】
    • InlineBlot 內聯 【可格式化的父級節點】
    • TextBlot 文本【葉節點】
  • 屬性Attributor

    • Attributor 【一種表明格式的方法】
    • ClassAttributor 【使用classname模式來表明格式】
    • StyleAttributor 【使用內聯樣式來表明格式】
    • AttributorStore 【節點的attributes管理器】在BlockBlot InlineBlot中使用到了
  • 註冊中心

    • Registry 【static blots = new WeakMap<Node, Blot>, attributes,classes,tags,types 】
  • 類型常量Scope

    • 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);
});
  1. 依賴於Quill提供的Inline類,構造了BoldBlot, ItalicBlot類,並註冊到Quill, 會註冊出formats/blod, formats/italic(是在Parchment導出的Registry中註冊)
  2. 傳入dom id, 構造出一個Quill實例quill。 這裏會初始化大量屬性,和它內部的模塊,註冊一些事件,#editor-container中會插入些個quill本身的dom結構,prev sibling會插入一個div.ql-toolbar。
<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, 進行對應的格式化)。

quill.format分兩種狀況

A. 選中部份內容後,執行格式化。代碼流程:
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);`
B. 未選中內容,執行格式化。

未選中內容是對光標進行格式化。 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都會進行。

參考資料

https://github.com/quilljs/pa...

https://quilljs.com/guides/cl...

相關文章
相關標籤/搜索