Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #+TITLE:Quill Blots
- There are four options for creating a custom blot:
- +-------+--------+-------+
- | | Inline | Block |
- +-------+--------+-------+
- | Embed | | |
- +-------+--------+-------+
- | Text | | |
- +-------+--------+-------+
- * Inline vs block
- 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."
- * TODO Embed vs text
- * How Quill/Parchment work
- 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.
- * How Quill uses Parchment
- 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= with =value= 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.
- * The Parchment blot hierarchy
- ** ShadowBlot
- 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
- *** ContainerBlot
- Defines a =children= property and extends the [[*How Quill uses Parchment][core methods]] to
- forward the call to the appropriate child (modifying the =index=
- and =length= parameters accordingly)
- **** ScrollBlot
- 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.
- **** FormatBlot
- 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 adds =this.statics.blotName=
- to the map with value =static
- 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= to
- =value= in the format map.
- ***** BlockBlot
- BlockBlot handles =formatAt= calls in two ways:
- - attributor calls :: These calls use formats registered to
- =Attributors= and simply delegate to =FormatBlot=
- - 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 [[*How Quill uses Parchment][core methods]] are essentially unchanged
- ***** InlineBlot
- InlineBlot handles =formatAt= calls that cause the blot to vanish from
- the tree (e.g., removing bolding). It forwards =Attributor=
- calls into the [[*FormatBlot][FormatBlot]] API. =InlineBlot.optimize= performs
- two optimizations:
- 1. If the blot has no children, it is removed from the tree
- 2. If the blot and its next sibling have identical formats, they
- are merged together
- *** LeafBlot
- 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
- **** TextBlot
- Implements =deleteAt= and =insertAt= as expected. =optimize=
- removes empty text nodes and merges consecutive text nodes.
- **** EmbedBlot
- Adds a =format= method for subclasses to override wholesale
- formatting of the embed. Similar to [[*FormatBlot][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 blots
- Quill extends and overrides Parchment's blots as follows:
- ** Container
- Only allows quill-specific Blocks, BlockEmbeds, and itself as
- children.
- *** ScrollBlot
- 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.
- *** FormatBlot
- No change
- **** Block
- 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
- **** BlockEmbed
- This new element ignores all =formatAt= calls except
- Attributors. =insertAt= adds new nodes as siblings rather than
- children.
- **** Inline
- Ensures nested inlines are always nested in the same order (to
- ensure a canonical representation as DOM nodes)
- ** Leaf
- *** Text
- No change from Parchment
- *** Embed
- 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.
- * Specific methods
- ** Basic blot methods
- 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
- ** static create(value: any) : Node
- 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.
- *** When is this method called?
- 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
Add Comment
Please, Sign In to add comment