Parchment

Parchment是Quill的文档模型。它是一个并行的树结构,并且提供对内容的编辑(如Quill)的功能。一个Parchment树是由Blots组成的,它反映了一个DOM对应的节点。Blots能够提供结构、格式和内容或者只有内容。Attributors能够提供轻量级的格式化信息。

注意:你不应该使用new来实例化一个Blot。这个方法可能阻止Blot的必要生命周期。使用注册create()方法代替。

  1. npm install --save parchment

可以查看Cloning Medium with Parchment来了解Quill是如何使用Parchment的文档模型的。

Blots

Blots是Parchment文档的基本组成部分。提供了几个基本的实现,如:BlockInlineEmbed。一般来说,你会想扩展其中的一个,而不是从头开始构建。实现之后,需要在使用之前进行注册

一个最基本的Blots必须使用一个静态的blotName来命名,并且有一个与之相关联的tagName或者className。如果一个Blot是通过标签和类定义的,类是第一优先级,标签被用作备用。Blots还必须有一个范围,来确定他是内联(inline)还是分块(block)。

  1. class Blot {
  2. static blotName: string;
  3. static className: string;
  4. static tagName: string;
  5. static scope: Scope;
  6. domNode: Node;
  7. prev: Blot;
  8. next: Blot;
  9. parent: Blot;
  10. // 创建相应的节点
  11. static create(value?: any): Node;
  12. constructor(domNode: Node, value?: any);
  13. // 对于子集,是Blot的长度
  14. // 对于父节点,是所有子节点的总和
  15. length(): Number;
  16. // 如果适用,按照给定的指数和长度进行操作
  17. // 经常会把响应转移到合适的子节点上
  18. deleteAt(index: number, length: number);
  19. formatAt(index: number, length: number, format: string, value: any);
  20. insertAt(index: number, text: string);
  21. insertAt(index: number, embed: string, value: any);
  22. // 返回当前Blot与父节点之间的偏移量
  23. offset(ancestor: Blot = this.parent): number;
  24. // 更新的生命周期之后调用。
  25. // 不能修改文档的值和长度,并且任何DOM操作都必须降低DOM树的复杂性。
  26. // 共享上下文对象被传递给所有的Blots。
  27. optimize(context: {[key: string]: any}): void;
  28. // 当Blot改变是调用,伴随着改变记录。
  29. // blot的内部值能够被更新,并且允许修改Blot本身。
  30. // 可以通过用户行为或者API调用触发。
  31. // 共享上下文对象被传递给所有的Blots。
  32. update(mutations: MutationRecord[], context: {[key: string]: any});
  33. /** 仅对于作为子节点 **/
  34. // 如果是Blot的类型,返回由domNode表示的值。
  35. // 本身没有对domNode的类型进行校验,需要应用程序在调用之前进行外部校验。
  36. static value(domNode): any;
  37. // 给定一个node和一个在DOM选择范围内的偏移量,返回一个该位置的索引。
  38. index(node: Node, offset: number): number;
  39. // 给定一个Blot的位置信息坐标,返回当前节点在DOM可选范围的偏移量
  40. position(index: number, inclusive: boolean): [Node, number];
  41. // 返回当前Blot代表的值
  42. // 除了来自于API或者通过update检测的用户改变,不应该被改变。
  43. value(): any;
  44. /** 仅对于作为父节点 **/
  45. // Blots的白名单上数组,可以是直接的子节点
  46. static allowedChildren: Blot[];
  47. // 默认节点,当节点为空时会被插入
  48. static defaultChild: string;
  49. children: LinkedList<Blot>;
  50. // 在构造时调用,应该填写子节点的LinkedList
  51. build();
  52. // 有用的后代搜索功能,不应该被修改
  53. descendant(type: BlotClass, index: number, inclusive): Blot
  54. descendents(type: BlotClass, index: number, length: number): Blot[];
  55. /** 仅对于作为格式表 **/
  56. // 如果是Blot的类型,返回domNode的格式化后的值
  57. // 不需要检查domNode是否为Blot的类型
  58. static formats(domNode: Node);
  59. // Blot应用格式。不应该传递个子节点或者其他Blot
  60. format(format: name, value: any);
  61. // 返回格式代表的Blot,包括来自于Attributors的。
  62. formats(): Object;
  63. }

Example

表示链接的Blot实现,该链接是父级,内联范围和格式表。

  1. import Parchment from 'parchment';
  2. class LinkBlot extends Parchment.Inline {
  3. static create(url) {
  4. let node = super.create();
  5. node.setAttribute('href', url);
  6. node.setAttribute('target', '_blank');
  7. node.setAttribute('title', node.textContent);
  8. return node;
  9. }
  10. static formats(domNode) {
  11. return domNode.getAttribute('href') || true;
  12. }
  13. format(name, value) {
  14. if (name === 'link' && value) {
  15. this.domNode.setAttribute('href', value);
  16. } else {
  17. super.format(name, value);
  18. }
  19. }
  20. formats() {
  21. let formats = super.formats();
  22. formats['link'] = LinkBlot.formats(this.domNode);
  23. return formats;
  24. }
  25. }
  26. LinkBlot.blotName = 'link';
  27. LinkBlot.tagName = 'A';
  28. Parchment.register(LinkBlot);

