Skip to content

Commit

Permalink
Improve types for Blot
Browse files Browse the repository at this point in the history
  • Loading branch information
luin committed Mar 15, 2024
1 parent dbf42a1 commit 7ea9e53
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 33 deletions.
37 changes: 17 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 **/

Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -144,21 +144,19 @@ 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

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

Expand All @@ -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).
Expand Down Expand Up @@ -207,10 +204,10 @@ 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
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>
console.log(imageNode.outerHTML); // Will print <img>
```

### Class Attributor
Expand All @@ -225,7 +222,7 @@ Parchment.register(Align);

let node = document.createElement('div');
Align.add(node, 'right');
console.log(node.outerHTML); // Will print <div class="blot-align-right"></div>
console.log(node.outerHTML); // Will print <div class="blot-align-right"></div>
```

### Style Attributor
Expand All @@ -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 <div style="text-align: right;"></div>
console.log(node.outerHTML); // Will print <div style="text-align: right;"></div>
```

## Registry
Expand Down
54 changes: 42 additions & 12 deletions src/blot/abstract/blot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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 };
}

Expand Down
18 changes: 18 additions & 0 deletions src/blot/abstract/leaf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand All @@ -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);
Expand All @@ -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,
Expand Down
12 changes: 11 additions & 1 deletion src/blot/abstract/parent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';

Expand Down Expand Up @@ -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<Blot>();
// Need to be reversed for if DOM nodes already in order
Expand Down

0 comments on commit 7ea9e53

Please sign in to comment.