voxmedia/parchment

Name: parchment

Owner: Vox Media

Description: null

Created: 2018-03-29 17:09:38.0

Updated: 2018-04-20 20:34:55.0

Pushed: 2018-04-27 00:07:41.0

Homepage: null

Size: 643

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Parchment Build Status Coverage Status

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

s Blot {
atic blotName: string;
atic className: string;
atic tagName: string;
atic scope: Scope;

mNode: Node;
ev: Blot;
xt: Blot;
rent: Blot;

 Creates corresponding DOM node
atic create(value?: any): Node;

nstructor(domNode: Node, value?: any);

 For leaves, length of blot's value()
 For parents, sum of children's values
ngth(): Number;

 Manipulate at given index and length, if applicable.
 Will often pass call onto appropriate child.
leteAt(index: number, length: number);
rmatAt(index: number, length: number, format: string, value: any);
sertAt(index: number, text: string);
sertAt(index: number, embed: string, value: any);

 Returns offset between this blot and an ancestor's
fset(ancestor: Blot = this.parent): number;

 Called after update cycle completes. Cannot change the value or length
 of the document, and any DOM operation must reduce complexity of the DOM
 tree. A shared context object is passed through all blots.
timize(context: {[key: string]: any}): void;

 Called when blot changes, with the mutation records of its change.
 Internal records of the blot values can be updated, and modifcations of
 the blot itself is permitted. Can be trigger from user change or API call.
 A shared context object is passed through all blots.
date(mutations: MutationRecord[], context: {[key: string]: any});


* Leaf Blots only **/

 Returns the value represented by domNode if it is this Blot's type
 No checking that domNode can represent this Blot type is required so
 applications needing it should check externally before calling.
atic value(domNode): any;

 Given location represented by node and offset from DOM Selection Range,
 return index to that location.
dex(node: Node, offset: number): number;

 Given index to location within blot, return node and offset representing
 that location, consumable by DOM Selection Range
sition(index: number, inclusive: boolean): [Node, number];

 Return value represented by this blot
 Should not change without interaction from API or
 user change detectable by update()
lue(): any;


* Parent blots only **/

 Whitelist array of Blots that can be direct children.
atic allowedChildren: Blot[];

 Default child blot to be inserted if this blot becomes empty.
atic defaultChild: string;

ildren: LinkedList<Blot>;

 Called during construction, should fill its own children LinkedList.
ild();

 Useful search functions for descendant(s), should not modify
scendant(type: BlotClass, index: number, inclusive): Blot
scendents(type: BlotClass, index: number, length: number): Blot[];


* Formattable blots only **/

 Returns format values represented by domNode if it is this Blot's type
 No checking that domNode is this Blot's type is required.
atic formats(domNode: Node);

 Apply format to blot. Should not pass onto child or other blot.
rmat(format: name, value: any);

 Return formats represented by blot, including from Attributors.
rmats(): Object;

Example

Implementation for a Blot representing a link, which is a parent, inline scoped, and formattable.

rt Parchment from 'parchment';

s LinkBlot extends Parchment.Inline {
atic create(url) {
let node = super.create();
node.setAttribute('href', url);
node.setAttribute('target', '_blank');
node.setAttribute('title', node.textContent);
return node;


atic formats(domNode) {
return domNode.getAttribute('href') || true;


rmat(name, value) {
if (name === 'link' && value) {
  this.domNode.setAttribute('href', value);
} else {
  super.format(name, value);
}


rmats() {
let formats = super.formats();
formats['link'] = LinkBlot.formats(this.domNode);
return formats;


Blot.blotName = 'link';
Blot.tagName = 'A';

hment.register(LinkBlot);

Quill also provides many great example implementions 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 approprate 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:

s Attributor {
trName: string;
yName: string;
ope: Scope;
itelist: string[];

nstructor(attrName: string, keyName: string, options: Object = {});
d(node: HTMLElement, value: string): boolean;
nAdd(node: HTMLElement, value: string): boolean;
move(node: HTMLElement);
lue(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.

rt Parchment from 'parchment';

Width = new Parchment.Attributor.Attribute('width', 'width');
hment.register(Width);

imageNode = document.createElement('img');

h.add(imageNode, '10px');
ole.log(imageNode.outerHTML);   // Will print <img width="10px">
h.value(imageNode);                 // Will return 10px
h.remove(imageNode);
ole.log(imageNode.outerHTML);   // Will print <img>
Class Attributor

Uses a classname pattern to represent formats.

rt Parchment from 'parchment';

Align = new Parchment.Attributor.Class('align', 'blot-align');
hment.register(Align);

node = document.createElement('div');
n.add(node, 'right');
ole.log(node.outerHTML);  // Will print <div class="blot-align-right"></div>
Style Attributor

Uses inline styles to represent formats.

rt Parchment from 'parchment';

Align = new Parchment.Attributor.Style('align', 'text-align', {
itelist: ['right', 'center', 'justify']   // Having no value implies left align

hment.register(Align);

node = document.createElement('div');
n.add(node, 'right');
ole.log(node.outerHTML);  // Will print <div style="text-align: right;"></div>
Registry

All methods are accessible from Parchment ex. Parchment.create('bold').

reates a blot given a name or DOM node.
hen given just a scope, creates blot the same name as scope
te(domNode: Node, value?: any): Blot;
te(blotName: string, value?: any): Blot;
te(scope: Scope): Blot;

iven DOM node, find corresponding Blot.
ubbling is useful when searching for a Embed Blot with its corresponding
OM node's descendant nodes.
(domNode: Node, bubble: boolean = false): Blot;

earch for a Blot or Attributor
hen given just a scope, finds blot with same name as scope
y(tagName: string, scope: Scope = Scope.ANY): BlotClass;
y(blotName: string, scope: Scope = Scope.ANY): BlotClass;
y(domNode: Node, scope: Scope = Scope.ANY): BlotClass;
y(scope: Scope): BlotClass;
y(attributorName: string, scope: Scope = Scope.ANY): Attributor;

egister Blot class definition or Attributor instance
ster(BlotClass | Attributor);

This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.