Quill再其源码中提供了很多实现的示例。

Block Blot

块类型格式化Blot的基本实现。默认格式的块级Blot会替代Blot的适当部分。

Inline Blot

行级格式化Blot的基本实现。默认格式的行级Blot或者用一个Blot包裹自己,或者将它传递给合适的子节点。

Embed Blot

非文本节点的基本实现,可以被格式化。其对应的额DOM节点通常是一个Void元素,也可以是一个正常元素。在这些情况下,Parchment将不会操作或者感知到元素的子元素,正确的执行Blot的index()position()方法对于正确的光标显示/选区是很重要的。

Scroll

Parchment文档的根节点。不能够被格式化。

Attributors

Attributors是一种轻量级的格式话方式。它们的DOM对应的是属性(Attribute)。像DOM属性和节点的关系一样,Attributors也属于Blots。调用Inline或者Block Blot的formats()方法将会返回相应的DOM节点的格式(如果有的话)以及DOM节点属性表示的格式(如果有的话)。

Attributors 有以下的以下接口:

  1. class Attributor {
  2. attrName: string;
  3. keyName: string;
  4. scope: Scope;
  5. whitelist: string[];
  6. constructor(attrName: string, keyName: string, options: Object = {});
  7. add(node: HTMLElement, value: string): boolean;
  8. canAdd(node: HTMLElement, value: string): boolean;
  9. remove(node: HTMLElement);
  10. value(node: HTMLElement);
  11. }

注意:自定义的属性是实例,而不是类似于Blots的类定义。类似于Blots,你可能希望使用现有的Attributors实现,而不是从头开始创建,比如基础的AttritorClass Attributor或者Style Attributor

Attributors的实现非常简单,并且它的源码可能是另一个库的资源。

Attributor

使用一个普通属性来表示格式。

  1. import Parchment from 'parchment';
  2. let Width = new Parchment.Attributor.Attribute('width', 'width');
  3. Parchment.register(Width);
  4. let imageNode = document.createElement('img');
  5. Width.add(imageNode, '10px');
  6. console.log(imageNode.outerHTML); // Will print <img width="10px">
  7. Width.value(imageNode); // Will return 10px
  8. Width.remove(imageNode);
  9. console.log(imageNode.outerHTML); // Will print <img>

Class Attributor

使用类名模式来表示格式。

  1. import Parchment from 'parchment';
  2. let Align = new Parchment.Attributor.Class('align', 'blot-align');
  3. Parchment.register(Align);
  4. let node = document.createElement('div');
  5. Align.add(node, 'right');
  6. console.log(node.outerHTML); // Will print <div class="blot-align-right"></div>

Style Attributor

使用行样式来表示格式。

  1. import Parchment from 'parchment';
  2. let Align = new Parchment.Attributor.Style('align', 'text-align', {
  3. whitelist: ['right', 'center', 'justify'] // Having no value implies left align
  4. });
  5. Parchment.register(Align);
  6. let node = document.createElement('div');
  7. Align.add(node, 'right');
  8. console.log(node.outerHTML); // Will print <div style="text-align: right;"></div>

Registry

除了Parchment.create('bold')的所有方法都可以从Parchment中获得。

  1. // 通过名称或者DOM节点创建一个Blot
  2. // 当只给定一个范围,创建一个范围相同名称的Blot
  3. create(domNode: Node, value?: any): Blot;
  4. create(blotName: string, value?: any): Blot;
  5. create(scope: Scope): Blot;
  6. // 通过一个DOM节点找到相应的Blot
  7. // 当搜索一个嵌入式的Blot节点时,冒泡算法是很有用的。
  8. // DOM几点的后代节点。
  9. find(domNode: Node, bubble: boolean = false): Blot;
  10. // 搜索Blot或者Attributor
  11. // 当给定一个范围是,根据相应范围查找,返回相应的Blot
  12. query(tagName: string, scope: Scope = Scope.ANY): BlotClass;
  13. query(blotName: string, scope: Scope = Scope.ANY): BlotClass;
  14. query(domNode: Node, scope: Scope = Scope.ANY): BlotClass;
  15. query(scope: Scope): BlotClass;
  16. query(attributorName: string, scope: Scope = Scope.ANY): Attributor;
  17. // 注册Blot类定义或Attributor实例
  18. register(BlotClass | Attributor);