There are four options for creating a custom blot:
---------------+-------+
Inline | Block |
---------------+-------+
Embed |
---------------+-------+
Text |
---------------+-------+
The major difference between inline and block blots is that blocks cannot be nested. “Instead of wrapping, Block blots replace one another when applied to the same text range.”
Quill’s user-facing interface is based on the Delta
format:
documents are represented as text strings with properties applied to
certain ranges (this format strongly resembles the Emacs propertized
strings format). Block-level formatting is handled by special-casing
the formatting operations on newlines in the text string.
Parchment’s interface is tree-based, though it maps into a linear
content model similar to how the DOM does (in the case of the DOM,
the mapping is called document order). In Parchment’s tree, nodes
are called Blots, which are instances of a ShadowBlot
subclass. As
with any tree, there are two main types of blots: parent blots
inherit from ContainerBlot
and leaf blots inherit from
LeafBlot
.
There’s another categorization of blots that’s specific to Parchment however:
- Inline blots
- These represent either run-level formatting (e.g., bold, italics, etc.) or run-level content (e.g., text nodes). Formatting blots are nestable, just like HTML tags.
- Block blots
- These represent paragraph-level content or formatting (e.g., block quote) that are not nestable. E.g., paragraphs can be of type “unordered list” or “ordered list”, but not both. Block-level content is unspecified in Parchment, but can be e.g., video or floated pictures.
Each Quill instance has an associated Scroll
instance attached to
the main <div contenteditable>
(viz. quill.root
). Operations
using the quill API like quill.applyDelta
or quill.format
will
typically reduce to calls to one of the following core methods on
the scroll instance:
- formatAt(index, length, name, value)
- apply the format
identified by the string
name
withvalue
to the user range[index, index + length)
- insertAt(index, value: string, def?)
- Insert a new string
into the document at
index
(in user coordinates). - deleteAt(index, length)
- Delete the user range
[index, index + length)
- update(mutations, context)
- After any outside modifications to the DOM, this method synchronizes the Blot’s internal data with its DOM node
- optimize()
- This method “canonicalizes” a Blot tree to ensure that the Delta -> Blot map remains well-defined.
This is the root blot type, defining core operations on the blot
tree. There are also a number of essentially internal operations
(attach
, clone
, …, wrap
) made to ease the implementation of
other blots that need to manipulate the blot tree
Defines a children
property and extends the core methods to
forward the call to the appropriate child (modifying the index
and length
parameters accordingly)
This blot defines a MutationObserver
to ensure that update
and optimize
are called on descendant nodes after any DOM
modification. This is used as the root blot in quill.
In addition to managing child nodes, this blot handles
Attributors
(cf. README). It defines an additional API for its
descendant nodes to handle formatAt
calls. The idea is to
associate with each blot a {key: value}
format map
representing the sum of the blot and its attributors. This map is
used in the following methods:
- formats()
- Return the format map for this blot. The default
implementation starts with the
Attributor
attributes and simply addsthis.statics.blotName
to the map with valuestatic formats(this.domNode)
- static formats(domNode)
- Returns the value to use in the format map for the overal blot
- format(name, value)
- This method is in charge of modifying
the blot tree to mirror the effect of setting
name
tovalue
in the format map.
BlockBlot handles formatAt
calls in two ways:
- attributor calls
- These calls use formats registered to
Attributors
and simply delegate toFormatBlot
- all-or-nothing
- These calls replace this blot with another block-scoped blot.
insertAt
is modified slightly to ensure that inserting
block-scoped blots splits the blot instead of adding a
child. The other core methods are essentially unchanged
InlineBlot handles formatAt
calls that cause the blot to vanish from
the tree (e.g., removing bolding). It forwards Attributor
calls into the FormatBlot API. InlineBlot.optimize
performs
two optimizations:
- If the blot has no children, it is removed from the tree
- If the blot and its next sibling have identical formats, they are merged together
Leaf blots are independent units which have a value associated
with them. Their static value(domNode)
and value()
methods
retrieve this value from a dom node and a blot respectively. They
also have index
and position
nodes that I don’t really
understand
Implements deleteAt
and insertAt
as expected. optimize
removes empty text nodes and merges consecutive text nodes.
Adds a format
method for subclasses to override wholesale
formatting of the embed. Similar to FormatBlot, adds static
formats
and formats
methods as well, though instead of a
key-value map, the two formats
methods return any value
representing the embed value. (Default undefined
)
Quill extends and overrides Parchment’s blots as follows:
Only allows quill-specific Blocks, BlockEmbeds, and itself as children.
Special-cases various things for code blocks and breaks. Adds a
whitelist
attribute to restrict which formats may be
used. Ensures that inline blots are always descended from block
blots.
No change
Adds a translation layer to the Delta format using newlines to
indicate blocks. Thus, it ignores all-or-nothing formatAt
calls
unless they include the final newline and modifies insertAt
calls to add blocks where needed
This new element ignores all formatAt
calls except
Attributors. insertAt
adds new nodes as siblings rather than
children.
Ensures nested inlines are always nested in the same order (to ensure a canonical representation as DOM nodes)
No change from Parchment
Wraps the embed in a span with leftGuard
and rightGuard
nodes
on either side of the real dom node. Disables contenteditable on
the wrapping span.
Every Blot has these structure editing methods, which don’t generally have to be overriden
- attach
- clone
- deleteAt
- detach
- formatAt
- insertAt
- insertInto
- isolate
- offset
- optimize
- remove
- replace
- replaceWith
- split
- update
- wrap
This method should create a new `Node` and modify it such that value is later recoverable. In other words, this should be an invertible function. Often this involves using the `dataset` API. E.g., a `LinkBlot` which takes a url as `value` sets the `href` attribute, which can then be used to recover the url.
This method must also set non-format related attributes as needed (e.g., `target=_blank` on links), since the returned node will probably be added to the page.
The super method creates a `DOMNode` using the `tagName` and `className` properties.
This method is only ever called by `Registry.create`, which is in turn called whenever Parchment tree manipulations (and their associated DOM node manipulations) are taking place
I have
const Embed = Quill.import("blots/embed");
class CustomBlot extends Embed {
Class extends value undefined is not a constructor or null
How do I overcome this error?