Generalized state model for rich-text editors to interface with browser DOM
Installations
npm install parchment
Releases
Version 3.0.0
Published on 17 Apr 2024
Version 3.0.0-rc.1
Published on 04 Apr 2024
Version 3.0.0-rc.0
Published on 20 Mar 2024
Version 3.0.0-beta.0
Published on 15 Mar 2024
Version 3.0.0-alpha.2
Published on 16 Jan 2024
Version 3.0.0-alpha.0
Published on 16 Jun 2023
Developer
quilljs
Developer Guide
Module System
ESM
Min. Node Version
Typescript Support
Yes
Node Version
20.12.1
NPM Version
10.5.0
Statistics
640 Stars
648 Commits
146 Forks
18 Watching
6 Branches
18 Contributors
Updated on 19 Nov 2024
Bundle Size
20.51 kB
Minified
5.75 kB
Minified + Gzipped
Languages
TypeScript (96.78%)
JavaScript (3.22%)
Total Downloads
Cumulative downloads
Total Downloads
265,342,919
Last day
-0.4%
360,420
Compared to previous day
Last week
3%
1,860,662
Compared to previous week
Last month
11.4%
7,894,389
Compared to previous month
Last year
16.1%
78,187,722
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
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.
1class Blot { 2 static blotName: string; 3 static className: string; 4 static tagName: string | string[]; 5 static scope: Scope; 6 7 domNode: Node; 8 prev: Blot | null; 9 next: Blot | null; 10 parent: Blot; 11 12 // Creates corresponding DOM node 13 static create(value?: any): Node; 14 15 constructor(domNode: Node, value?: any); 16 17 // For leaves, length of blot's value() 18 // For parents, sum of children's values 19 length(): Number; 20 21 // Manipulate at given index and length, if applicable. 22 // Will often pass call onto appropriate child. 23 deleteAt(index: number, length: number); 24 formatAt(index: number, length: number, format: string, value: any); 25 insertAt(index: number, text: string); 26 insertAt(index: number, embed: string, value: any); 27 28 // Returns offset between this blot and an ancestor's 29 offset(ancestor: Blot = this.parent): number; 30 31 // Called after update cycle completes. Cannot change the value or length 32 // of the document, and any DOM operation must reduce complexity of the DOM 33 // tree. A shared context object is passed through all blots. 34 optimize(context: { [key: string]: any }): void; 35 36 // Called when blot changes, with the mutation records of its change. 37 // Internal records of the blot values can be updated, and modifications of 38 // the blot itself is permitted. Can be trigger from user change or API call. 39 // A shared context object is passed through all blots. 40 update(mutations: MutationRecord[], context: { [key: string]: any }); 41 42 /** Leaf Blots only **/ 43 44 // Returns the value represented by domNode if it is this Blot's type 45 // No checking that domNode can represent this Blot type is required so 46 // applications needing it should check externally before calling. 47 static value(domNode): any; 48 49 // Given location represented by node and offset from DOM Selection Range, 50 // return index to that location. 51 index(node: Node, offset: number): number; 52 53 // Given index to location within blot, return node and offset representing 54 // that location, consumable by DOM Selection Range 55 position(index: number, inclusive: boolean): [Node, number]; 56 57 // Return value represented by this blot 58 // Should not change without interaction from API or 59 // user change detectable by update() 60 value(): any; 61 62 /** Parent blots only **/ 63 64 // Whitelist array of Blots that can be direct children. 65 static allowedChildren: Registry.BlotConstructor[]; 66 67 // Default child blot to be inserted if this blot becomes empty. 68 static defaultChild: Registry.BlotConstructor; 69 70 children: LinkedList<Blot>; 71 72 // Called during construction, should fill its own children LinkedList. 73 build(); 74 75 // Useful search functions for descendant(s), should not modify 76 descendant(type: BlotClass, index: number, inclusive): Blot; 77 descendants(type: BlotClass, index: number, length: number): Blot[]; 78 79 /** Formattable blots only **/ 80 81 // Returns format values represented by domNode if it is this Blot's type 82 // No checking that domNode is this Blot's type is required. 83 static formats(domNode: Node); 84 85 // Apply format to blot. Should not pass onto child or other blot. 86 format(format: name, value: any); 87 88 // Return formats represented by blot, including from Attributors. 89 formats(): Object; 90}
Example
Implementation for a Blot representing a link, which is a parent, inline scoped, and formattable.
1import { InlineBlot, register } from 'parchment'; 2 3class LinkBlot extends InlineBlot { 4 static blotName = 'link'; 5 static tagName = 'A'; 6 7 static create(url) { 8 let node = super.create(); 9 node.setAttribute('href', url); 10 node.setAttribute('target', '_blank'); 11 node.setAttribute('title', node.textContent); 12 return node; 13 } 14 15 static formats(domNode) { 16 return domNode.getAttribute('href') || true; 17 } 18 19 format(name, value) { 20 if (name === 'link' && value) { 21 this.domNode.setAttribute('href', value); 22 } else { 23 super.format(name, value); 24 } 25 } 26 27 formats() { 28 let formats = super.formats(); 29 formats['link'] = LinkBlot.formats(this.domNode); 30 return formats; 31 } 32} 33 34register(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:
1class Attributor { 2 attrName: string; 3 keyName: string; 4 scope: Scope; 5 whitelist: string[]; 6 7 constructor(attrName: string, keyName: string, options: Object = {}); 8 add(node: HTMLElement, value: string): boolean; 9 canAdd(node: HTMLElement, value: string): boolean; 10 remove(node: HTMLElement); 11 value(node: HTMLElement); 12}
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.
1import { Attributor, register } from 'parchment'; 2 3let Width = new Attributor('width', 'width'); 4register(Width); 5 6let imageNode = document.createElement('img'); 7 8Width.add(imageNode, '10px'); 9console.log(imageNode.outerHTML); // Will print <img width="10px"> 10Width.value(imageNode); // Will return 10px 11Width.remove(imageNode); 12console.log(imageNode.outerHTML); // Will print <img>
Class Attributor
Uses a class name pattern to represent formats.
1import { ClassAttributor, register } from 'parchment'; 2 3let Align = new ClassAttributor('align', 'blot-align'); 4register(Align); 5 6let node = document.createElement('div'); 7Align.add(node, 'right'); 8console.log(node.outerHTML); // Will print <div class="blot-align-right"></div>
Style Attributor
Uses inline styles to represent formats.
1import { StyleAttributor, register } from 'parchment'; 2 3let Align = new StyleAttributor('align', 'text-align', { 4 whitelist: ['right', 'center', 'justify'], // Having no value implies left align 5}); 6register(Align); 7 8let node = document.createElement('div'); 9Align.add(node, 'right'); 10console.log(node.outerHTML); // Will print <div style="text-align: right;"></div>
Registry
All methods are accessible from Parchment ex. Parchment.create('bold')
.
1// Creates a blot given a name or DOM node. 2// When given just a scope, creates blot the same name as scope 3create(domNode: Node, value?: any): Blot; 4create(blotName: string, value?: any): Blot; 5create(scope: Scope): Blot; 6 7// Given DOM node, find corresponding Blot. 8// Bubbling is useful when searching for a Embed Blot with its corresponding 9// DOM node's descendant nodes. 10find(domNode: Node, bubble: boolean = false): Blot; 11 12// Search for a Blot or Attributor 13// When given just a scope, finds blot with same name as scope 14query(tagName: string, scope: Scope = Scope.ANY): BlotClass; 15query(blotName: string, scope: Scope = Scope.ANY): BlotClass; 16query(domNode: Node, scope: Scope = Scope.ANY): BlotClass; 17query(scope: Scope): BlotClass; 18query(attributorName: string, scope: Scope = Scope.ANY): Attributor; 19 20// Register Blot class definition or Attributor instance 21register(BlotClass | Attributor);
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: BSD 3-Clause "New" or "Revised" License: LICENSE:0
Reason
7 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm
- Warn: Project is vulnerable to: GHSA-8jhw-289h-jh2g
- Warn: Project is vulnerable to: GHSA-64vr-g452-qvp3
- Warn: Project is vulnerable to: GHSA-9cwx-2883-4wfx
Reason
dependency not pinned by hash detected -- score normalized to 2
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/_test.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/slab/parchment/_test.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/_test.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/slab/parchment/_test.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/slab/parchment/release.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/slab/parchment/release.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/slab/parchment/release.yml/main?enable=pin
- Info: 0 out of 5 GitHub-owned GitHubAction dependencies pinned
- Info: 2 out of 2 npmCommand dependencies pinned
Reason
Found 1/26 approved changesets -- score normalized to 0
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/_test.yml:1
- Warn: no topLevel permission defined: .github/workflows/main.yml:1
- Warn: no topLevel permission defined: .github/workflows/release.yml:1
- Info: no jobLevel write permissions found
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
branch protection not enabled on development/release branches
Details
- Warn: branch protection not enabled for branch 'main'
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 13 are checked with a SAST tool
Score
2.9
/10
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn MoreOther packages similar to parchment
@xeger/quill-deltacvt
Converts Quill Delta to HTML (or other formats) without depending on quill, parchment or quill-delta.
@types/quill
TypeScript definitions for quill
parchment-composition
A document model for rich text editors
panda-parchment
JavaScript helpers for use in functional programming.