Parchment
Parchment is Quill'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, which mirror a DOM node counterpart. Blots can provide structure, formatting, and/or content. Attributors can also provide lightweight formatting information.
Note: You should never instantiate a Blot yourself with new
. This may prevent necessary lifecycle functionality of a Blot. Use the Registry's create()
method instead.
npm install parchment
See Cloning Medium with Parchment for a guide on how Quill uses Parchment its document model.
Blots
Blots are the basic building blocks of a Parchment document. Several basic implementations such as Block, Inline, and Embed are provided. In general you will want to extend one of these, instead of building from scratch. After implementation, blots need to be registered before usage.
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, which determine if it is inline or block.
class Blot {
static blotName: string;
static className: string;
static tagName: string | string[];
static scope: Scope;
domNode: Node;
prev: Blot | null;
next: Blot | null;
parent: Blot;
static create(value?: any): Node;
constructor(domNode: Node, value?: any);
length(): Number;
deleteAt(index: number, length: number);
formatAt(index: number, length: number, format: string, value: any);
insertAt(index: number, text: string);
insertAt(index: number, embed: string, value: any);
offset(ancestor: Blot = this.parent): number;
optimize(context: { [key: string]: any }): void;
update(mutations: MutationRecord[], context: { [key: string]: any });
static value(domNode): any;
index(node: Node, offset: number): number;
position(index: number, inclusive: boolean): [Node, number];
value(): any;
static allowedChildren: Registry.BlotConstructor[];
static defaultChild: Registry.BlotConstructor;
children: LinkedList<Blot>;
build();
descendant(type: BlotClass, index: number, inclusive): Blot;
descendants(type: BlotClass, index: number, length: number): Blot[];
static formats(domNode: Node);
format(format: name, value: any);
formats(): Object;
}
Example
Implementation for a Blot representing a link, which is a parent, inline scoped, and formattable.
import { InlineBlot, register } from 'parchment';
class LinkBlot extends InlineBlot {
static blotName = 'link';
static tagName = 'A';
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;
}
}
register(LinkBlot);
Quill also provides many great example implementations in its source code.
Block Blot
Basic implementation of a block scoped formattable parent Blot. Formatting a block blot by default will replace the appropriate subsection of the blot.
Inline Blot
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.
Embed Blot
Basic implementation of a non-text leaf blot, that is formattable. Its corresponding DOM node will often be a Void Element, but can be a Normal Element. 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.
Scroll
The root parent blot of a Parchment document. It is not formattable.
Attributors
Attributors are the alternative, more lightweight, way to represent formats. Their DOM counterpart is an Attribute. Like a DOM attribute's relationship to a node, Attributors are meant to belong to Blots. Calling formats()
on an Inline or Block 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).
Attributors have the following interface:
class Attributor {
attrName: string;
keyName: string;
scope: Scope;
whitelist: string[];
constructor(attrName: string, keyName: string, options: Object = {});
add(node: HTMLElement, value: string): boolean;
canAdd(node: HTMLElement, value: string): boolean;
remove(node: HTMLElement);
value(node: HTMLElement);
}
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, Class Attributor or Style Attributor.
The implementation for Attributors is surprisingly simple, and its source code may be another source of understanding.
Attributor
Uses a plain attribute to represent formats.
import { Attributor, register } from 'parchment';
let Width = new Attributor('width', 'width');
register(Width);
let imageNode = document.createElement('img');
Width.add(imageNode, '10px');
console.log(imageNode.outerHTML);
Width.value(imageNode);
Width.remove(imageNode);
console.log(imageNode.outerHTML);
Class Attributor
Uses a class name pattern to represent formats.
import { ClassAttributor, register } from 'parchment';
let Align = new ClassAttributor('align', 'blot-align');
register(Align);
let node = document.createElement('div');
Align.add(node, 'right');
console.log(node.outerHTML);
Style Attributor
Uses inline styles to represent formats.
import { StyleAttributor, register } from 'parchment';
let Align = new StyleAttributor('align', 'text-align', {
whitelist: ['right', 'center', 'justify'],
});
register(Align);
let node = document.createElement('div');
Align.add(node, 'right');
console.log(node.outerHTML);
Registry
All methods are accessible from Parchment ex. Parchment.create('bold')
.
create(domNode: Node, value?: any): Blot;
create(blotName: string, value?: any): Blot;
create(scope: Scope): Blot;
find(domNode: Node, bubble: boolean = false): Blot;
query(tagName: string, scope: Scope = Scope.ANY): BlotClass;
query(blotName: string, scope: Scope = Scope.ANY): BlotClass;
query(domNode: Node, scope: Scope = Scope.ANY): BlotClass;
query(scope: Scope): BlotClass;
query(attributorName: string, scope: Scope = Scope.ANY): Attributor;
register(BlotClass | Attributor);