3

概念介绍

Parchment是Quill的文档模型。是一个和DOM树对应的平行树结构,给内容编辑器Quill提供有用的功能。

一个Parchment 树是由Blots构成。Blot是一个DOM节点的对应物。Blots可以提供结构,格式化,或内容。Attributor可以提供轻量级的格式化信息。

Parchment tree是DOM tree的对应,二者关系紧密。

官方示例

LinkBlot

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在这两方面都不完美。所以现代的编辑器通过管理自己的文档模型来表示内容。

它的价值?
提供一致的数据和可预测的行为

如何构造出一套文档模型?如何与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。 即建立关系。

源码分析

基础设计

目录结构

- 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...


littlelightss
406 声望14 粉丝

下一篇 »
quill深入浅出

引用和评论

0 条评论