b5d58fe037f51c5e8e8f3469c4534540e7693e9c54827bdd8f57f1cea32f9860feaab04c1fb46f757c2314b4c849ea811298c663605aa66c4ac16a6e0142d3 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. # Parchment [![Build Status](https://github.com/quilljs/parchment/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/quilljs/parchment/actions?query=branch%3Amain)
  2. Parchment is [Quill](https://quilljs.com)'s document model. It is a parallel tree structure to the DOM tree, and provides functionality useful for content editors, like Quill. A Parchment tree is made up of [Blots](#blots), which mirror a DOM node counterpart. Blots can provide structure, formatting, and/or content. [Attributors](#attributors) can also provide lightweight formatting information.
  3. **Note:** You should never instantiate a Blot yourself with `new`. This may prevent necessary lifecycle functionality of a Blot. Use the [Registry](#registry)'s `create()` method instead.
  4. `npm install parchment`
  5. See [Cloning Medium with Parchment](https://quilljs.com/guides/cloning-medium-with-parchment/) for a guide on how Quill uses Parchment its document model.
  6. ## Blots
  7. Blots are the basic building blocks of a Parchment document. Several basic implementations such as [Block](#block-blot), [Inline](#inline-blot), and [Embed](#embed-blot) are provided. In general you will want to extend one of these, instead of building from scratch. After implementation, blots need to be [registered](#registry) before usage.
  8. At the very minimum a Blot must be named with a static `blotName` and associated with either a `tagName` or `className`. If a Blot is defined with both a tag and class, the class takes precedence, but the tag may be used as a fallback. Blots must also have a [scope](#registry), which determine if it is inline or block.
  9. ```typescript
  10. class Blot {
  11. static blotName: string;
  12. static className: string;
  13. static tagName: string | string[];
  14. static scope: Scope;
  15. domNode: Node;
  16. prev: Blot | null;
  17. next: Blot | null;
  18. parent: Blot;
  19. // Creates corresponding DOM node
  20. static create(value?: any): Node;
  21. constructor(domNode: Node, value?: any);
  22. // For leaves, length of blot's value()
  23. // For parents, sum of children's values
  24. length(): Number;
  25. // Manipulate at given index and length, if applicable.
  26. // Will often pass call onto appropriate child.
  27. deleteAt(index: number, length: number);
  28. formatAt(index: number, length: number, format: string, value: any);
  29. insertAt(index: number, text: string);
  30. insertAt(index: number, embed: string, value: any);
  31. // Returns offset between this blot and an ancestor's
  32. offset(ancestor: Blot = this.parent): number;
  33. // Called after update cycle completes. Cannot change the value or length
  34. // of the document, and any DOM operation must reduce complexity of the DOM
  35. // tree. A shared context object is passed through all blots.
  36. optimize(context: { [key: string]: any }): void;
  37. // Called when blot changes, with the mutation records of its change.
  38. // Internal records of the blot values can be updated, and modifications of
  39. // the blot itself is permitted. Can be trigger from user change or API call.
  40. // A shared context object is passed through all blots.
  41. update(mutations: MutationRecord[], context: { [key: string]: any });
  42. /** Leaf Blots only **/
  43. // Returns the value represented by domNode if it is this Blot's type
  44. // No checking that domNode can represent this Blot type is required so
  45. // applications needing it should check externally before calling.
  46. static value(domNode): any;
  47. // Given location represented by node and offset from DOM Selection Range,
  48. // return index to that location.
  49. index(node: Node, offset: number): number;
  50. // Given index to location within blot, return node and offset representing
  51. // that location, consumable by DOM Selection Range
  52. position(index: number, inclusive: boolean): [Node, number];
  53. // Return value represented by this blot
  54. // Should not change without interaction from API or
  55. // user change detectable by update()
  56. value(): any;
  57. /** Parent blots only **/
  58. // Whitelist array of Blots that can be direct children.
  59. static allowedChildren: Registry.BlotConstructor[];
  60. // Default child blot to be inserted if this blot becomes empty.
  61. static defaultChild: Registry.BlotConstructor;
  62. children: LinkedList<Blot>;
  63. // Called during construction, should fill its own children LinkedList.
  64. build();
  65. // Useful search functions for descendant(s), should not modify
  66. descendant(type: BlotClass, index: number, inclusive): Blot;
  67. descendants(type: BlotClass, index: number, length: number): Blot[];
  68. /** Formattable blots only **/
  69. // Returns format values represented by domNode if it is this Blot's type
  70. // No checking that domNode is this Blot's type is required.
  71. static formats(domNode: Node);
  72. // Apply format to blot. Should not pass onto child or other blot.
  73. format(format: name, value: any);
  74. // Return formats represented by blot, including from Attributors.
  75. formats(): Object;
  76. }
  77. ```
  78. ### Example
  79. Implementation for a Blot representing a link, which is a parent, inline scoped, and formattable.
  80. ```typescript
  81. import { InlineBlot, register } from 'parchment';
  82. class LinkBlot extends InlineBlot {
  83. static blotName = 'link';
  84. static tagName = 'A';
  85. static create(url) {
  86. let node = super.create();
  87. node.setAttribute('href', url);
  88. node.setAttribute('target', '_blank');
  89. node.setAttribute('title', node.textContent);
  90. return node;
  91. }
  92. static formats(domNode) {
  93. return domNode.getAttribute('href') || true;
  94. }
  95. format(name, value) {
  96. if (name === 'link' && value) {
  97. this.domNode.setAttribute('href', value);
  98. } else {
  99. super.format(name, value);
  100. }
  101. }
  102. formats() {
  103. let formats = super.formats();
  104. formats['link'] = LinkBlot.formats(this.domNode);
  105. return formats;
  106. }
  107. }
  108. register(LinkBlot);
  109. ```
  110. Quill also provides many great example implementations in its [source code](https://github.com/quilljs/quill/tree/develop/packages/quill/src/formats).
  111. ### Block Blot
  112. Basic implementation of a block scoped formattable parent Blot. Formatting a block blot by default will replace the appropriate subsection of the blot.
  113. ### Inline Blot
  114. Basic implementation of an inline scoped formattable parent Blot. Formatting an inline blot by default either wraps itself with another blot or passes the call to the appropriate child.
  115. ### Embed Blot
  116. Basic implementation of a non-text leaf blot, that is formattable. Its corresponding DOM node will often be a [Void Element](https://www.w3.org/TR/html5/syntax.html#void-elements), but can be a [Normal Element](https://www.w3.org/TR/html5/syntax.html#normal-elements). In these cases Parchment will not manipulate or generally be aware of the element's children, and it will be important to correctly implement the blot's `index()` and `position()` functions to correctly work with cursors/selections.
  117. ### Scroll
  118. The root parent blot of a Parchment document. It is not formattable.
  119. ## Attributors
  120. Attributors are the alternative, more lightweight, way to represent formats. Their DOM counterpart is an [Attribute](https://www.w3.org/TR/html5/syntax.html#attributes-0). Like a DOM attribute's relationship to a node, Attributors are meant to belong to Blots. Calling `formats()` on an [Inline](#inline-blot) or [Block](#block-blot) blot will return both the format of the corresponding DOM node represents (if any) and the formats the DOM node's attributes represent (if any).
  121. Attributors have the following interface:
  122. ```typescript
  123. class Attributor {
  124. attrName: string;
  125. keyName: string;
  126. scope: Scope;
  127. whitelist: string[];
  128. constructor(attrName: string, keyName: string, options: Object = {});
  129. add(node: HTMLElement, value: string): boolean;
  130. canAdd(node: HTMLElement, value: string): boolean;
  131. remove(node: HTMLElement);
  132. value(node: HTMLElement);
  133. }
  134. ```
  135. Note custom attributors are instances, rather than class definitions like Blots. Similar to Blots, instead of creating from scratch, you will probably want to use existing Attributor implementations, such as the base [Attributor](#attributor), [Class Attributor](#class-attributor) or [Style Attributor](#style-attributor).
  136. The implementation for Attributors is surprisingly simple, and its [source code](https://github.com/quilljs/parchment/tree/main/src/attributor) may be another source of understanding.
  137. ### Attributor
  138. Uses a plain attribute to represent formats.
  139. ```js
  140. import { Attributor, register } from 'parchment';
  141. let Width = new Attributor('width', 'width');
  142. register(Width);
  143. let imageNode = document.createElement('img');
  144. Width.add(imageNode, '10px');
  145. console.log(imageNode.outerHTML); // Will print <img width="10px">
  146. Width.value(imageNode); // Will return 10px
  147. Width.remove(imageNode);
  148. console.log(imageNode.outerHTML); // Will print <img>
  149. ```
  150. ### Class Attributor
  151. Uses a class name pattern to represent formats.
  152. ```js
  153. import { ClassAttributor, register } from 'parchment';
  154. let Align = new ClassAttributor('align', 'blot-align');
  155. register(Align);
  156. let node = document.createElement('div');
  157. Align.add(node, 'right');
  158. console.log(node.outerHTML); // Will print <div class="blot-align-right"></div>
  159. ```
  160. ### Style Attributor
  161. Uses inline styles to represent formats.
  162. ```js
  163. import { StyleAttributor, register } from 'parchment';
  164. let Align = new StyleAttributor('align', 'text-align', {
  165. whitelist: ['right', 'center', 'justify'], // Having no value implies left align
  166. });
  167. register(Align);
  168. let node = document.createElement('div');
  169. Align.add(node, 'right');
  170. console.log(node.outerHTML); // Will print <div style="text-align: right;"></div>
  171. ```
  172. ## Registry
  173. All methods are accessible from Parchment ex. `Parchment.create('bold')`.
  174. ```typescript
  175. // Creates a blot given a name or DOM node.
  176. // When given just a scope, creates blot the same name as scope
  177. create(domNode: Node, value?: any): Blot;
  178. create(blotName: string, value?: any): Blot;
  179. create(scope: Scope): Blot;
  180. // Given DOM node, find corresponding Blot.
  181. // Bubbling is useful when searching for a Embed Blot with its corresponding
  182. // DOM node's descendant nodes.
  183. find(domNode: Node, bubble: boolean = false): Blot;
  184. // Search for a Blot or Attributor
  185. // When given just a scope, finds blot with same name as scope
  186. query(tagName: string, scope: Scope = Scope.ANY): BlotClass;
  187. query(blotName: string, scope: Scope = Scope.ANY): BlotClass;
  188. query(domNode: Node, scope: Scope = Scope.ANY): BlotClass;
  189. query(scope: Scope): BlotClass;
  190. query(attributorName: string, scope: Scope = Scope.ANY): Attributor;
  191. // Register Blot class definition or Attributor instance
  192. register(BlotClass | Attributor);
  193. ```