Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
A document model for rich text editors
Parchment is a modular, extensible library for building rich text editors. It is designed to provide a clean, semantic document model for text and all sorts of embedded content, along with a powerful API for manipulating that content. Parchment is used as the foundation for the Quill editor but can be utilized independently for custom editor projects.
Creating and manipulating text
This code demonstrates how to create a new block element and append a text node to it using Parchment. It showcases the basic structure of creating and manipulating text within the document model.
var Parchment = require('parchment');
var Block = Parchment.create('block');
var blockNode = Parchment.create(Block);
blockNode.appendChild(Parchment.create('text', 'Hello, world!'));
Defining custom formats
This example shows how to define a custom format (in this case, a bold text format) by extending an existing blot (Inline) and registering it with Parchment. This allows for the creation of rich text formatting options.
var Inline = Parchment.query('inline');
class Bold extends Inline {}
Bold.blotName = 'bold';
Bold.tagName = 'strong';
Parchment.register(Bold);
Embedding external content
This code snippet illustrates how to embed external content, such as images, by creating a custom blot that extends Parchment's Embed class. It demonstrates setting attributes (like 'src' for an image URL) on the embedded content.
class ImageBlot extends Parchment.Embed {
static create(value) {
let node = super.create();
node.setAttribute('src', value.url);
return node;
}
}
ImageBlot.blotName = 'image';
ImageBlot.tagName = 'img';
Parchment.register(ImageBlot);
Slate is a completely customizable framework for building rich text editors. Unlike Parchment, which focuses on a modular document model, Slate provides a more comprehensive editing framework, including a React-based rendering layer and a sophisticated plugin system.
Draft.js is a rich text editor framework for React, backed by an immutable model and offering a high level of customization. It differs from Parchment by being tightly integrated with React and focusing on content editing within web applications rather than the modular document model.
ProseMirror is a toolkit for building rich text editors that are web-based and collaborative. It offers a schema-based approach to defining the structure of documents, which is more flexible than Parchment's blot-based model. ProseMirror also focuses heavily on collaborative editing features.
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 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;
// Creates corresponding DOM node
static create(value?: any): Node;
constructor(domNode: Node, value?: any);
// For leaves, length of blot's value()
// For parents, sum of children's values
length(): Number;
// Manipulate at given index and length, if applicable.
// Will often pass call onto appropriate child.
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);
// Returns offset between this blot and an ancestor's
offset(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.
optimize(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 modifications of
// the blot itself is permitted. Can be trigger from user change or API call.
// A shared context object is passed through all blots.
update(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.
static value(domNode): any;
// Given location represented by node and offset from DOM Selection Range,
// return index to that location.
index(node: Node, offset: number): number;
// Given index to location within blot, return node and offset representing
// that location, consumable by DOM Selection Range
position(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()
value(): any;
/** Parent blots only **/
// Whitelist array of Blots that can be direct children.
static allowedChildren: Registry.BlotConstructor[];
// Default child blot to be inserted if this blot becomes empty.
static defaultChild: Registry.BlotConstructor;
children: LinkedList<Blot>;
// Called during construction, should fill its own children LinkedList.
build();
// Useful search functions for descendant(s), should not modify
descendant(type: BlotClass, index: number, inclusive): Blot;
descendants(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.
static formats(domNode: Node);
// Apply format to blot. Should not pass onto child or other blot.
format(format: name, value: any);
// Return formats represented by blot, including from Attributors.
formats(): Object;
}
Implementation for a Blot representing a link, which is a parent, inline scoped, and formattable.
import Parchment from 'parchment';
class LinkBlot extends Parchment.Inline {
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;
}
}
Parchment.register(LinkBlot);
Quill also provides many great example implementations in its source code.
Basic implementation of a block scoped formattable parent Blot. Formatting a block blot by default will replace the appropriate subsection of the 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.
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.
The root parent blot of a Parchment document. It is not formattable.
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.
Uses a plain attribute to represent formats.
import Parchment from 'parchment';
let Width = new Parchment.Attributor.Attribute('width', 'width');
Parchment.register(Width);
let imageNode = document.createElement('img');
Width.add(imageNode, '10px');
console.log(imageNode.outerHTML); // Will print <img width="10px">
Width.value(imageNode); // Will return 10px
Width.remove(imageNode);
console.log(imageNode.outerHTML); // Will print <img>
Uses a classname pattern to represent formats.
import Parchment from 'parchment';
let Align = new Parchment.Attributor.Class('align', 'blot-align');
Parchment.register(Align);
let node = document.createElement('div');
Align.add(node, 'right');
console.log(node.outerHTML); // Will print <div class="blot-align-right"></div>
Uses inline styles to represent formats.
import Parchment from 'parchment';
let Align = new Parchment.Attributor.Style('align', 'text-align', {
whitelist: ['right', 'center', 'justify'], // Having no value implies left align
});
Parchment.register(Align);
let node = document.createElement('div');
Align.add(node, 'right');
console.log(node.outerHTML); // Will print <div style="text-align: right;"></div>
All methods are accessible from Parchment ex. Parchment.create('bold')
.
// Creates a blot given a name or DOM node.
// When given just a scope, creates blot the same name as scope
create(domNode: Node, value?: any): Blot;
create(blotName: string, value?: any): Blot;
create(scope: Scope): Blot;
// Given DOM node, find corresponding Blot.
// Bubbling is useful when searching for a Embed Blot with its corresponding
// DOM node's descendant nodes.
find(domNode: Node, bubble: boolean = false): Blot;
// Search for a Blot or Attributor
// When given just a scope, finds blot with same name as scope
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 Blot class definition or Attributor instance
register(BlotClass | Attributor);
FAQs
A document model for rich text editors
The npm package parchment receives a total of 1,535,806 weekly downloads. As such, parchment popularity was classified as popular.
We found that parchment demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.