From 7ea9e53f6971e4925e1cc8ae0939c079978f1bc6 Mon Sep 17 00:00:00 2001 From: Zihua Li Date: Fri, 15 Mar 2024 10:11:15 +0800 Subject: [PATCH] Improve types for Blot --- README.md | 37 ++++++++++++------------- src/blot/abstract/blot.ts | 54 ++++++++++++++++++++++++++++--------- src/blot/abstract/leaf.ts | 18 +++++++++++++ src/blot/abstract/parent.ts | 12 ++++++++- 4 files changed, 88 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 34d1e42..ad6c918 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Parchment is [Quill](https://quilljs.com)'s document model. It is a parallel tre **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. -`npm install --save parchment` +`npm install parchment` See [Cloning Medium with Parchment](https://quilljs.com/guides/cloning-medium-with-parchment/) for a guide on how Quill uses Parchment its document model. @@ -48,14 +48,13 @@ class Blot { // 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; + 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 modifcations of + // 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}); - + update(mutations: MutationRecord[], context: { [key: string]: any }); /** Leaf Blots only **/ @@ -77,11 +76,10 @@ class Blot { // user change detectable by update() value(): any; - /** Parent blots only **/ // Whitelist array of Blots that can be direct children. - static allowedChildren: Blot[]; + static allowedChildren: Registry.BlotConstructor[]; // Default child blot to be inserted if this blot becomes empty. static defaultChild: Registry.BlotConstructor; @@ -92,10 +90,9 @@ class Blot { build(); // Useful search functions for descendant(s), should not modify - descendant(type: BlotClass, index: number, inclusive): Blot + 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 @@ -118,6 +115,9 @@ Implementation for a Blot representing a link, which is a parent, inline scoped, 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); @@ -144,13 +144,11 @@ class LinkBlot extends Parchment.Inline { return formats; } } -LinkBlot.blotName = 'link'; -LinkBlot.tagName = 'A'; Parchment.register(LinkBlot); ``` -Quill also provides many great example implementions in its [source code](https://github.com/quilljs/quill/tree/develop/formats). +Quill also provides many great example implementations in its [source code](https://github.com/quilljs/quill/tree/develop/formats). ### Block Blot @@ -158,7 +156,7 @@ Basic implementation of a block scoped formattable parent Blot. Formatting a blo ### 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. +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 @@ -168,7 +166,6 @@ Basic implementation of a non-text leaf blot, that is formattable. Its correspon 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](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). @@ -207,10 +204,10 @@ Parchment.register(Width); let imageNode = document.createElement('img'); Width.add(imageNode, '10px'); -console.log(imageNode.outerHTML); // Will print -Width.value(imageNode); // Will return 10px +console.log(imageNode.outerHTML); // Will print +Width.value(imageNode); // Will return 10px Width.remove(imageNode); -console.log(imageNode.outerHTML); // Will print +console.log(imageNode.outerHTML); // Will print ``` ### Class Attributor @@ -225,7 +222,7 @@ Parchment.register(Align); let node = document.createElement('div'); Align.add(node, 'right'); -console.log(node.outerHTML); // Will print
+console.log(node.outerHTML); // Will print
``` ### Style Attributor @@ -236,13 +233,13 @@ 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 + 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
+console.log(node.outerHTML); // Will print
``` ## Registry diff --git a/src/blot/abstract/blot.ts b/src/blot/abstract/blot.ts index 6a8b58d..fb10cae 100644 --- a/src/blot/abstract/blot.ts +++ b/src/blot/abstract/blot.ts @@ -4,11 +4,20 @@ import type { RegistryDefinition } from '../../registry.js'; import Scope from '../../scope.js'; export interface BlotConstructor { - blotName: string; - className?: string; - tagName: string | string[]; new (...args: any[]): Blot; + /** + * Creates corresponding DOM node + */ create(value?: any): Node; + + blotName: string; + tagName: string | string[]; + scope: Scope; + className?: string; + + requiredContainer?: BlotConstructor; + allowedChildren?: BlotConstructor[]; + defaultChild?: BlotConstructor; } export interface Blot extends LinkedNode { @@ -18,21 +27,22 @@ export interface Blot extends LinkedNode { next: Blot | null; domNode: Node; - statics: { - allowedChildren?: BlotConstructor[]; - blotName: string; - className?: string; - defaultChild?: BlotConstructor; - requiredContainer?: BlotConstructor; - scope: Scope; - tagName: string | string[]; - }; + statics: BlotConstructor; attach(): void; clone(): Blot; detach(): void; isolate(index: number, length: number): Blot; + + /** + * For leaves, length of blot's value() + * For parents, sum of children's values + */ length(): number; + + /** + * Returns offset between this blot and an ancestor's + */ offset(root?: Blot): number; remove(): void; replaceWith(name: string, value: any): Blot; @@ -44,8 +54,21 @@ export interface Blot extends LinkedNode { deleteAt(index: number, length: number): void; formatAt(index: number, length: number, name: string, value: any): void; insertAt(index: number, value: string, def?: any): void; + + /** + * 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; optimize(mutations: MutationRecord[], 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 }): void; } @@ -76,7 +99,14 @@ export interface Root extends Parent { } export interface Formattable extends Blot { + /** + * Apply format to blot. Should not pass onto child or other blot. + */ format(name: string, value: any): void; + + /** + * Return formats represented by blot, including from Attributors. + */ formats(): { [index: string]: any }; } diff --git a/src/blot/abstract/leaf.ts b/src/blot/abstract/leaf.ts index 7808add..1b6218c 100644 --- a/src/blot/abstract/leaf.ts +++ b/src/blot/abstract/leaf.ts @@ -5,10 +5,19 @@ import ShadowBlot from './shadow.js'; class LeafBlot extends ShadowBlot implements Leaf { public static scope = Scope.INLINE_BLOT; + /** + * 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. + */ public static value(_domNode: Node): any { return true; } + /** + * Given location represented by node and offset from DOM Selection Range, + * return index to that location. + */ public index(node: Node, offset: number): number { if ( this.domNode === node || @@ -20,6 +29,10 @@ class LeafBlot extends ShadowBlot implements Leaf { return -1; } + /** + * Given index to location within blot, return node and offset representing + * that location, consumable by DOM Selection Range + */ public position(index: number, _inclusive?: boolean): [Node, number] { const childNodes: Node[] = Array.from(this.parent.domNode.childNodes); let offset = childNodes.indexOf(this.domNode); @@ -29,6 +42,11 @@ class LeafBlot extends ShadowBlot implements Leaf { return [this.parent.domNode, offset]; } + /** + * Return value represented by this blot + * Should not change without interaction from API or + * user change detectable by update() + */ public value(): any { return { [this.statics.blotName]: this.statics.value(this.domNode) || true, diff --git a/src/blot/abstract/parent.ts b/src/blot/abstract/parent.ts index 207dd26..c5e58a9 100644 --- a/src/blot/abstract/parent.ts +++ b/src/blot/abstract/parent.ts @@ -23,7 +23,14 @@ function makeAttachedBlot(node: Node, scroll: Root): Blot { } class ParentBlot extends ShadowBlot implements Parent { - public static allowedChildren: BlotConstructor[] | null; + /** + * Whitelist array of Blots that can be direct children. + */ + public static allowedChildren?: BlotConstructor[]; + + /** + * Default child blot to be inserted if this blot becomes empty. + */ public static defaultChild?: BlotConstructor; public static uiClass = ''; @@ -59,6 +66,9 @@ class ParentBlot extends ShadowBlot implements Parent { this.domNode.insertBefore(this.uiNode, this.domNode.firstChild); } + /** + * Called during construction, should fill its own children LinkedList. + */ public build(): void { this.children = new LinkedList(); // Need to be reversed for if DOM nodes already in order