This is the reference manual for Wordgard. It is a complete list of
every type and value in the library's public interface.
This module defines the library's document data structure, change
model, and some helper functionality for working with those.
Document
A document is a tree of nodes. It is made up of these elements:
- Nodes can be plots (nodes with content
nodes) or leaves. The top document node is a plot.
- Each node has a type.
- Marks are pieces of information attached to nodes.
They are used for things like text style, image alt text, or
block alignment.
- Tags describes a node's nature, it combines a
node type, an optional parameter value, and a
set of marks. Leaf nodes are tags, plot nodes
have tags.
type Node = Plot | Leaf
A node in the document is either a plot (which may have content)
or a leaf node.
type Node.Type<T = unknown> = Leaf.Type<T> | Plot.Type<T>
A node type can be either a leaf type or a plot type.
type Node.Type.Ref<T> = Plot.Type<T> | Leaf.Type<T> |
Plot.Tag<T> | Leaf<T>
Used as input type by some functions acting on node types, so
that you can pass either a bare type or a singleton leaf or
plot tag.
Node.Type.get<T>(ref: Node.Type.Ref<T>): Node.Type<T>
type Node.Tag = Leaf | Plot.Tag
A tag is a node type with a parameter and a set of marks. For
leaves, the entire node is the tag. For plots, it is a separate
object in the tag property.
type Node.Tag.For<
Type extends Node.Type.Ref<any>
> = Type extends Leaf.Type<infer T> ? Leaf<T> : Type
extends Plot.Type<infer T> ? Plot.Tag<T> : Type
Deduce a tag type for a given node type or tag.
interface Node.Spec {
inline?: boolean
Whether this node is an inline or a block node. Defaults to
block.
defaultParam?: Param
The default parameter value for the node type. Only meaningful
when the type is being defined directly, rather than as a
singleton tag.
validate?: string | ((param: Param) => void)
A function or type name used to validate this tag's parameter
value. This will be used when deserializing the attribute from
JSON. When a string, it should be a |-separated string of
primitive types ("number", "string", "boolean", "null",
and "undefined"). The library will raise an error when the
value is not one of those types. When a function, it should
raise an error if the value doesn't have the expected type or
shape.
group?: Node.Group | readonly Node.Group[]
Assign one or more groups to this node type. Groups are used
when specifying allowed content for a plot. Schema overrides
can change a node's set of
groups.
role?: Node.Role | readonly Node.Role[]
Roles to add to this node type, which mark it as having a
certain semantic role, such as being a list.
shape: Shape.Element<Param> | Shape.Structure<Param>
The default DOM/HTML shape of this node. This will determine
what the node looks like, both in an editor an in serialized
HTML form. In most cases, this also specifies the way the node
is parsed when reading HTML content.
parseRules?: readonly parse.Rule.Element<Param>[]
Extra parse rules to associate with this node type.
selectable?: boolean
When set to true, nodes of this type, if they are a leaf or
atom, can be selected by clicking them or moving the selection
into them with the keyboard.
}
class Node.Group {
Groups are used to specify parent-child relationships between
nodes, and valid targets for marks. You can use predefined
groups provided as static properties on the class, or define
your own for custom categories.
parent: Node.Group | undefined
Groups may have a parent group. Membership of a group
implies membership of its parent groups.
static define(parent?: Node.Group): Node.Group
Define a custom node group.
static All: Node.Group
A group that contains every node type.
static Inline: Node.Group
All inline nodes are automatically assigned to this group.
static Block: Node.Group
Block elements automatically get assigned to this group.
static Leaf: Node.Group
The group of all leaf nodes.
static Plot: Node.Group
The group of all non-leaf nodes.
static Textblock: Node.Group
Block plots with inline content are tagged as textblocks.
static Content: Node.Group
A group used for generic block content, such as paragraphs and
lists. The basic schema uses this as the content type for the
top level document, blockquotes, and list items.
static TableCell: Node.Group
Group for the cell nodes in tables.
static ListItem: Node.Group
}
type Node.Query = Node.Tag |
Node.Group |
Node.Type |
readonly Node.Query[] |
{and: readonly Node.Query[]}
Describes a set of node types. Can be either a single tag or
type, which matches exactly that type (tags are assumed to be
singleton tags—only their type is used), a reference to a node group, or a combination of multiple of those.
An array indicates the union of all the groups in the array
(matches types that match any of the queries). An object with an
and property indicates an intersection (must match all the
queries).
class Node.Role {
Roles are used to add some semantic information to node types.
You can define your own, and use the hasRole method to check
whether a given node has the role attached.
static define(): Node.Role
static Code: Node.Role
This role indicates that a plot contains code, and makes some
commands behave differently inside such a plot.
static List: Node.Role
Identifies a plot as a list container. This makes some
commands treat the plot specially.
static LineBreak: Node.Role
A single leaf type in a schema may have the LineBreak role,
which identifies it as the canonical node that represents a
line break. Nodes marked as line breaks will be parsed from
and serialized to newline characters inside whitespace-preserving nodes.
}
Leaf.Text: Leaf.Type<string>
The type of text leaves. Represents a series of characters with
a given set of marks. The only leaf with a length that isn't
always 1. Adjacent text leaves with the same marks are merged
automatically.
class Plot implements Node.Shared {
Plots delimit parts of the document, giving a special meaning to
the nodes inside them. They are defined by a tag
and an array of content.
tag: Plot.Tag
The tag that identifies this plot.
content: readonly Node[]
contentLength: number
The sum of the length of this plot's content nodes.
type: Plot.Type
The type of this plot's tag.
contentEq(other: Plot): boolean
Compare the content of this plot to the content of the given
plot.
inlineContent: boolean
Tells you whether the content of this plot is inline.
isTextblock: boolean
True if this is a block node with inline content.
isDoc: boolean
True if this is a document node.
firstChild: Node | null
Get the plot's first child, if any.
lastChild: Node | null
Get the plot's first child.
iterate(
from: number,
to: number,
f: (
node: Node,
pos: number,
parent: Plot | null,
index: number
) => boolean | undefined
)
iterate(f: (
node: Node,
pos: number,
parent: Plot | null,
index: number
) => boolean | undefined)
Iterate though the given range (or the entire document, when
given only one argument), and call the given function on every
node that overlaps the given range, outer nodes before inner
nodes. When the function returns false for a node, descendents
of that node are not iterated.
nodeAt(pos: number): Node | null
Return the node that starts at the given offset from this node's
content start, if any. Will not return text nodes.
plotAt(pos: number): Plot | null
Return the plot at the given offset, if any.
textContent(options: {
from?: number
An optional start position, as an offset from the plot's
content start.
to?: number
An optional end position.
blockSeparator?: string
Text to separate blocks with. Defaults to a single newline
character.
leafText?: string | ((node: Leaf) => string)
Override the way non-text leaves are converted to string.
} = {}): string
Return the text content of this plot.
tokenType: Token.Type.Node
static define(
name: string,
spec: Plot.Spec<null>
): Plot.Tag<null>
Define a singleton plot type. If the plot needs a parameter
value, use Plot.Type.define instead.
static defineDoc(spec: {
inlineContent?: true | Node.Query,
blockContent?: Node.Query,
canBeEmpty?: boolean
}): Plot.Type<null>
Define a document plot type. Exactly one of these must occur in a schema.
}
Plot.End: {
The end token for a plot. Used in slices.
tokenType: Token.Type.Close
}
interface Plot.Spec extends Node.Spec<Param> {
Object used to define a plot type.
blockContent?: Node.Query
When this node has block-level content, provide a query
matching the nodes it may contain here. You generally don't
want to use Node.Group.Block here, since specialized block
types (like table cells or list items) should probably only be
allowed in their designated parent plots.
inlineContent?: true | Node.Query
When this node has inline content, provide a query specifying
valid content. If set to true, any inline node may appear in
this node.
canBeEmpty?: boolean
Plots with block content, by default, require at least one
child. You can set this to true to allow them to be empty.
Plots with inlne content can always be empty.
cursorBarrier?: boolean
Whether the sides of this plot act as a 'barrier' when normalizing a cursor
position, which means that a separate cursor position exists
at its boundary. By default, nodes that are isolating, whitespace-preserving, or both
a leaf and a block count as barriers.
defaultBlock?: boolean
Indicates that this type of block is the default generic block
type in parent nodes where it may occur (which is appropriate
for, for example, paragraphs tags). Default blocks should not
have a required param. When not specified, the configuration
precedence order determines which child type is the default.
preserveWhitespace?: boolean
Controls whether whitespace inside this type of node should be
preserved. Disables whitespace collapsing and the replacement
of newlines with line break nodes in the parser and
serializer. Defaults to false, unless the node has the Node.Role.Code role.
isolating?: boolean
Isolating plots disallow some kinds of editing across their
borders (such as backspacing or unwrapping). A table cell is
an example of a node that you'd use this for.
orientation?: "row" | "column"
Block containers are, by default, assumed to arrange their
children vertically below each other ("column"). You can set this
to "row" to tell the editor that this container's children
are horizontally next to each other.
defining?: boolean
Defining nodes are preserved (when possible) when their content
is duplicated (dragged, pasted, etc) into a new position.
Defaults to false.
neutral?: boolean
Neutral nodes may be completely replaced when their entire
content gets replaced. Defaults to !Plot.Spec.defining.
autoJoin?: boolean | ((
before: Plot.Tag,
after: Plot.Tag
) => boolean)
Whether block nodes of this type should be automatically
joined when they become adjacent through an edit. Defaults to
false. Note that editing commands need to explicitly call
autoJoinBlocks for joining to happen.
preserveOnSplitAtEnd?: boolean
By default, splitting a textblock at the end will revert the new
block to the default type of textblock at that position. Setting
this to true on a textblock type will prevent that behavior.
cursorInsideBounds?: boolean
For inline nodes with inline content, this determines whether
there are normalized cursor positions directly inside the node.
The default is to only have cursor positions right outside the
node.
}
interface Mark.Spec {
target?: Node.Query
Which node tags this mark may apply to, as node query.
The default is {and: [Node.Group.Inline, Node.Group.Leaf]}.
rank?: number
Determines the position of this mark relative to other marks.
Should be a number between 0 and 100. Marks with lower rank
appear first in mark set arrays, and are rendered around
higher rank marks when rendered as an element. Ties are broken
by name. Defaults to 100.
inclusive?: boolean
Whether this mark should be active when the cursor is positioned
at its end (or at its start when that is also the start of the
parent node). Defaults to true.
spanning?: boolean
Whether this mark can span across multiple nodes, or refers to
an individual node. Only spanning marks can be added to text.
Spanning marks with an element representation can be drawn as
elements containing multiple nodes, unless another, lower-ranked
mark requires the nodes to be wrapped separately. Defaults to
true for specs with an element representation, false
for specs with an attribute representation.
keepOnSplit?: boolean | ((
tag: Plot.Tag,
atEnd: boolean
) => boolean)
Used by Plot.Tag.split to determine
whether to keep this mark in the split-off tag. atEnd will
be true if the split happens at the end of the node's content.
keepOnTypeChange?: boolean | ((
from: Node.Tag,
to: Node.Tag
) => boolean)
Used by withMarksFrom to decide whether marks
of this type are preserved after the type change.
defaultParam?: Param
A default value for the parameter. If given, a mark with this
parameter will be stored in Mark.Type.default.
validate?: string | ((value: Param) => void)
set?: Param extends readonly (infer Content)[] ? {compare: (
a: Content,
b: Content
) => number} : never
A mark parameter can be set-valued, which changes how adding
and removing marks of that type works. This requires the mark
parameter to be an array type. When adding a set-valued mark
to a mark set, the value of the mark in the new set is the
union of the values of its original value and the added mark.
Similarly, when removing such a mark, only the values in the
parameter of the removed mark are removed from the mark in the
set (except when there are none left, in which case the mark
is removed entirely).
compare should be a function that compares two values and
returns 0 if they are the same, or an ordering number
otherwise. This is used to sort and compare the values.
shape: Shape.Element<Param> | Shape.Attribute<Param> |
Shape.Attributes<Param>
A mark can be either represented with a wrapping element, or
with one or more attributes added to the affected nodes.
parseRules?: readonly parse.Rule<Param>[]
A set of parse rules for this mark. The mark field for these
will automatically be defaulted to the mark type itself.
}
type Mark.Set = readonly Mark[]
A set of marks is a sorted array in which a given mark type can
occur at most once.
Shapes
Node and mark shapes (the way they are converted to and from
DOM/HTML structure) can be defined with these data structures.
type Shape.Structure<Param> = {
Declares the shape of a node in a way that allows a more
complicated shape than Shape.Element. This will not
automatically create a parse rule, so you'll want to define
those yourself.
structure: Elt | ((param: Param) => Elt)
The structure as a tree of Elts. If this is for a plot
that is not to be rendered as an atom, the structure should
contain a hole for the content.
atom?: boolean
If structure is a function, and the target node is a plot, use
this to specify whether the plot should be rendered as an atom
or with content.
}
type Shape.Attribute<Param> = {
Declares that a mark is represented with a specific DOM attribute.
This allows a matching parse rule to be derived automatically in
most situations.
attribute: string
The name of the attribute.
value: string |
(Param extends string ? 0 : never) |
((param: Param) => string | null)
Its value. When given as 0, which is ony valid when the
Param type is string, the value of the mark's parameter is
used directly.
readAttribute?: (value: string) => typeof parse.Reject | Param
An optional function that converts the value of the attribute
back into a parameter value. Used in the parse rule.
preferTarget?: string
If the target node may be a composite shape (rather than a
single DOM element), you can provide a limited form of selector
here to target a specific element in that shape. Node names and
class names are supported, as in "img", "img.my-class", or
".class1.class2". If no matching element is found, the
attributes will be added to the node's outer element, as normal.
}
class Elt<T = string> {
This class describes a DOM element, its attributes, and its
children. It is used in describing the structure of nodes and decorations.
Elements can provide a wrapping structure by having a content hole
somewhere in their children, which indicates where the structure
they wrap (typically a node's content) goes.
The type parameter indicates the set of additional leaf types. By
default, it holds only string (as a shorthand for text nodes),
but elements used in decorations also support custom widgets.
tagName: string
The element's tag name. May be prefixed with "svg:" or
"math:" to indicate an SVG or MathML element.
attrs: Attributes
children: readonly (0 | Elt<T> | T)[]
static create<T = string>(
tagName: string,
attrs: Attributes,
children: readonly (0 | T | Elt<T>)[]
): Elt<Exclude<T, 0 | Elt<any>>>
Create an element. See also Elt.mk for a more ergonomic
creation function.
static mk<T = string>(
name: string,
children?: (0 | T | Elt<T>)[]
): Elt<Exclude<T, 0 | Elt<any>>>
static mk<T = string>(
name: string,
attrs: Record<string, string>,
children?: readonly (0 | T | Elt<T>)[]
): Elt<Exclude<T, 0 | Elt<any>>>
Create an element, specifying the attributes as an object. Both
the set of attributesand the array of children are optional. The
literal number 0 is used to indicate a content hole in the
array of children.
hasContent: boolean
True if this element or one of its children has a content hole.
eqTag(elt: Elt<any>): boolean
Compare this element's tag name and attributes (not its
children) to another element.
eq(other: any): boolean
Compare this element (including its children) to another
element.
toHTML(): string
Convert an element with string content to an HTML string.
toDOM(doc?: Document): Element | Text
Convert an element (with only string content) to a DOM tree.
}
type Attributes = readonly string[]
Sets of attributes are stored in arrays of strings, with the even
indices holding attribute names, the odd ones attribute values. The
attributes are sorted by name.
Attributes.none: Attributes
The empty set of attributes.
Attributes.eq(a: Attributes, b: Attributes): boolean
Compare two attribute sets.
Attributes.merge(a: Attributes, b: Attributes): Attributes
Combine two attribute sets, with b having higher precedence when
they set the same attribute.
Attributes.read(obj: Record<string, string | null>): Attributes
Convert an attribute object into a set.
Attributes.get(attrs: Attributes, name: string): string | null
Get the value of the given attribute.
Schema
Each document has a schema associated with it that
configures the set of nodes and marks that can occur in it, and
their relations.
class Schema {
A schema is a collection of node and mark types, including exactly
one document type, plus an optional set of overrides that modify the relations between those elements. It
determines what kind of elements may occur in documents that
follow this schema, and where they can show up.
elements: readonly Schema.Element[]
All the schema elements that make up this schema. Useful if you
want to base another schema on this one.
nodes: readonly Node.Type[]
The node types that are part of this schema.
marks: readonly Mark.Type[]
Mark types used in this schema.
docTag: Plot.Tag<null>
The plot tag used by documents in this schema.
lineBreak: Leaf | null
The line break node defined in
this schema, if any.
doc(children: readonly Node[]): Plot.Doc
Create a document in this schema.
validate(node: Node)
Validate that a node and its content conform to this schema.
Will run automatically when creating a document.
has(elt: Node.Type.Ref<any> | Mark.Type | Mark<any>): boolean
Test whether the given mark or tag type is included in this
schema.
matchNode(node: Node.Type, q: Node.Query): boolean
Test whether a node type matches the given node query.
markAllowed(mark: Mark.Type, node: Node.Type): boolean
Test whether the given mark is allowed on the given node type.
sharesContent(a: Plot.Type, b: Plot.Type): boolean
Returns true if there's at least one node type in the schema
that may occur in both a and b.
withMarksFrom<T extends Node.Tag>(from: Node.Tag, to: T): T
Returns a copy of to with all the marks from from that it
doesn't already have, and that aren't dropped by the mark's
Mark.Spec.keepOnTypeChange configuration.
canContain(parent: Plot.Type, child: Node.Type): boolean
Check whether a given plot type can contain a given node type.
defaultContentTag(parent: Plot.Type): Node.Tag | null
Return the first defaultable node tag that
can occur as a child of parent.
defaultContentPlot(parent: Plot.Type): Plot.Tag | null
Return the first defaultable plot tag
that can be a child of parent.
createAndFill(parent: Node.Tag): Node
Create a node from a tag, optionally adding a default child if
this is a plot that cannot be empty.
findWrapping(
parent: Plot.Type,
child: Node.Type
): readonly Plot.Tag[] | null
Find a set of tags that child must be wrapped in to be able to
occur in parent. Will return the empty array if it fits
directly, and null if it cannot occur at all.
getMark(name: string): Mark.Type | undefined
Get the mark type with the given name in this schema.
getNode(name: string): Node.Type | undefined
Get the node type with the given name.
static define(spec: readonly Schema.Element[]): Schema
Define a schema from a set of schema elements. The set must
contain precisely one document type, and no conflicting node or
mark names.
nodeFromJSON(json: Node.JSON): Node
Deserialize a node from its JSON representation.
tagFromJSON(json: Node.JSON): Leaf | Plot.Tag
Deserialize a tag from its JSON representation.
marksFromJSON(json: Record<string, any>): Mark.Set
Read a set of marks from their JSON representation.
docFromJSON(json: Node.JSON): Plot.Doc
Read a document from JSON.
}
type Schema.Element = Node.Tag | Node.Type | Mark | Mark.Type |
Schema.Override
A schema element is any node tag or type, mark or mark type, or
override.
class SchemaError extends Error {
Exception type used for errors related to schema definition.
}
class ValidationError extends Error {
Exception type used when validating content against a schema, when
checking JSON input, or when validating change sets.
}
Changes
Change sets provide a precise description of how a document
changed. Any modification to the editor's document goes through a
transaction, which describes it using a change set.
class ChangeSet {
A change set contains a series of changes to a given document that
produce a new document. They divide the document in a number of
sections that are either kept as-is, have marks added or removed,
or are replaced entirely by a Slice of new tokens.
Change sets store the length of their start document and will
raise an error if you try to apply them to a document with a
different length.
sections: ChangeSet.Sections
Pairs of integers, with the first one representing the length
of the section in the start document, the second either -1 for
a preserved, -2 for a marked range, or a non-negative
insertion length for a replacement.
length: number
The length of the start document.
newLength: number
The length of the updated document.
empty: boolean
Returns true if this set makes no changes.
eq(other: ChangeSet): boolean
Compare this change set to another one.
apply(doc: Plot.Doc): Plot.Doc
Apply the changes to the given document, producing a new
document. Will raise an error if the document length doesn't
match or the change is not well-formed for this document.
The result of this method is cached, so applying the same change
set to the same document multiple times is cheap.
toJSON(): ChangeSet.JSON
Convert this change set to a JSON-serializeable representation.
static fromJSON(
schema: Schema,
json: ChangeSet.JSON
): ChangeSet
Parse a JSON representation into a change set.
transform(
doc: Plot.Doc,
other: ChangeSet,
before: boolean = false
): ChangeSet
Perform an operational
transformation
on this change and the given other change. Both changes should
start with the given document doc. Returns a modified version
of the change that can be applied after the other change has
been applied to doc.
By default, the semantics of conflicting changes are resolved as
if this came after other. That means content inserted in the
same position by both will put the content inserted by this
last. You can set before to true to invert this, making this
come before other. Setting this correctly is necessary to make
the result of independently applied transformed changes converge.
compose(other: ChangeSet): ChangeSet
Compose two change sets, where other starts from the document
produced by this, into a single change set.
invert(doc: Plot.Doc): ChangeSet
Compute the inverse of this change set. doc is the document
that the change starts from. For a given change A,
doc.apply(A).apply(A.invert(doc)) equals doc.
correct(doc: Plot.Doc, local: boolean = false): ChangeSet
Returns the change itself if it can be applied to this document
and produce a valid new document, or a modified version of the
change that is correct.
mapPos(pos: number, assoc?: 1 | -1): number
mapPos(
pos: number,
assoc: 1 | -1,
track?: ChangeSet.TrackMode
): number | null
Map a document position through this change, returning either
the adjusted position, or null if a the tracked position is
deleted.
The assoc parameter, which defaults to -1, decides to which
side the position sticks. When content is inserted precisely at
the mapped position, it will stay before it when assoc == -1,
and move after it when assoc == 1.
By default, mapping will always return a new position, even if
all the content around the position was deleted. You can pass a
tracking mode to make it return null
when either the token before, the token after, or both tokens
around the position were deleted.
findInserted(pred: (tag: Node.Tag) => boolean): number | null
Scan through the content inserted by this change until a tag
that matches the predicate is found. If successful, return the
position (in the new document) of the tag. This can be useful
for when creating a new selection after a fitted change.
touchesRange(from: number, to: number): boolean | "cover"
Returns true if any of the replaced ranges in this change set
overlaps or is adjacent to the given range.
iterChanges(
replaced: (
fromA: number,
toA: number,
fromB: number,
toB: number,
inserted: Slice
) => void,
preserved?: (
fromA: number,
toA: number,
fromB: number,
toB: number,
modifications: readonly ({add: Mark} |
{remove: Mark})[] | null
) => void
)
Iterate over the ranges in this changeset, calling replaced
for ranges that have been replaced, and preserved for ranges
that are either preserved as-is (when modifications is null)
or only have marks modified.
iterGaps(
gap: (
fromA: number,
toA: number,
fromB: number,
toB: number
) => void,
change?: (
fromA: number,
toA: number,
fromB: number,
toB: number
) => void
)
Iterate over the sections of the document this change leaves
unchanged or which have only mark changes. posA provides the
position of the range in the original document, posB the
position in the changed document.
iterChangedRanges(range: (
fromA: number,
toA: number,
fromB: number,
toB: number
) => void)
Iterate over the ranges changed (either replaced or modified) by
this change desc. Joins adjacent changed ranges together.
pad(before: number, after: number): ChangeSet
Add skipped sections before and after this change set, so that
it can apply to a larger document. Mostly useful when
propagating changes from an editor displaying a smaller part of
a document into the full document.
clip(from: number, to: number): ChangeSet | null
Clip the set to only a sub-region. This can fail, if there are
replacements across the region's sides, in which case the
method returns null
static create(doc: Plot.Doc, spec: ChangeSet.Spec): ChangeSet
Create a change set. All positions in the given change
description refer to positions in the starting document.
static empty(length: number): ChangeSet
Returns an empty change set for a document of the given length.
static transform(
doc: Plot.Doc,
a: ChangeSet,
b: ChangeSet
): {a: ChangeSet, b: ChangeSet}
Transform two change set starting from the same document over
each other, returning two transformed change sets. The returned
a can be applied after the b passed in, and the returned b
can be applied after the a passed in, resulting the same final
document on both sides. a is taken to happen before b when insertions
at the same position need to be merged.
This method is slightly more efficient than transforming both steps
separately.
}
type ChangeSet.Change = {
Representation of a single document change, as used in ChangeSet.Spec. Changes can either affect marks (when add or
remove is present), or replace a part of the document
(otherwise).
from: number
The start position of the change.
to?: number
The end position. When not given, this defaults to from for
replacement changes, and from + 1 for changes that add or
remove marks.
insert?: Slice | readonly Token[]
Replace the given range with this slice.
fit?: boolean | readonly Plot.Tag[]
For deletions or insertions where it isn't obvious that the
replacement will produce a valid document, set this to true
or a stack of context tags to make the library process the
replacement to make sure it fits. Context tags (passed with
the innermost tag first, as in contextAt may
be used as wrappers when fitting the slice.
add?: Mark
Add the given mark to this change's range. Cannot be combined
with insert.
remove?: Mark
Remove the given mark from this range.
}
type ChangeSet.Spec = ChangeSet | ChangeSet.Change | {
correct: ChangeSet.Spec,
local?: boolean
} | readonly ChangeSet.Spec[]
This type is used to describe a change
set. A spec can be a single change, an existing change set, a
set of changes wrapped in a correction scope, or an array of the
same.
The from and to positions in the changes in a set spec
all refer to the origin document. It is not necessary to
'compensate' for earlier changes in those specified later. If,
for some reason, you have changes that should be applied after
each other, create multiple change sets and compose them.
By default, the provider of changes vouches for their
correctness. It is possible to create change sets that will
error when you try to apply them, because applying them does not
create a well formed document.
When making changes where you cannot guarantee that they fit,
you should either use ChangeSet.Change.fit, which will
try to change the range of a change to make it fit, or the
{correct} form, which will combine the changes it is given,
and then process them as a whole to make sure they produce a
valid document. The local flag indicates that the effect of
changes should be kept as narrow as possible—for example, that
nodes opened but not closed by them should not extend to cover
content after the change.
type ChangeSet.Sections = readonly number[]
The sections in a change set are represented as an array, with
each pair of two numbers describing a changed section. The first
number is the length of the section in the old document. The
second number is -1 for unchanged sections, -2 for updated
sections, and a non-negative number (the length of the inserted
content) for replacements.
type ChangeSet.JSON = readonly (number | [
number,
Slice.JSON | readonly ({
add: string,
value: any
} | {remove: string, value: any})[]
])[]
The JSON representation of a change set.
type ChangeSet.TrackMode = "before" | "after" | "around"
Modes available in mapPos to control whether
null is returned on nearby deletions.
type Slice.JSON = readonly (Node.JSON | ".")[]
A slice's JSON representation.
type Token = Node | Plot.Tag | {tokenType: Token.Type.Close}
The type of tokens in a slice. A plot tag represents the point
where a plot is opened, Plot.End the point where a plot is
closed, and nodes just represent the insertion of that node.
Resolved Positions
Positions in a wordgard document are expressed with offset numbers.
On its own, such a number doesn't tell you much about its context.
Resolved positions are data structures that describe a node or a
position in a node, plus that node's parents (if any).
class Pos {
This class represents a resolved
position.
parent: Pos.Plot
The plot that the position points into.
pos: number
index: number
The index into its parent's content array. Note that if
inText is non-zero, this is the index of the text node.
inText: number
Text nodes don't count as parent plots. Rather, positions that
fall inside a text node have a non-zero value here that
provides the offset into the text node.
matchingParent(pred: (plot: Plot) => boolean): Pos.Plot | null
Find the innermost parent plot for which the given predicate
returns true.
advance(distance: number, walk?: Pos.Walker): Pos
Move ahead through the document the given number of positions.
Don't descend into nodes that fall entirely within the skipped
range. If walk is given, call methods on it for each node
entered, skipped, or left. Return a new position at the end of
the range.
walk(distance: number, walk: Pos.Walker): Pos
Move ahead through the document, always entering every plot. If
walk is given, call methods on it for each node or node
boundary passed. Return a new position.
nodeAfter: Node | null
Get the node directly after this position. If inText is
non-zero, return only the part of the text node that's after the
position.
nodeBefore: Node | null
Get the node directly before this position. If inText is
non-zero, return only the part of the text node before the
position.
textblockParent: Pos.Plot | null
depth: number
Get the depth of this position (the amount of plots that wrap
it, not counting the document).
parentAt(depth: number): Pos.Plot
Get the parent plot at the given depth.
isAtStart(parent: Pos.Plot): boolean
Returns true if this position is right at the start of the given
parent plot, or transitively at the start of its first child.
isAtEnd(parent: Pos.Plot): boolean
Returns true if this position is right at the end of the given
parent plot, or transitively at the end of its last child.
doc: Plot.Doc
Get the document that this position points into.
marks(across?: Pos): Mark.Set
Get the set of active inline marks at this position or, if
across is given, the marks that would apply to content
replacing the range between that this and across.
}
class Pos.Node {
Represents the position of a node, with information about its
parent plots.
parent: Pos.Plot | null
The node's direct parent.
node: Node
index: number
The node's index in its parent plot.
before: number
The position before the node. Will raise an error if this is
the document top node.
after: number
The position after the node. Throws if this is a document.
depth: number
The depth of this node (the number of parent nodes, not
counting the document).
doc: Plot.Doc
The document that this position points into.
isFirst: boolean
Returns true if this is either a document node or the first
node in its parent.
isLast: boolean
Returns true if this is a document node or the last node in its parent.
nextSibling: Node | null
The node before this one, if any.
previousSibling: Node | null
The node after this node, if any.
}
Parsing and Serialization
These functions allow conversion between HTML and Wordgard
document formats. Note that, because not all of HTML is
expressible in a given Wordgard schema, parsing is lossy.
parse(
schema: Schema,
doc: Element | DocumentFragment,
options: parse.Options = {}
): Plot.Doc
Parse the given DOM structure as a document, using the given
schema. By default the set of parse rules will be derived from the
schema, but it is possible to pass in a custom set.
type parse.Options = {
Options that can be passed to parsing functions.
collapseWhiteSpace?: boolean
Controls whether HTML-style whitespace collapsing is used
(outside nodes that don't enable preserveWhitespace). Defaults
to true.
isOpen?: (elt: Element) => "start" | "end" | "start end" | null
Function used in in parse.slice to determine
whether a given element is open (on either side).
ruleSet?: parse.Rule.Set
The rule set to use. Defaults to the rule set derived from the
schema.
}
parse.slice(
schema: Schema,
doc: Element | DocumentFragment,
options: parse.Options = {}
): {slice: Slice, context: Plot.Tag[]}
Parse the given DOM structure as a slice.
type parse.Rule<Param = any> = parse.Rule.Element<Param> |
parse.Rule.Attribute<Param>
Parse rules describe how DOM constructs should map to Wordgard
document nodes. Many can be automatically derived from node and
mark shapes, but it is also often
useful to provide them directly.
interface parse.Rule.Attribute {
An attribute parse rule matches an attribute, instead of an
entire element. Such a rule can only create marks, not tags.
attribute: string
The attribute to look for. May have the form style/color
to look at a style property instead.
value?: string
When given, this rule only matches when the attribute has
this value.
mark?: Mark.Type<Param> | Mark<Param>
The mark to create for this attribute, if any.
clearMark?: (mark: Mark) => boolean
Remove a mark from the surrounding set of marks when this
rule matches.
ignore?: boolean
Can be set to true to cause the parser to ignore this
attribute.
param?: Param
readAttribute?: (value: string) => typeof parse.Reject | Param
Read a parameter value from the attribute value. May return
parse.Reject to prevent the rule from matching.
consuming?: boolean
Controls whether other rules may match this attribute after
this rule matches. Defaults to true.
precedence?: number
A number between -10 and 10 (inclusive) that specifies the
relative precedence of this rule. Defaults to 0.
}
parse.Reject: symbol
A special value that parse rule functions can return to block
the rule from matching.
serialize(
doc: Plot.Doc,
options: serialize.Options = {}
): Elt.Fragment
Serialize a document to an array of elements and
strings. These can be converted to a DOM structure with toDOM or an HTML string with toHTML. Will use the shapes specified in the node specs, unless overridden.
serialize.node(
node: Node,
options: serialize.Options
): string | Elt
serialize.slice(
slice: Slice,
options: serialize.slice.Options
): Elt.Fragment
interface serialize.slice.Options extends serialize.Options {
openAttr?: string
If given, the serializer will set this attribute to
"start", "end", or "start end" for nodes that are open
at the start and/or end of the slice.
context?: readonly Plot.Tag[]
The slice's context. Will be used to determine the type of
open nodes at the start of the slice.
includeContext?: number
The amount of context nodes to include in the output.
Defaults to 0, meaning only use those that are open at the
start of the slice.
}
This module defines an editor state data
structure, a transaction abstraction for
updating such a state, and a selection
model.
Editor State
The editor state captures the situation in a Wordgard editor at
any given point in time. The editor starts with a given state when
created. On every state-affecting user action, it applies a transaction, which produces the new editor state.
class GardState {
The editor state tracks things like the current document, the
selection, the configuration of the editor, and any extra state
defined by extensions.
The state is a persistent (immutable) data structure. To update a
state, you create a transaction, which produces a new state instance, without
modifying the original object.
static create(spec: GardState.Spec): GardState
Create a new state. You'll usually only need this when
initializing an editor or loading a new document—updated states
are created by applying transactions.
The schema of the state can be provided either via the configuration or by passing in an
initialized document (which will have its own schema). If the
configuration contains a document plot type, the schema from the
configuration will be used, even if a document was provided.
config: GardState.Configuration
The configuration for this state.
doc: Plot.Doc
schema: Schema
selection: GardSelection
sel: GardSelection.Resolved
A resolved form of the state's selection. Instead of raw
positions, this object holds document position
objects for head, anchor, from, and to.
field<T>(field: GardState.Field<T>): T
field<T>(
field: GardState.Field<T>,
require: false
): T | undefined
Retrieve the value of a state field.
Throws an error when the state doesn't have that field, unless
you pass false as second parameter.
facet<Output>(facet: GardState.Facet.Reader<Output>): Output
Get the value of a state facet.
update(spec: Transaction.Spec): Transaction
textblockMap(node: Pos.Plot): TextblockMap
Compute the textblock map for the given plot (which should be a
textblock).
toJSON(fields?: {[string]: GardState.Field<any>}): any
Convert this state to a JSON-serializable object. When custom
fields should be serialized, you can pass them in as an object
mapping property names (in the resulting object, which should
not use doc or selection) to fields.
static fromJSON(
json: any,
extensions: GardState.Extension,
fields?: {[string]: GardState.Field<any>}
): GardState
Deserialize a state from its JSON representation. When custom
fields should be deserialized, pass the same object you passed
to toJSON when serializing as third
argument.
readOnly: boolean
Returns true when the editor is readOnly to be
read-only.
textLTR: boolean
Get the global text direction (true when left-to-right, false
when right-to-left) for the document. Note that the direction of
individual blocks can be overridden with textblockLTR.
textblockLTR(plot: Plot): boolean
Return the text direction in a given textblock (by tag).
isAtom(type: Node.Type): boolean
Tells you whether a node type is an atom (a leaf or a plot with
an atomic shape).
wordAt(pos: number, bias: 1 | -1 = 1): GardSelection.Text
Return the extent of the word around the given position, as a
text selection.
static reconfigure: Transaction.Effect.Type<
GardState.Extension
>
This effect can be used to reconfigure the root extensions of
the editor. Doing this will discard any extensions appended, but does not reset the
content of reconfigured compartments.
static appendConfig: Transaction.Effect.Type<
GardState.Extension
>
Append extensions to the top-level configuration of the editor.
}
type GardState.Field.Spec<Value> = {
The options passed when defining a state field.
create: (state: GardState) => Value
Creates the initial value for the field when a state is created.
update: (value: Value, transaction: Transaction) => Value
Compute a new value from the field's previous value and a
transaction. Should not mutate the old
value (since that will change the existing state), but
create a fresh one or return the old value unchanged.
compare?: (a: Value, b: Value) => boolean
Compare two values of the field, returning true when they are
the same. This is used to avoid recomputing facets that depend
on the field when its value did not change. Defaults to using
===.
provide?: (
field: GardState.Field<Value>
) => GardState.Extension
Provide extensions based on this field. The given function
will be called once with the initialized field. It is
typically used with a facet's from
method to create facet inputs from this field, but can also
return other extensions that should be enabled when the
field is present in a configuration.
toJSON?: (value: Value, state: GardState) => any
A function used to serialize this field's content to JSON. Only
necessary when this field is included in the argument to
toJSON.
fromJSON?: (json: any, state: GardState) => Value
A function that deserializes the JSON representation of this
field's content.
}
class GardState.Facet<
Input,
Output = readonly Input[]
> implements GardState.Facet.Reader<Output> {
A facet is a labeled value that is associated with an editor
state. It takes inputs from any number of extensions, and combines
those into a single output value.
Examples of uses of facets are the read-only configuration, editor attributes, and update listeners.
Note that Facet instances can be used anywhere where GardState.Facet.Reader is expected.
Facets have an input type (the type of values provided for it),
and an output type (the type you get when you read the facet)
that defaults to an array of input values, but can be anything
if a GardState.Facet.Spec.combine
option is provided.
default: Output
The output of the facet when it has no inputs.
isStatic: boolean
True when this is a static facet.
reader: GardState.Facet.Reader<Output>
A facet reader for this facet, which can be used to read it but not to define values for it.
static define<
Input,
Output = readonly Input[]
>(config: GardState.Facet.Spec<
Input,
Output
> = {}): GardState.Facet<Input, Output>
Defines a facet with the given input an output types.
of(value: Input): GardState.Extension
Returns an extension that provides the given value to this
facet.
compute(get: (state: GardState) => Input): GardState.Extension
Create an extension that computes a value for the facet from a
state. The given function should only depend on the state, not
any external non-constant inputs. Its return value will be kept
on state update, unless any of the fields or facets (including
document and selection) that it read are changed by the update,
in which case it is called again.
In cases where your value depends only on a single field, you
can use the from method
instead.
computeN(get: (
state: GardState
) => readonly Input[]): GardState.Extension
Create an extension that computes zero or more values for this
facet from a state.
from<T extends Input>(
field: GardState.Field<T>
): GardState.Extension
from<T>(
field: GardState.Field<T>,
get: (value: T) => Input
): GardState.Extension
Shorthand method for registering a facet source with a state
field as input. If the field's type corresponds to this facet's
input type, the getter function can be omitted. If given, it
will be used to produce the input from the field value.
}
type GardState.Facet.Spec<Input, Output> = {
combine?: (value: readonly Input[]) => Output
How to combine the input values into a single output value. When
not given, the array of input values becomes the output. This
function will immediately be called on creating the facet, with
an empty array, to compute the facet's default value when no
inputs are present.
compare?: (a: Output, b: Output) => boolean
How to compare output values to determine whether the value
of the facet changed. When a new value for the facet is
computed that that compares as equal to the old value, the
old value is kept. So in most circumstances, facet values
can be cheaply compared by identity to check for changes.
Defaults to comparing by === or, if no combine function
was given, comparing each element of the array with ===.
compareInput?: (a: Input, b: Input) => boolean
How to compare input values to avoid recomputing the output
value when no inputs changed. Defaults to comparing with ===.
static?: boolean
Forbids dynamic inputs to this facet. Allows the facet to be
read from a
configuration.
enables?: GardState.Extension |
((self: GardState.Facet<
Input,
Output
>) => GardState.Extension)
If given, these extensions (or the result of calling the
given function with the facet) will be added to any state
where this facet is provided. (Note that, while a facet's
default value can be read
from a state even if the facet wasn't present in the state
at all, the extensions won't be added in that situation.)
}
type GardState.Facet.Reader<Output> = {}
A facet reader can be used to fetch the value of a facet,
through facet or as a dependency in Facet.compute, but not to define
new values for the facet.
GardState.Facet.combineConfig<Config extends {}>(
configs: readonly Partial<Config>[],
defaults: Partial<Config>,
combine: {[P in keyof Config]: (
first: Config[P],
second: Config[P]
) => Config[P]} = {}
): Config
Utility function for combining multiple configuration objects.
defaults should hold default values for all optional fields
in Config.
The function will, by default, raise an error when a field
gets two values that aren't ===-equal, but you can provide
combine functions per field to do something else.
type GardState.Extension = {extension: GardState.Extension} |
readonly GardState.Extension[]
Extension values can be provided
when creating a state to attach various kinds of configuration
and behavior information. They can either be built-in
extension-providing objects, such as state fields or facet providers, or
objects with an extension in its extension property.
Extensions can be nested in arrays arbitrarily deep—they will be
flattened when resolved into a configuration.
GardState.prec: {
By default extensions are registered in the order they are found
in the flattened form of the configuration's extension tree.
Individual extension values can be assigned a precedence to
override this. Extensions that do not have a precedence set get
the precedence of the nearest parent with a precedence, or
default if there is no such
parent. The final ordering of extensions is determined by first
sorting by precedence and then by order within each precedence.
highest: (ext: GardState.Extension) => GardState.Extension
The highest precedence level, for extensions that should end up
near the start of the precedence ordering.
high: (ext: GardState.Extension) => GardState.Extension
A higher-than-default precedence, for extensions that should
come before those with default precedence.
default: (ext: GardState.Extension) => GardState.Extension
The default precedence, which is also used for extensions
without an explicit precedence.
low: (ext: GardState.Extension) => GardState.Extension
A lower-than-default precedence.
lowest: (ext: GardState.Extension) => GardState.Extension
The lowest precedence level. Meant for things that should end up
near the end of the extension order.
}
GardState.schemaElement: GardState.Facet<
Schema.Element | readonly Schema.Element[],
readonly Schema.Element[]
>
Facet used to register schema elements. If
a configuration contains a document
type, the editor's document schema will be derived from the
content of this facet. (Otherwise, the state will try to use the
schema provided via the GardState.Spec.doc option, or
raise an error is none is provided.)
GardState.readOnly: GardState.Facet<boolean, boolean>
This facet controls the value of the readOnly getter, which is consulted by commands and
extensions that implement editing functionality to determine
whether they should apply. It defaults to false, but when its
highest-precedence value is true, the state is considered
read-only, and such functions won't change the document.
Not to be confused with editable, which
controls whether the editor's DOM is set to be editable (and
thus focusable).
GardState.textLTR: GardState.Facet<boolean, boolean>
Facet that indicates the document's default text direction. Note
that this will not affect the editor CSS, and when the state's
value disagrees with the direction set in the editor, the editor
component will automatically inject an instance of this with a
high precedence to align the state to the DOM. Still, if you
know the direction in advance, it can be useful to set this, so
that the direction is already accurate during initialization.
Defaults to true.
GardState.textblockLTR: GardState.Facet<
(plot: Plot) => boolean | null
>
Configure the text direction per textblock. All values given for
this will be consulted in order of precedence, until one returns
a non-null value. If none set a direction, the editor's base direction is used.
Schema elements like direction register an
instance of this to make the editor aware of the meaning of the
Direction mark.
GardState.visualCursorMotion: GardState.Facet<boolean, boolean>
Configure whether to use visual or logical cursor motion in
bidirectional text. The default is visual, where pressing
left/right arrow keys moves the cursor in the direction that
corresponds to the arrow on key. When disabled, the motion uses
the string index order instead.
class Transaction {
Changes to the editor state are grouped into transactions.
Typically, a user action creates a single transaction, which may
contain any number of document changes, may change the selection,
or have other effects. Create a
transaction by calling update, or immediately
dispatch one by calling Wordgard.dispatch.
startState: GardState
The state from which this transaction starts.
changes: ChangeSet
The document changes made by this transaction.
selection: GardSelection | undefined
The selection set by this transaction, or undefined if it
doesn't explicitly set a selection.
effects: readonly Transaction.Effect<any>[]
The effects contained in this transaction.
scrollIntoView: boolean
Whether the selection should be scrolled into view after this
transaction is dispatched.
newSelection: GardSelection
The new selection produced by the transaction. If this.selection is undefined, this will
map the start state's current
selection through the changes made by the transaction.
newDoc: Plot.Doc
The new document produced by the transaction. Contrary to
.state.doc, accessing this won't
force the entire new state to be computed right away, so it is
recommended that transaction
extenders use this property when they need to look at the new
document.
state: GardState
The new state created by the transaction. Lazily computed so
that the state is resolved the first time this property is
accessed.
annotation<T>(
type: Transaction.Annotation.Type<T>
): T | undefined
Get the value of the given transaction annotation type, if any.
docChanged: boolean
Indicates whether the transaction changed the document.
reconfigured: boolean
isUserEvent(event: string): boolean
Returns true if the transaction has a user event annotation that is equal to or
more specific than event. For example, if the transaction has
"select.pointer" as user event, "select" and
"select.pointer" will match it.
}
Transaction.merge(
state: GardState,
a: Transaction.Spec,
b: Transaction.Spec
): Transaction.Spec
Merge two transaction specs into a single one, combining the
effect of both.
Transaction.extender: GardState.Facet<
(tr: Transaction) => Transaction.Spec | null
>
Facet used to register a hook that gets a chance to add to
transactions before they are applied. If such a function returns
a transaction spec, it will be combined with the original
transaction (in the same way as the arguments to
update).
When possible, it is recommended to avoid accessing state in an extender, since it will force creation
of a state that will then be discarded again, if the transaction
is actually extended.
This functionality should be used with care. Indiscriminately
modifying transaction is likely to break something or degrade
the user experience.
Extenders that may add document changes should generally not do
anything for remote transactions,
because doing so risks causing endlessly cascading changes or
other confusion. It is possible to define extenders that are
safe when activated on multiple peer (for example, duplicate
deletions of the same content tend to converge), but it requires
a lot of care.
Transaction.appender: GardState.Facet<(
trs: readonly Transaction[],
state: GardState
) => Transaction.Spec | null>
A transaction appender can create more transactions in response
to a transaction. Transaction.append, which is
called by the editor when dispatching a
transaction, will call appenders on sets of transactions,
allowing them to add another transaction. When another appender
adds a transaction, extenders that already ran will be called
again, but only with the transactions that were added after they
ran.
Transaction.append(tr: Transaction): readonly Transaction[]
Transaction.foo: number
Transaction.time: Transaction.Annotation.Type<number>
Annotation used to store transaction timestamps. Automatically
added to every transaction, holding Date.now().
Transaction.userEvent: Transaction.Annotation.Type<string>
Annotation used to associate a transaction with a user interface
event. Holds a string identifying the event, using a
dot-separated format to support attaching more specific
information. The events used by the core libraries are:
"input" when content is entered
"input.type" for typed input
"input.type.compose" for composition
"input.paste" for pasted input
"input.drop" when adding content with drag-and-drop
"delete" when the user deletes content
"delete.selection" when deleting the selection
"delete.forward" when deleting forward from the selection
"delete.backward" when deleting backward from the selection
"delete.cut" when cutting to the clipboard
"move" when content is moved
"move.drop" when content is moved within the editor
through drag-and-drop
"select" when explicitly changing the selection
"select.pointer" when selecting with a mouse or other pointing
device
"select.all" when selecting the entire document
"undo" and "redo" for history actions
"insert" for actions that insert nodes
"mark" for actions that manipulate marks
"mark.add" when a command adds a mark
"mark.remove" when a command removes one
"split", "wrap", "settype", "wrap", "unwrap" for
block manipulation actions
Use isUserEvent to check
whether the annotation matches a given event.
Transaction.addToHistory: Transaction.Annotation.Type<boolean>
Annotation indicating whether a transaction should be added to
the undo history or not.
Transaction.remote: Transaction.Annotation.Type<boolean>
Annotation indicating (when present and true) that a transaction
represents a change made by some other actor, not the user. This
is used, for example, to tag other people's changes in
collaborative editing.
Transaction.appended: Transaction.Annotation.Type<boolean>
Transaction.Effect.mapEffects(
effects: readonly Transaction.Effect<any>[],
mapping: ChangeSet
): readonly Transaction.Effect<any>[]
Map an array of effects through a change set.
interface Transaction.Effect.Spec {
Options passed when defining an effect.
map?: (value: Value, mapping: ChangeSet) => Value | undefined
Provides a way to map an effect like this through a position
mapping. When not given, the effects will simply not be mapped.
When the function returns undefined, that means the mapping
deletes the effect.
}
Selection
Editor selections are objects that derive from GardSelection. This module defines text and node selection types. It is
possible for extension code to define
custom selection types.
abstract class GardSelection {
protected new GardSelection(
anchor: number,
head: number,
goalColumn?: number
)
anchor: number
The anchor of the selection—the side that doesn't move when
you extend it.
head: number
The head of the selection, which is moved when it is extended
(for example by moving the cursor while holding Shift).
goalColumn?: number
The goal column (stored vertical offset) associated with a
selection. This is used to preserve the vertical position when
moving across lines of different length.
from: number
The lower boundary of the selected range.
to: number
The upper boundary of the range.
empty: boolean
True when anchor and head are at the same position.
isCursor: boolean
Returns true when this is an empty text selection.
ranges: readonly {from: number, to: number}[]
The set of ranges covered by this selection, sorted. By default,
this is just the selection's main from to to, but custom
selection implementations can override it.
replacementRange: {from: number, to: number}
The range that should be used when replacing this selection with
other content (for example when typing or pasting over it) or
deleting it. The default implementation returns this.from to
this.to.
domSelection: {
head: number,
headSide: 1 | -1,
anchor: number,
anchorSide: 1 | -1
}
This can be overridden to control the DOM selection created for
a selection. The default is to just return the selection's own
head and anchor.
headSide: 1 | -1
The side that the selection head is associated with. -1 means it
is after the element before its position, 1 means it is before
the element after its position. This influences where the
cursor is drawn (for example when on a line wrapping boundary
or in bidirectional text) and where further motion takes it. It
is valid for it to point in a direction where the is no element
(say, -1 when at the start of its parent node).
By default, this points in the direction of the anchor or
forward if that is equal to the head, but selection types like
GardSelection.Text can override it.
anchorSide: 1 | -1
The side associated with the
selection anchor. Also by default points towards the head, or if
that is the same position, forward.
abstract eq(other: GardSelection): boolean
Compare this selection to another selection.
eqPos(other: GardSelection): boolean
Returns true if this selection has the same head and anchor as
the given selection.
abstract map(
change: ChangeSet,
cx: GardSelection.Context,
assoc?: 1 | -1
): GardSelection
Map a selection through a change. Used to adjust the selection
position for changes.
toJSON(state: GardState): unknown
Convert this selection to an object that can be serialized to
JSON. Each selection type may define its own JSON representation
format.
static fromJSON(
cx: GardSelection.Context,
json: unknown
): GardSelection
Deserialize a selection. The configuration is used to associate
custom selection types with their implementation.
static cursor(
pos: number,
side?: 1 | -1,
goalColumn?: number
): GardSelection.Text
Create a cursor text selection at the given position.
static range(
anchor: number,
head?: number,
headSide?: 1 | -1,
goalColumn?: number
): GardSelection.Text
static node(
pos: number,
node: Node,
goalColumn?: number
): GardSelection.Node
nextNormalCursor(
cx: GardSelection.Context,
forward: boolean = true
): GardSelection.Text | null
Find the next normal cursor position after or before this selection's
head. Normal cursor positions are:
Any inline position, except one directly inside of an inline
plot that doesn't have cursorInsideBounds set.
Positions between two cursor barriers, if not already an
inline position. Cursor barriers are the sides of the
document, any block leaves, or plots that are isolating, whitespace-preserving, or
explicitly defined as a cursor barrier.
normalCursorAtBound(
cx: GardSelection.Context,
forward: boolean = true
): GardSelection.Text | null
Get a normal cursor at the start or end of this selection.
skipWord(
cx: GardSelection.Context,
forward: boolean = true
): GardSelection.Text | null
Move across one word starting from this selection's head.
static near(
cx: GardSelection.Context,
pos: number,
bias: 1 | -1 = 1
): GardSelection.Text
Find a normal selection near the given position.
static atStart(
cx: GardSelection.Context,
block?: Pos.Plot
): GardSelection.Text
Find a normal selection at the start of the document or the
given textblock.
static atEnd(
cx: GardSelection.Context,
block?: Pos.Plot
): GardSelection.Text
Find a normal selection at the end of the document or the given
textblock.
}
GardSelection.define<T extends GardSelection, JSON extends {}>(
tag: string,
cls: {new(...args: any[]): T},
toJSON: (sel: T) => JSON,
fromJSON: (doc: Plot.Doc, json: JSON) => T
): any
Create an extension that registers a custom selection type using
the given class. Such a selection is only valid in a state that
has the extension active. The JSON representation of a selection
will be tagged with the tag string, and created and read via
the functions passed here.
class GardSelection.Text extends GardSelection {
Text selections hold a single arbitrary range in the document.
They represent a cursor when their anchor and head are the same
position.
marks: Mark.Set | undefined
A set of active marks that should be applied to content
inserted at this selection (replacing the contextual marks).
Used mostly for making the effect of toggling inline styles
stick until something is inserted. Marks that aren't valid for
the inserted content will be ignored.
static create(
spec: GardSelection.Text.Spec
): GardSelection.Text
}
type GardSelection.Text.Spec = {
Description of a text selection.
anchor: number
The anchor point of the selection. This is the side that doesn't
move when extending the selection (for example by moving the
cursor while holding shift).
head?: number
The moving side of the selection. This will default to anchor
when not given.
headSide?: 1 | -1
The side of the head position that the selection is associated
with, if any. -1 points at the element before the position,
1 at the element after. This is only meaningful for
empty/cursor selections. It will influence where the cursor is
drawn.
goalColumn?: number
Associates a horizontal position with this selection for use
during vertical cursor motion.
marks?: Mark.Set
Marks associated with a cursor selection, which will determine
the marks of inline content inserted at that selection. This is
used for things like toggling emphasis on a cursor selection.
}
type GardSelection.Text.JSON = {
anchor: number,
head?: number,
side?: 1 | -1,
marks?: Record<string, any>
}
The representation of a text selection when serialized to JSON.
type GardSelection.Node.JSON = {pos: number}
The representation of a node selection when serialized to JSON.
type GardSelection.Context = {
doc: Plot.Doc,
config: GardState.Configuration
}
Many selection related functions need access to a configuration
(to determine text direction and visual motion behavior) and a
document. Note that GardState is a subtype of this.
Textblocks
It is often helpful to look at a textblock (a block plot
containing inline content) as a flat line of text. A textblock map is a data structure intended to make
that easy.
class TextblockMap {
A textblock map contains the text in a textblock as a string, and
can help convert between string offsets and document positions.
Note that this is not the way to convert a piece of document to a
string. Use textContent for that.
start: number
The start position of the textblock content.
node: Plot
The textblock's document node.
ltr: boolean
Whether the base direction of this block is left-to-right.
text: string
The text in the block. Non-text leaf nodes and nodes with
block content will be replaced by a single 0xfffc character
in this string.
order: readonly BidiSpan[]
The text order of the text in this block. Will generally be a
single span, but if the block mixes left-to-right and
right-to-left text, this describes the individual sections,
ordered from the block's start to its end.
static get(
cx: GardSelection.Context,
start: number,
node: Plot
): TextblockMap
Get the map for the given textblock. Will use a cache to reuse
results for unchanged blocks.
toIndex(pos: number): number
Get the string index for a document position. Positions outside
of the textblock will be clipped to its start or end.
fromIndex(index: number): number
Get the document position for a given string index.
visualSide(start: boolean): {pos: number, side: 1 | -1}
Get the position at the start or end of the textblock. Note that
in bidirectional text this may not be the actual start or end
position of the node.
}
class BidiSpan {
Represents a contiguous range of text that has a single direction
(as in left-to-right or right-to-left).
ltr: boolean
The direction of this span.
from: number
The start of the span (relative to the start of the line).
to: number
level: number
The "bidi
level"
of the span. 0 means left-to-right, 1 means right-to-left, 2
means left-to-right embedded inside right-to-left, and so on,
with even numbers being left-to-right, odd numbers
right-to-left..
static strongDir(ch: number): boolean | null
Query whether the given character has a strong direction.
Returns null when not, true when left-to-right, and false when
right-to-left.
}
Corrections
Corrections provide an abstraction for enforcing certain types of
invariants about a document's shape. They watch for some kinds of
document changes on specific types of nodes and, when they occur,
run a function to verify that some condition holds and optionally
adjust the content by making more changes.
The correcting function will be passed a Pos.Node object
pointing at the matched node, as well as the editor state before
the transaction. Changes it produces will be interpreted as
relative to the document after the transaction (so node.doc).
Corrections are wrappers around transaction extenders. This means that their effect will be
included in transactions as they are applied.
class Correction {
The class representing a correction. Counts as an editor
extension.
Corrections install themselves as transaction extenders that check modified nodes and, if
necessary, apply fixes to enforce constraints. In normal
operation, this means that they guarantee the constraints
implemented in the transaction are enforced.
They do not activate for remote
transactions, because acting on those can cause collaborative
editing setups to malfunction (for example, causing all peers to
repeatedly try to correct the same issue, causing an endless loop
of updates or other chaos). The collab module has special
provisions for integrating corrections in the step transformation
in a safe way, but that requires you to explicitly tell it to use
them, both on the client and
the server.
extension: GardState.Extension
To take effect, corrections must be included in an editor
configuration.
scan(state: GardState): Transaction | null
This method can be used to run a correction agains all matching
nodes in an existing document. If the correction makes any
changes, the method returns a transaction with those changes.
static onChildList(
query: Node.Query,
correct: (node: Pos.Plot) => ChangeSet.Spec | null
): Correction
Create a correction that runs whenever the child list of a node
that matches the given query changes, or such a node is inserted
into the document.
static onContent(
query: Node.Query,
correct: (node: Pos.Plot) => ChangeSet.Spec | null
): Correction
Create a correction that runs whenever any content inside a node
that matches the given query changes, or such a node is inserted
into the document.
static onMarks(
query: Node.Query,
correct: (node: Pos.Node) => ChangeSet.Spec | null
): Correction
Define a correction that runs whenever the set of marks on a
matching tag changes.
static check(
changes: ChangeSet,
doc: Plot.Doc,
corrections: readonly Correction[]
): ChangeSet | null
Check the ranges touched by the given change set against the
given list of corrections. Return a change set if any changes
need to be made. (This isn't how you normally use corrections,
but can be useful in a situation where you aren't working with
an editor state transaction.)
}
This module defines an editor component for use in the browser, on
top of the editing model defined in the other modules. It can be
loaded outside the browser, but won't do anything useful there.
Editor Component
The Wordgard class is responsible for displaying an editor
interface and the document inside of it. It captures basic user
events, such as typing and pointer clicks, and converts them into
editing actions.
class Wordgard {
This class implements the editor's user interface. It wraps the
editable DOM surface and possibly other elements such as panels.
static create(spec: Wordgard.Spec): Wordgard
Construct a new editor. You'll want to either provide a parent
option, or put the editor's DOM element
into your document after creating an editor, so that the user
can see it.
state: GardState
The current editor state.
composing: boolean
Indicates whether the user is currently composing text via
IME, and at least
one change has been made in the current composition.
compositionStarted: boolean | null
Indicates whether the user is currently in composing state. Note
that on some platforms, like Android, this will be the case a
lot, since just putting the cursor on a word starts a
composition there.
editable: boolean
Queries whether the editor's DOM is editable.
focusable: boolean
Returns true if the editor can be focused (is editable or has a tabindex).
root: DocumentOrShadowRoot
The document or shadow root that the editor lives in.
dom: HTMLElement
The outer DOM element that represents the editor.
scrollDOM: HTMLElement
The DOM element that can be styled to scroll. (Note that it may
not have been, so you can't assume this is scrollable.)
contentDOM: HTMLElement
The editable DOM element holding the editor content. You should
not, usually, interact with this content directly though the
DOM, since the editor will immediately undo most of the changes
you make. Instead, dispatch transactions to modify content, and decorations to style it.
dispatch(tr: Transaction.Spec | Transaction)
All editor state updates go through this. It takes a transaction
or transaction spec and updates the editor to show the new state
produced by that transaction. This function is bound to the editor
instance, so it does not have to be called as a method.
Will apply transaction appenders
and include any extra transactions they produce in the editor's
state.
Updates will be immediately be reflected in the object's state
property, but updating the DOM will be deferred to the next
display update.
flush()
Force a flush on the editor content, updating its DOM
representation for any pending changes.
scheduleDOMRead(read: (wg: Wordgard) => void)
Schedule a function that needs to read from the (flushed) DOM.
During an editor update, when doing anything that needs to
access the DOM layout, it is important to schedule it with this
method, to avoid forcing unnecessary DOM layouts.
scheduleDOMWrite(write: (wg: Wordgard) => void)
Schedule a function that needs to modify the DOM. When doing any
kind of DOM mutation that depends on a DOM read, use this method, so that
read and write phases remain separate.
plugin<
T extends Wordgard.Plugin.Value
>(plugin: Wordgard.Plugin<T>): T | null
Get the value of a specific plugin, if present. Note that
plugins that crash can be dropped from an editor, so even when
you know you registered a given plugin, it is recommended to
check the return value of this method.
moveToLineBoundary(
start: GardSelection,
forward: boolean
): GardSelection.Text | null
Find the position at the end or start of the (wrapped) line. If
the given position isn't in a textblock, this will return null.
moveVertically(
start: GardSelection,
forward: boolean,
distance?: number,
allowNode?: boolean
): GardSelection | null
Move a cursor position vertically. When distance isn't given,
it defaults to moving to the vertical element below or above the
start position. Otherwise, distance should provide a positive
distance in pixels.
When start has a
goalColumn, the vertical
motion will use that as a target horizontal position. Otherwise,
the cursor's own horizontal position is used. The returned
cursor will have its goal column set to whichever column was
used. If allowNode is true, this may return a node selection
on a block node.
domAtPos(pos: number, assoc: 1 | -1 = -1): {
node: Node,
offset: number
}
Find the DOM parent node and offset (child offset if node is
an element, character offset when it is a text node) at the
given document position.
nodeDOM(pos: number): Element | null
Get the DOM element for the node at the given position, if any.
posAtDOM(node: Node, offset: number = 0): number
Find the document position at the given DOM node. Can be useful
for associating positions with DOM events. Will raise an error
when node isn't part of the editor content.
nodeFromDOM(node: Element): {pos: number, node: Node} | null
Find the Wordgard node represented by the given DOM node, or one
of its parent nodes, if any. Will not return the outer document node.
posAtCoords(coords: {
x: number,
y: number
}): {pos: number, side: 1 | -1, target: number | null}
Get the document position at the given screen coordinates.
coordsAtPos(pos: number, assoc: 1 | -1 = -1): DOMRect
Get the screen coordinates at the given document position.
side determines whether the coordinates are based on the
element before (-1) or after (1) the position (if no element is
available on the given side, the method will transparently use
another strategy to get reasonable coordinates).
coordsForElement(pos: number): DOMRect | null
Return the rectangle around a given node or character. If there
is no element directly after pos, this will return null. For
space characters that are a line wrap point, this will return
the position before the line break.
hasFocus: boolean
Check whether the editor has focus.
focus()
themeClasses: string
Get the CSS classes for the currently active editor themes.
static scrollIntoView(
pos: number | GardSelection,
options: Wordgard.ScrollSpec = {}
): Transaction.Effect<unknown>
Returns an effect that can be added to a transaction to cause it to scroll the given position
or range into view.
static label(
label: string | PhraseSet.Ref
): GardState.Extension
Add an
aria-label
attribute to the editable element holding the given string or
phrase.
static clipboardOutputFilter: GardState.Facet<(
content: Slice,
state: GardState
) => Slice>
Filter functions provided through this facet will be run on a
slice before it is serialized to the clipboard.
static clipboardOutputHTMLFilter: GardState.Facet<(
html: string,
state: GardState
) => string>
Filter functions provided through this facet will be run on an
HTML string before it put onto the clipboard.
static clipboardTextSerializer: GardState.Facet<(
slice: Slice,
context: readonly Plot.Tag[],
state: GardState
) => string | null>
This can be used to provide a function that converts a document
slice to a string that is put onto the plain-text clipboard.
Serializers are tried in order of precedence until one returns
a string.
static clipboardOutputTextFilter: GardState.Facet<(
html: string,
state: GardState
) => string>
Filter to run on the plain text representation of content put
onto the clipboard.
static clipboardInputFilter: GardState.Facet<(
content: Slice,
state: GardState
) => Slice>
Filter functions provided through this facet will be run on a
slice after it is read from the clipboard.
static clipboardInputHTMLFilter: GardState.Facet<(
html: string,
state: GardState
) => string>
Filter functions to run on HTML text that is read from the
clipboard.
static clipboardTextParser: GardState.Facet<(
text: string,
state: GardState
) => Slice | null>
When the editor reads plain text from the clipboard, this facet
can be used to provide a custom parser. Each provided function
is tried in order of precedence, until one returns a slice.
static clipboardInputTextFilter: GardState.Facet<(
html: string,
state: GardState
) => string>
Filter to run on plain text read from the clipboard.
static pasteHandler: GardState.Facet<(
wg: Wordgard,
event: ClipboardEvent,
slice: Slice,
context: readonly Plot.Tag[]
) => boolean>
Facet that allows you to register handlers to override paste
behavior.
static dropHandler: GardState.Facet<(
wg: Wordgard,
event: DragEvent,
pos: number,
move: {from: number, to: number} | null,
slice: Slice,
context: readonly Plot.Tag[]
) => boolean>
Facet for custom drop handlers. When the drop is done inside the
editor and should move an existing range, the move parameter
will hold the origin range.
static isFocusChange: Transaction.Annotation.Type<boolean>
This annotation is added to transactions created because the
editor's focused status changed. It holds true when the editor
gained focus, false when it lost focus.
static styleModule: GardState.Facet<StyleModule>
static domEventHandler<
Event extends keyof HTMLElementEventMap
>(
event: Event,
handler: (
event: HTMLElementEventMap[Event],
wg: Wordgard
) => boolean | undefined
): GardState.Extension
Returns an extension that can be used to add a DOM event handler
to the editor. For any given event, such functions are ordered
by extension precedence, and the first handler to return true
will be assumed to have handled that event, and no other
handlers or built-in behavior will be activated for it. These
are registered on the content
element, except for scroll handlers, which will be called any
time the editor's scroll element or
one of its parent nodes is scrolled.
static domEventObserver<
Event extends keyof HTMLElementEventMap
>(
event: Event,
observer: (
event: HTMLElementEventMap[Event],
wg: Wordgard
) => void
): GardState.Extension
Create an extension that registers a DOM event observers. Contrary
to event handlers,
observers can't be prevented from running by a higher-precedence
handler returning true. They also don't prevent other handlers
and observers from running when they return true, and should not
call preventDefault.
static scrollHandler: GardState.Facet<(
wg: Wordgard,
target: {from: number, to: number} & Wordgard.ScrollSpec
) => boolean>
Scroll handlers can override how editor content is scrolled into
view. If they return true, no further handling happens for the
scrolling. If they return false, the default scroll behavior is
applied. Scroll handlers should never initiate editor updates.
static exceptionSink: GardState.Facet<(exception: any) => void>
Allows you to provide a function that should be called when the
library catches an exception from an extension (mostly from
plugins, but may be used by other extensions to route exceptions
from user-code-provided callbacks). This is mostly useful for
debugging and logging. See Wordgard.logException.
static transactionListener: GardState.Facet<(
trs: readonly Transaction[],
wg: Wordgard
) => void>
Registers a listener function to be called whenever a set of
transactions is applied to the editor. This function may
dispatch additional transactions, if needed.
static updateListener: GardState.Facet<(
update: Wordgard.Update
) => void>
A facet that can be used to register a function to be called
after the editor flushes updates to the DOM. Dispatching
transactions from such a function is allowed, but will cause a
new, separate update to happen.
static editable: GardState.Facet<boolean, boolean>
Facet that controls whether the editor content DOM is editable.
When its highest-precedence value is false, the element will
not have its contenteditable attribute set. (Note that this
doesn't affect API calls that change the editor content, even
when those are bound to keys or buttons. See the readOnly facet for that.)
A non-editable editor will, by default, not be focusable. You
can set a content attribute
of tabindex: 0 to make an uneditable Wordgard focusable.
static cursorBlinkRate: GardState.Facet<number, number>
Controls the length of a full cursor blink cycle, in milliseconds.
Defaults to 1200. Can be set to 0 to disable blinking.
static mouseSelectionStyle: GardState.Facet<(
wg: Wordgard,
event: MouseEvent
) => Wordgard.MouseSelectionStyle | null>
Allows you to influence the way mouse selection happens. The
functions in this facet will be called for a mousedown event
on the editor, and can return an object that overrides the way a
selection is computed from that mouse click or drag.
static dragMovesSelection: GardState.Facet<(
event: MouseEvent
) => boolean>
Facet used to configure whether a given selection drag event
should move or copy the selection. The given predicate will be
called with the mousedown event, and can return true when
the drag should move the content. The default behavior is to
copy when holding Alt on Mac and Control on other platforms, and
move otherwise.
static theme(spec: Record<
string,
StyleSpec
>): GardState.Extension
Create a theme extension. The first argument can be a
style-mod
style spec providing the styles for the theme. These will be
prefixed with a generated scope class.
Because the selectors are prefixed, rules that directly match
the editor's wrapper element (to which the
scope class will be added) need to be explicitly differentiated
by adding an & to the selector for that element—for example
&:has(wg-content:focus).
static colorScheme: GardState.Facet<
"dark" | "light" | "auto",
"dark" | "light" | "auto"
>
This facet controls whether a dark or light color scheme is
active, which determines whether style rules with a &dark or
&light selector are applied. Defaults to "light". If set to
"auto", the editor uses a CSS prefers-color-scheme: dark
query to determine whether to enable light or dark mode.
Note that setting this to dark will not automatically make the
editor look dark. The default styling does not override the
inherited background and color of the editor. In case of a
page-wide prefers-color-scheme selection, those might already
be dark. But when setting an editor on a light background to
explicitly to use a dark theme, you'll need to make sure you
also load styles for that.
static styles(spec: Record<
string,
StyleSpec
>): GardState.Extension
Create an extension that loads a set of style rules. Like
with theme, use & to indicate the
place of the editor wrapper element when directly targeting
that. You can also use &dark or &light instead to only
target editors with a dark or light color scheme.
static scrolling(height: string | number): GardState.Extension
Creates a simple theme that sets a height (given in pixels or,
if a string, a CSS number + unit) and automatic overflow
scrolling on the editor. (The default styling makes the editor
height fit its content.)
static cspNonce: GardState.Facet<string, string>
Provides a Content Security Policy nonce to use when creating
the style sheets for the editor. Holds the empty string when no
nonce has been provided.
static contentAttributes: GardState.Facet<Record<
string,
string | null
> | ((wg: Wordgard) => Record<string, string | null>)>
Facet that provides additional DOM attributes for the editor's
editable DOM element, either directly, or as a function from the
editor state.
static editorAttributes: GardState.Facet<Record<
string,
string | null
> | ((wg: Wordgard) => Record<string, string | null>)>
Facet that provides DOM attributes for the editor's outer
element.
static announce: Transaction.Effect.Type<string>
State effect used to include screen reader announcements in a
transaction. These will be added to the DOM in a visually hidden
element with aria-live="polite" set, and should be used to
describe effects that are visually obvious but may not be
noticed by screen reader users (such as moving to the next
search match).
static coveredMargins: GardState.Facet<
(wg: Wordgard) => Partial<DOMRect> | null
>
Facet that allows extensions to indicate that some amount of
space around the sides of the scrolling element should be
considered blocked from view when scrolling something into view.
This is only used by plugins that introduce elements that cover
part of the editor (for example a gutter).
}
type Wordgard.ScrollSpec = {
y?: "center" | "start" | "end" | "nearest"
By default ("nearest") the position will be vertically
scrolled only the minimal amount required to move the given
position into view. You can set this to "start" to move it
to the top of the editor, "end" to move it to the bottom, or
"center" to move it to the center.
x?: "center" | "start" | "end" | "nearest"
Effect similar to y, but for the horizontal scroll position.
yMargin?: number
Extra vertical distance to add when moving something into
view. Not used with the "center" strategy. Defaults to 5.
Must be less than the height of the editor.
xMargin?: number
Extra horizontal distance to add. Not used with the "center"
strategy. Defaults to 5. Must be less than the width of the
editor.
}
interface Wordgard.MouseSelectionStyle {
get: (curEvent: MouseEvent, extend: boolean) => GardSelection
Return a new selection for the mouse gesture that starts with
the event that was originally given to the constructor, and ends
with the event passed here. In case of a plain click, those may
both be the mousedown event, in case of a drag gesture, the
latest mousemove event will be passed.
When extend is true, that means the new selection should, if
possible, extend the start selection.
update: (update: Wordgard.Update) => boolean | undefined
Called when the editor is updated while the gesture is in
progress. When the document changes, it may be necessary to map
some data (like the original selection or start position)
through the changes.
This may return true to indicate that the get method should
get queried again after the update, because something in the
update could change its result. Be wary of infinite loops when
using this (where get returns a new selection, which will
trigger update, which schedules another get in response).
}
Wordgard.logException(
state: GardState,
exception: any,
context?: string
): void
Log or report an unhandled exception in client code. Should
probably only be used by extension code that allows client code to
provide functions, and calls those functions in a context where an
exception can't be propagated to calling code in a reasonable way
(for example when in an event handler).
Either calls a handler registered with Wordgard.exceptionSink, window.onerror, if defined, or
console.error (in which case it'll pass context, when given,
as first argument).
class Wordgard.Plugin<V extends Wordgard.Plugin.Value> {
Plugins associate stateful values with an editor. They can be
useful for displaying interface elements, or keeping ephemeral
interface state.
extension: GardState.Extension
Instances of this class act as extensions.
static define<V extends Wordgard.Plugin.Value>(
create: (wg: Wordgard) => V,
provide?: (plugin: Wordgard.Plugin<V>) => GardState.Extension
): Wordgard.Plugin<V>
Define a plugin from a constructor function that creates the
plugin's value, given an editor.
static fromClass<V extends Wordgard.Plugin.Value>(
cls: {new(wg: Wordgard): V},
provide?: (plugin: Wordgard.Plugin<V>) => GardState.Extension
): Wordgard.Plugin<V>
Create a plugin for a class whose constructor takes an editor
as only argument.
eventHandler<Event extends keyof HTMLElementEventMap>(
event: Event,
handler: (
event: HTMLElementEventMap[Event],
wg: Wordgard,
value: V
) => boolean | undefined
): GardState.Extension
Create an event handler for this
plugin. Usually called from the plugin's provide function.
eventObserver<Event extends keyof HTMLElementEventMap>(
event: Event,
observer: (
event: HTMLElementEventMap[Event],
wg: Wordgard,
value: V
) => void
): GardState.Extension
}
interface Wordgard.Plugin.Value {
This is the interface plugin objects must expose.
update(update: Wordgard.Update)
Notifies the plugin of an update that happened in the
editor. This is called before the editor updates its own
DOM. It is responsible for updating the plugin's internal
state (including any state that may be read by plugin
fields) and writing to the DOM for the changes in the
update. To avoid unnecessary layout recomputations, it
should not read the DOM layout—use scheduleDOMRead to schedule your
code in a DOM reading phase if you need to.
docUpdate(wg: Wordgard)
When present, this will be called when an update causes any
changes in the DOM representation of the document.
connect(wg: Wordgard)
Called when the editor is attached to the DOM. If the plugin
needs to allocate any resource that must be released, or modify
something outside the editor, it should do it in this method,
and make sure to release/undo it in its disconnect method.
disconnect(wg: Wordgard)
Called when the editor is removed from the DOM, or the
plugin is removed from the editor.
remove(wg: Wordgard)
Called when the plugin is removed from an editor. This
should clean up any changes it made to the editor itself. If
the editor was connected to a document, disconnect will be called
before this.
}
Key Bindings
Functionality used for binding keys to editing commands.
class KeyBinding {
Key bindings associate keys with functions that should be run when
a matching keyboard event happens.
A key binding can either specify a specific character to match on, which will be
compared against the actual character produced by a key event, or
describe a key combination.
Bindings for a given key event are evaluated in order of
precedence, with each getting a chance to handle the event,
stopping when the first handler returns true.
Key combinations are described by strings like
"Shift-Ctrl-Enter"—a key identifier prefixed with zero or more
modifiers. Key identifiers are based on the strings that can
appear in
KeyEvent.key.
Use lowercase letters to refer to letter keys. You can use
"Space" as an alias for the " " name.
Modifiers can be given in any order. Shift- (or s-), Alt- (or
a-), Ctrl- (or c- or Control-) and Cmd- (or m- or
Meta-) are recognized.
You can use Mod- as a shorthand for Cmd- on Mac and Ctrl- on
other platforms. So Mod-b is Ctrl-b on Linux but Cmd-b on
macOS.
Unlike character bindings, key combination bindings should refer
to the unmodified base key that is being pressed, not the
character produced by combining that key with Shift or AltGraph.
Keyboard mappings that rearrange the positions of Latin characters
are taken into account for this (the mapped position is used),
but the library tries to 'see through' keyboard mappings that
assign non-Latin characters to keys (so that both the Latin and
the non-Latin name can be used).
extension: GardState.Extension
Bindings count as extensions and can be included in an editor
configuration.
spec: KeyBinding.Spec
The configuration object used to define this binding.
static of(spec: KeyBinding.Spec): KeyBinding
}
interface KeyBinding.Spec {
A description of a key binding.
char?: string
A textual character that this binding should trigger for.
key?: string
A key combination to use for this binding. If the
platform-specific property (mac, win, or linux) for the
current platform is used as well in the binding, that one takes
precedence. If key isn't defined and the platform-specific
binding isn't either, a binding is ignored.
mac?: string
Key to use specifically on macOS.
win?: string
Key to use specifically on Windows.
linux?: string
Key to use specifically on Linux.
run: Command.Bound | Command
The command to execute when this binding is triggered.
shift?: Command.Bound | Command
When given, this defines a second binding, using the (possibly
platform-specific) key name, prefixed with Shift-, to activate
this command.
any?: (wg: Wordgard, event: KeyboardEvent) => boolean
When this property is present, the function is called for every
key, and may return true to indicate the key was handled.
scope?: string
By default, key bindings apply when focus is on the editor
content (the "editor" scope). Some extensions, mostly those
that define their own panels, might want to allow registering
bindings local to that panel. Such bindings should use a custom
scope name. You may also assign multiple scope names to a
binding, separating them by spaces.
allowDefault?: boolean
By default, all keys events for which a handler exists have
their preventDefault called, even if no handler returns
true. You can set this to true to disable that behavior.
}
KeyBinding.runScopeHandlers(
wg: Wordgard,
event: KeyboardEvent,
scope: string
): boolean
Run the key handlers registered for a given scope. The event
object should be a "keydown" event. Returns true if any of the
handlers handled it.
KeyBinding.source: GardState.Facet<KeyBinding>
Facet used for registering key bindings. Extension precedence
determines the order in which bindings that match the same key
are called. When a handler has returned true for a given key,
no further handlers are called.
KeyBinding.useDefaultKeymap: GardState.Facet<boolean, boolean>
By default, the default keymap
is automatically active. You can configure this to false if you
want to completely replace it.
KeyBinding.defaultKeymap: readonly KeyBinding[]
The editor's set of default key bindings. Binds the following
keys. Most cursor motion bindings include a Shift- variant
that passes the extend flag to the command. Enabled by default
unless KeyBinding.useDefaultKeymap is disabled.
On MacOS, the following Emacs-style bindings are available:
Decoration
Decorations provide a way to influence the way the document is
displayed without changing the document itself, via editor
extensions. They are useful for showing out-of-band information or
editing controls directly in the editable content.
type Decoration.Shape = Widget | Elt<string | Widget>
Node shapes can be either a widget or an element which may
contain widgets.
Decoration.Tag.shape<T extends Node.Type.Ref<any>>(
type: T,
shape: Decoration.Shape |
((tag: Node.Tag.For<T>) => Decoration.Shape),
config?: {atom?: boolean}
): GardState.Extension
Override the way a given node type is drawn in the editor. By
default, the shape field in the
type's definition will be used, but extensions created with
this function can provide an alternative shape for a given
type.
When providing a function for the shape, keep in mind that the
result will be cached by tag, and you should make sure your
function is pure.
When providing a function that returns a shape that changes
whether the node is rendered as an atom, you need to provide
the atom.
Decoration.Tag.shape.dynamic<T extends Node.Type<any>>(
type: T,
shape: (state: GardState) => Decoration.Shape |
((tag: Node.Tag.For<T>) => Decoration.Shape),
config?: {atom?: boolean}
): GardState.Extension
This function allows you to define a custom node shape that depends on the editor state. It will
automatically track what slots (see compute) you use, and make sure the nodes
are redrawn when those change.
If your shape function returns a function from a tag, you
must be careful do any state access you need in the outer
function, not the returned function, or it won't be tracked.
You generally don't want to make your shapes depend on
constantly-changing slots like the document or selection,
because when the document is big, there's a non-trivial
amount of work involved when a node shape changes (or may
have changed).
When providing a shape for a plot that changes whether it is
rendered as an atom, provide the atom option.
Decoration.Tag.wrapper(
type: Node.Type.Ref<any>,
wrapper: Elt<string | Widget>,
options?: {target?: string}
): GardState.Extension
Define a wrapper to be added around a given node type, or some
part of it. The given elt should include a hole (0) to
indicate where the original shape goes.
If a target option is given, and matching some element in
the node's existing shape, only that element will be wrapped.
Uses a subset of CSS selectors that supports only tag name and
class names (img.x.y).
Decoration.Tag.widget<T extends Node.Type.Ref<any>>(
type: T,
place: "before" | "after" | "start" | "end",
widget: Widget | ((tag: Node.Tag.For<T>) => Widget)
): GardState.Extension
Add a widget to every instance of the given node type. Such
widgets can appear before or after the node, and for plots
that aren't rendered as atoms, at its start or end.
When a function, widget will be cached by tag, and should be
pure.
Decoration.Tag.widget.dynamic<T extends Node.Type.Ref<any>>(
type: T,
place: "before" | "after" | "start" | "end",
widget: (state: GardState) => Widget |
((tag: Node.Tag.For<T>) => Widget)
): GardState.Extension
Decoration.Tag.attribute<T extends Node.Type.Ref<any>>(
type: T,
attr: string,
value: string | ((tag: Node.Tag.For<T>) => string),
options?: {target?: string}
): GardState.Extension
Add an attribute to the representation of a given node type.
By default, the attribute is added to the outer element (or a
wrapper element if the node is rendered as a widget). If the
target option is given, and
matches an
element in the representation, it will be added to that
element instead.
abstract class Decoration.Point implements PointSet.Value {
A point decoration is a decoration that targets a given position
in the document, or the node after a given position. Sets of
point decorations can be provided as point sets through Decoration.Point.source.
static widget(widget: Widget, options?: {
side?: number
Determines where this widget appears relative to the cursor
(negative means before, positive after, zero means to make
it depend on the cursor's own side) and other widgets in the
same position. Defaults to zero.
trackMode?: ChangeSet.TrackMode
What side to track when changes happen around the widget.
The default is to keep the widget around unless the content
on both sides is deleted. You can pass undefined to indicate
the widget should not be deleted by changes, or
"before"/"after" to use one specific side.
}): Decoration.Point
Display a widget at this point.
static attributes(
attrs: Record<string, string>,
options?: {target?: string}
): Decoration.Point
Add a set of attributes to the node after this decoration's
position.
You can target a specific
element in the
node's representation with the target option.
static shape(shape: Decoration.Shape): Decoration.Point
Override the shape of the node after the decoration's point
with the given one.
static wrapper(
wrapper: Elt<string | Widget>,
spec?: {target?: string}
): Decoration.Point
Wrap the node, or inner node selected with target, at the
given position with a wrapper.
static source: GardState.Facet<(
state: GardState
) => PointSet<Decoration.Point>>
The facet used to register a point decoration source.
Functions provided in this way will be called on every editor
update, so computing the set on the fly will only perform well
for very simple decoration sets, and you'll usually want to
keep your set in a state field and update it incrementally.
}
interface Decoration.Range.Spec {
Configuration object for range decorations.
inclusive?: boolean | "start" | "end"
Determines whether content inserted next to the range is
included when mapping the range through a change. Defaults
to false.
query?: Node.Query
If given, apply this decoration only to matching nodes.
scope?: "atom" | "inlineatom" | "all"
The type of nodes in the range to apply the decoration to.
Defaults to "atom".
}
interface Decoration.Range.WrapperSpec
extends Decoration.Range.Spec {
Configuration object for wrapper range decorations.
attributes?: Record<string, string>
Attributes to add to the wrapper element.
rank?: number
A wrapper's rank determines the nesting order between it and
other wrappers created by range decorations or marks. Should be
a number between 0 and 100, if given.
spanning?: boolean
Whether this wrapper may span multiple sibling nodes.
Non-spanning wrappers will be created separately for each
node. Defaults to true.
}
Panels
Panels are interface elements displayed above or below the editor.
They use sticky positioning to stay in view even when the editor
is partially scrolled out of view. They are a good way to display
things like dialogs, status bars, and menu bars.
interface Panel {
Object that describes an active panel.
dom: HTMLElement
The element representing this panel. The library will add the
"wg-panel" DOM class to this.
top?: boolean
Controls whether the panel should be at the top or bottom of the
editor. Defaults to false.
update(update: Wordgard.Update)
Update the panel DOM for a given editor update.
connect(wg: Wordgard)
Called, when present, when the panel has been added the DOM.
disconnect(wg: Wordgard)
Called when the editor with the panel is disconnected from the
DOM, or the panel is removed from an editor.
remove(wg: Wordgard)
Called when the panel is removed from the editor.
}
type Panel.Constructor = (wg: Wordgard) => Panel
A function that initializes a panel. Used in Panel.show.
Panel.show: GardState.Facet<Panel.Constructor | null>
Opening a panel is done by providing a constructor function for
the panel through this facet. (The panel is closed again when its
constructor is no longer provided.) Values of null are ignored.
Panel.get<T extends Panel>(
wg: Wordgard,
constructor: (wg: Wordgard) => T
): T | null
Get the active panel created by the given constructor, if any.
This can be useful when you need access to your panels' DOM
structure.
Panel.configure(config?: {
topContainer?: HTMLElement
By default, panels will be placed inside the editor's DOM
structure. You can use this option to override where panels with
top: true are placed.
bottomContainer?: HTMLElement
Override where panels with top: false are placed.
}): GardState.Extension
Configures the panel-managing extension.
(: {
?: Menu.Template | readonly Menu.Template[]
} = {}): GardState.Extension
Provides a menu bar that displays menu items defined via the
menu system in a button bar at the top of the
editor. The same menu items can be used by custom menu
implementations, but this extension provides a solid default menu
style.
interface Dialog {
Dialogs are panels opened as a side-effect, and
closed by user action. This interface is used to describe them.
content?: (wg: Wordgard, close: () => void) => Element
A function to render the content of the dialog. The result
should contain at least one <form> element. Submit handlers
and a handler for the Escape key will be added to the form.
If this is not given, the label, input, and submitLabel
fields will be used to create a simple form for you.
label?: string
When content isn't given, this provides the text shown in the
dialog.
input?: {[string]: string}
The attributes for an input element shown next to the label. If
not given, no input element is added.
submitLabel?: string
The label for the button that submits the form. Defaults to
"OK".
class?: string
Extra classes to add to the panel.
focus?: boolean | string
A query selector to find the field that should be focused when
the dialog is opened. When set to true, this picks the first
<input> or <button> element in the form. When set to
false, focus is not moved into the dialog.
top?: boolean
By default, dialogs are shown above the editor. Set this to
false to have it show up at the bottom.
}
Dialog.show(wg: Wordgard, config: Dialog): {
close: Transaction.Effect<unknown>,
result: Promise<HTMLFormElement | null>
}
Show a dialog to display a message or prompt the user for input.
Returns an effect that can be dispatched to close the dialog,
and a promise that resolves when the dialog is closed or a form
inside of it is submitted.
You are encouraged, if your handling of the result of the promise
dispatches a transaction, to include the close effect in it. If
you don't, this function will automatically dispatch a separate
transaction right after.
Dialog.get(wg: Wordgard, className: string): Panel | null
Find the Panel for an open dialog, using a class name as
identifier.
Dialog.close(wg: Wordgard, className: string): boolean
Close the Panel for an open dialog, by class name.
Tooltips are small elements displayed over the editor near a
specific document position.
interface Tooltip {
Describes a tooltip. Values of this type, when provided through
the Tooltip.show facet, provide the active tooltips on an
editor.
pos: number
The document position at which to show the tooltip.
end?: number
The end of the range annotated by this tooltip, if different
from pos.
create(wg: Wordgard): Tooltip.View
above?: boolean
Whether the tooltip should be shown above or below the target
position. Not guaranteed to be respected for hover tooltips
since all hover tooltips for the same range are always
positioned together. Defaults to false.
strictSide?: boolean
Whether the above option should be honored when there isn't
enough space on that side to show the tooltip inside the
viewport. Defaults to false.
arrow?: boolean
When set to true, show a triangle connecting the tooltip element
to position pos.
clip?: boolean
By default, tooltips are hidden when their position is outside
of the visible editor content. Set this to false to turn that
off.
}
Tooltip.configure(config: {
position?: "fixed" | "absolute"
By default, tooltips use "fixed"
positioning,
which has the advantage that tooltips don't get cut off by
scrollable parent elements. However, CSS rules like contain: layout can break fixed positioning in child nodes, which can be
worked about by using "absolute" here.
On iOS, which at the time of writing still doesn't properly
support fixed positioning, the library always uses absolute
positioning.
If the tooltip parent element sits in a transformed element, the
library also falls back to absolute positioning.
parent?: HTMLElement
The element to put the tooltips into. By default, they are put
in the editor (<wordgard-editor>) element, and that is
usually what you want. But in some layouts that can lead to
positioning issues, and you need to use a different parent to
work around those.
tooltipSpace?: (wg: Wordgard) => DOMRect
By default, when figuring out whether there is room for a
tooltip at a given position, the extension considers the entire
space between 0,0 and
documentElement.clientWidth/clientHeight to be available for
showing tooltips. You can provide a function here that returns
an alternative rectangle.
} = {}): GardState.Extension
Creates an extension that configures tooltip behavior.
interface Tooltip.View {
Describes the way a tooltip is displayed.
dom: HTMLElement
The DOM element to position over the editor.
offset?: {x: number, y: number}
Adjust the position of the tooltip relative to its anchor
position. A positive x value will move the tooltip
horizontally along with the text direction (so right in
left-to-right context, left in right-to-left). A positive y
will move the tooltip up when it is above its anchor, and down
otherwise.
getCoords?: (pos: number) => DOMRect
By default, a tooltip's screen position will be based on the
document position of its pos property. This method can be
provided to make the tooltip view itself responsible for finding
its screen position.
overlap?: boolean
By default, tooltips are moved when they overlap with other
tooltips. Set this to true to disable that behavior for this
tooltip.
update(update: Wordgard.Update)
Update the DOM element for a change in the view's state.
connect(wg: Wordgard)
Called when the tooltip is added to a DOM-connected editor.
disconnect(wg: Wordgard)
Called when the editor containing the tooltip is disconnected,
or before the tooltip is removed.
remove(wg: Wordgard)
Called when the tooltip is removed from the editor.
positioned(space: DOMRect)
Called when the tooltip has been (re)positioned. The argument
is the space
available to the tooltip.
resize?: boolean
By default, the library will restrict the size of tooltips so
that they don't stick out of the available space. Set this to
false to disable that.
}
Tooltip.show: GardState.Facet<Tooltip | null>
Facet to which an extension can add a value to show a tooltip.
Tooltip.get<T extends Tooltip>(
wg: Wordgard,
tooltip: T
): ReturnType<T["create"]> | null
Tooltip.get<T extends Tooltip.View>(
wg: Wordgard,
create: (wg: Wordgard) => T
): T | null
Get the active tooltip view for a given tooltip or tooltip
constructor, if available.
Tooltip.reposition(wg: Wordgard): void
Tell the tooltip extension to recompute the position of the active
tooltips. This can be useful when something happens (such as a
re-positioning or CSS change affecting the editor) that could
invalidate the existing tooltip positions but isn't detected by
the extension.
Tooltip.hover(
source: (
wg: Wordgard,
pos: number,
side: 1 | -1
) => Tooltip | readonly Tooltip[] | Promise<
Tooltip | readonly Tooltip[] | null
> | null,
options: Tooltip.hover.Spec = {}
): {
extension: GardState.Extension,
active: GardState.Field<readonly Tooltip[]>
}
Set up a hover tooltip, which shows up when the pointer hovers
over ranges of text. The callback is called when the mouse hovers
over the document text. It should, if there is a tooltip
associated with position pos, return the tooltip description
(either directly or in a promise). The side argument indicates
on which side of the position the pointer is—it will be -1 if the
pointer is before the position, 1 if after the position.
Note that all hover tooltips are hosted within a single tooltip
container element. This allows multiple tooltips over the same
range to be "merged" together without overlapping.
Returns an editor extension that
installs the hover behavior and a state field that can be used
to read the currently active tooltips produced by this
extension.
Tooltip.hover.has(state: GardState): boolean
Returns true if any hover tooltips are currently active.
Tooltip.hover.closeAll: Transaction.Effect<null>
Transaction effect that closes all hover tooltips.
Utilities
Input rules are a way to respond to certain patterns of text input
with an action, such as replacing sequences of dashes with emdash
characters or automatically creating lists when a textblock is
started with a number and a period.
interface InputRule.Spec {
expr: RegExp
The regular expression to match against the text before the
input. This expression should end in a $ marker.
apply: string | ((
state: GardState,
match: InputRule.MatchArray
) => Transaction.Spec | null)
Handler to call when this rule matches. match will contain
the document positions of the full match and all matched
groups in expr. Should return true when it has taken an
action, false when it didn't. You probably want to include
history.isolate.of(true) in any
transactions you dispatch from a rule handler, so that users
can undo the adjustment if it wasn't what they wanted.
When given as a string, the full match will be replaced by
that string.
lookahead?: RegExp
Because the regular expression given in expr must end at the
cursor, it is matched against a string that stops at the
cursor, and cannot look beyond it. You can provide an
additional expression here (which should start with ^) to
enforce a lookahead condition.
inCode?: boolean
By default, input rules don't apply inside nodes with the
Code role. Set this to true to
allow matches in code.
}
type InputRule.Match = {from: Pos, to: Pos, text: string}
An object representing a matched group for an input rule. Holds
the start and end positions of the group in the document, along
with the matched text content.
type InputRule.MatchArray = readonly (InputRule.Match | null)[] &
{0: InputRule.Match}
InputRule.emDash: InputRule
Input rule that converts double dashes to an emdash.
InputRule.ellipsis: InputRule
Rule that converts three dots to an ellipsis character.
InputRule.openDoubleQuote: InputRule
“Smart” opening double quotes.
InputRule.closeDoubleQuote: InputRule
“Smart” closing double quotes.
InputRule.openSingleQuote: InputRule
‘Smart’ opening single quotes.
InputRule.closeSingleQuote: InputRule
‘Smart’ closing single quotes.
InputRule.smartQuotes: readonly InputRule[]
Smart-quote related input rules.
placeholder(content: string |
(() => Element)): GardState.Extension
Extension that enables a placeholder—a piece of example content
to show when the editor is empty.
dropCursor(): GardState.Extension
Draws a cursor at the current drop position when something is
being dragged over the editor.
This module exports abstractions to represent editor commands,
which represent user actions, as well as a collection of commands
and supporting functions.
Command Abstraction
Commands functions are used by things like key bindings, menus,
and event handlers to dispatch a specific type of user action.
They have a parameter type (which may be null and ignored).
type Command<Param = null> = (
target: Wordgard,
param: Param
) => boolean | Transaction.Spec
A command is a function that takes an editor and an additional
parameter, and either...
returns false to indicate that it does not apply to the
current editor state
performs its action as a side effect and returns true
returns a transaction spec that
should be dispatched as its effect
This formulation is chosen to cover both side-effecting commands
(whose effect may not even directly affect the editor—a command
may just open a dialog or change some editor-external state) and
commands implemented as pure functions from
state to transaction.
Extensions can register additional handlers for a command, which
will be called in order of precedence (until one returns true)
when the command is dispatched.
Commands are recognized by function identity. So, for example, the
enter command is both the tag used to indicate invocation of an
enter press and the function that implements the default behavior
for this action.
type Command.Pure<Param = null> = (
target: {state: GardState},
param: Param
) => false | Transaction.Spec
Command.Pure is a subtype of Command that relies only on the
editor state, and performs no imperative effects. When
implementing such a command function, it can be useful to tag it
with this type, so that it can be invoked without a full editor
component for testing or for use in a context where there is no
editor.
Note that invoking a command function directly will not activate
custom handlers.
Command.handler<Param>(
command: Command<Param>,
handler: Command<Param>
): GardState.Extension
Create an extension that adds a handler for the given command.
Command.bind<Param>(
command: Command<Param>,
param: Param
): Command.Bound
Bind a command with a parameter. The only thing you can do with
a bound command is to dispatch it.
type Command.Bound = {tag: symbol}
Opaque type used for bound commands.
Command.dispatch(
wg: Wordgard,
command: Command.Bound | Command
): boolean
Command.dispatch<Param>(
wg: Wordgard,
command: Command<Param>,
param: Param
): boolean
Apply a command to the given editor view. When passing a
non-bound command with a parameter, the
parameter has to be passed as second argument.
A set of abstractions used to define generic menu items and
structure. This does not include an actual implementation of a
menu component. For that, see menuBar.
type Menu.Item = Menu.Group | Menu.Submenu | Menu.Button |
Menu.CustomControl
Editor menus are structured as trees, with item groups and
submenus as internal nodes, and buttons and custom controls as
leaf nodes.
interface Menu.Item.Spec {
Generic configuration fields supported by all menu items.
select?: (state: GardState) => boolean
When given and returning false, this item should be hidden
from the menu. Should be used sparingly, to avoid the menu
constantly flickering and changing size as the user is
editing.
enable?: (state: GardState) => boolean
When given and returning false, this item is disabled, which
means it looks faded and cannot be interacted with.
updateFor?: (tr: Transaction) => boolean
By default, state predicates (select, enable, and active)
are re-checked whenever the document or selection changes. If an
item is sensitive to other aspects of the state, provide a test
here that returns true for transactions that might affect the
item state.
parent?: Menu.Group | Menu.Submenu
The item's parent. See Menu.resolve for information
on how menus are linked up.
rank?: number
Determines the order of elements in the parent. Should be a
number between 0 and 100. Defaults to 100.
description?: string | PhraseSet.Ref
A description to associate with the item, used for hover
tooltips and screen-reader text. If the item has a textual
label, this will default to that label when not given.
}
Menu.Item.source: GardState.Facet<Menu.Item>
The facet used to add menu items to a configuration. Used by
the items' extensions to register them, and by menu
implementations to find available items.
type Menu.Item.Resolved = Menu.Button | Menu.CustomControl |
"|" | Menu.Submenu.Resolved
A resolved menu consists of buttons, custom controls,
submenus, and spacers, which are represented by the string
literal "|".
type Menu.Label = string | PhraseSet.Ref | {
icon: string,
directional?: boolean
}
Labels are used by buttons and submenus to determine what they
look like. They may either be textual (a string or reference to
a phrase), or an icon, which is expressed an SVG path string
that draws the icon inside a 100-by-100 space. The directional
flag indicates that the icon should be mirrored vertically in a
right-to-left editor.
Menu.Button.toggleMark(config: {
mark: Mark,
parent?: Menu.Group | Menu.Submenu,
rank?: number,
description?: PhraseSet.Ref,
label: Menu.Label
}): Menu.Button
Creates a menu button that toggles an inline mark via Menu.Button.toggleMark, and is shown as active when either
that mark is part of the marks associated with the current
cursor, or the selection covers only content with that mark.
interface Menu.CustomControl.Spec extends Menu.Item.Spec {
render: (wg: Wordgard, done: () => void) => {
dom: HTMLElement,
focus?: HTMLElement
}
The function that renders the actual control. The dom
property on the returned object will be displayed in the
menu. If focus is provided, that is used as the element to
put focus on. If not, dom is used.
The control should call the done function when it decides
it is closed or activated, so that any submenu above it
knows to close, and focus can be moved back to the editor if
appropriate.
setEnabled?: (focus: Element, enabled: boolean) => void
If the control supports disabling, this function will be called when the enabled
state changes, and should update the control to show this.
}
interface Menu.Group.Spec {
Options used to configure a menu group.
margin?: boolean
When set to true, leave a bit of space between this group
and adjacent items.
parent?: Menu.Group | Menu.Submenu
The group's parent item, if any.
rank?: number
The group's rank within its parent.
content?: readonly (Menu.Item | "...")[]
Default content for this group. Usually you don't need this,
as you let parent links from the content items determine
what goes in the group. See the menu
resolution system.
overflow?: {at: number, wrap?: Menu.Submenu}
If given when, during resolution, the group contains more
than at items, wrap items at - 1 and up in a submenu.
You may optionally provide submenu object to specify the
look of the submenu, or let it default to showing three
vertical dots.
}
Menu.Group.top: Menu.Group
The top-level menu. When you don't provide a custom menu
template, this is the starting point from which the menu will
be resolved. Parent of most other groups.
Menu.Group.commands: Menu.Group
Editing commands. Holds items like the history undo/redo
buttons.
Menu.Group.inline: Menu.Group
Inline style items. Will, by default, contain buttons to
create emphasized text, links, and so on.
Menu.Group.block: Menu.Group
Group for block-related items. Holds things like list toggles
and text alignment.
Menu.Group.insert: Menu.Group
Group for inserting elements into the document, such as images
or tables.
interface Menu.Submenu.Spec extends Menu.Item.Spec {
The options that can be passed to a submenu.
label?: Menu.Label
The label to show for the submenu. When not given, the
submenu will look for the first active item in its children, and
use that child's label, or fall back to defaultLabel.
defaultLabel?: Menu.Label
Fallback label when no regular label is given and there are
no active children.
arrow?: boolean
Whether to show an arrow on the submenu button to indicate
that it can be expanded. Defaults to true.
width?: number
A base with for the submenu button, in CSS ch units. Can
be useful when the menu uses a dynamic textual label, and
you want to prevent it from changing size as its label
changes.
content?: readonly (Menu.Item | "...")[]
}
Menu.Submenu.textblockStyle: Menu.Submenu
The submenu to select textblock type. Used to switch between,
for example, regular paragraphs and headings
Menu.resolve(
items: readonly Menu.Item[],
template: Menu.Template | readonly Menu.Template[] =
Group.top.template(),
suppress?: readonly Menu.Item[]
): readonly Menu.Item.Resolved[]
Given a set of menu items and optionally a template, this
function will resolve a concrete menu tree. To do this, it goes
through the template (which defaults to just the top group), filling in open spaces (represented
as the string literal "...") with any items provided that have
the group or submenu as parent.
The idea is to combine a top-down (the template) and bottom-up
(the items, which typically come from an editor configuration) in a way that allows the user
to figure out a balance between manually specifying their menu
and just using whatever is in the configuration.
Items that are used explicitly in a template will not be used
again implicitly. Items included in the suppress parameter
will be ignored.
When a submenu or group specifies default content, this will
only be used when the template does not specify its own content
for the item.
Basic Editing Commands
Commands for fundamental editing actions.
insertText: Command.Pure<{
from: number,
to: number,
insert: string,
userEvent: string
}>
This command handles text input. To selectively override the
behavior of text input, provide a handler that, when the
conditions that it requires apply, handles the input and returns
true. userEvent will generally be one of "input.type",
"input.type.compose" (text inserted as part as a composition),
or "input.type.compose.start" (initial text created by a started
composition).
insertLineBreak: Command.Pure
Command to insert a line break. The default handler will, if the
schema defines a line break node and
the selection's parent node allows that, insert such a node.
Otherwise, in nodes marked as
whitespace-preserving, this
will insert a line break.
enter: Command.Pure
The command that handles enter presses. The default handler will,
if the selection is not in an inline context, insert an empty
default textblock in its position. Otherwise it first tries
liftEmptyTextblock, then splitTextblock.
transposeChars: Command.Pure
Swap the characters before and after the cursor.
undo: Command
Undo an edit. Does not have a default handler, but a handler is
added by the history extension.
redo: Command
Redo an edit. Does not have a default handler.
Selection Commands
Commands that move the selection. Most take an extend flag that
controls whether they move or extend the selection.
moveByUnit: Command.Pure<{
dir: "left" | "right" | "forward" | "backward",
extend?: boolean
}>
Move the selection head one unit (text cluster, atomic node, or
node boundary) in the indicated direction. When extend is true,
keep the selection anchor in place. The default handler moves
visually through bidirectional text, so when going left, the
motion will go back in left-to-right text, and
forward in right-to-left text.
moveByWord: Command.Pure<{
dir: "left" | "right",
extend?: boolean
}>
Move the selection head one word in the indicated direction. Keep
the anchor in place if extend is true. The default handler moves
visually.
moveByLine: Command<{dir: "up" | "down", extend?: boolean}>
Move the selection head one line up or down. When extend is
true, keep the anchor in place.
moveByPage: Command<{dir: "up" | "down", extend?: boolean}>
Move the selection head one page up or down. Extend the selection
when the extend flag is true.
moveToLineSide: Command<{
dir: "left" | "right" | "forward" | "backward",
extend?: boolean
}>
Move to the indicated side of the current line. Will stop at line
wrap points. "left" and "right" will be interpreted based on
the editor's text direction.
moveToTextblockSide: Command<{
dir: "left" | "right" | "forward" | "backward",
extend?: boolean
}>
Move to the start or end of the textblock that has the selection
head.
moveToDocSide: Command.Pure<{
side: "start" | "end",
extend?: boolean
}>
Move to the start or end of the document.
selectAll: Command.Pure
Select the entire document.
Deletion Commands
Commands that delete content.
deleteUnit: Command.Pure<"forward" | "backward">
Delete the selection, or the unit after or before the selection.
If that unit is the start or end of a textblock, this will try to
join that textblock to the next one. Otherwise, if it is a
character or leaf node, that is deleted. If none of that is
possible and the cursor is in an empty textblock, this will delete
the textblock.
When deleting backward at the start of a list item that has a
sibling before it, this command will try to join those list items.
deleteWord: Command.Pure<"forward" | "backward">
Delete the selection, or the word next to it. Will behave like
deleteUnit, except that, when deleting text, it will
delete an entire word.
deleteToLineEnd: Command<"forward" | "backward">
Delete to the end or start of the line. Stops at line wrapping
points.
deleteLine: Command
Delete the selection, or if that is empty, the line around the
cursor.
Block Manipulation Commands
Commands that act on the document's block structure.
setTextblockType: Command.Pure<Plot.Tag>
Set the type of the textblock(s) around the selection to the given
tag.
unwrapBlock: Command.Pure<Node.Query | null>
Try to unwrap blocks around the selection. The second argument, if
given, indicates what kind of wrapping plots may be removed.
Returns null when no unwrapping is possible.
wrapBlock: Command.Pure<Plot.Tag>
Try to wrap selected textblocks in the given wrapper. Will return
null if no wrapping is possible.
toggleBlock: Command.Pure<Plot.Tag>
If the selection is in a block of the given type, unwap it.
Otherwise, try to wrap the selected blocks in such a tag.
setAlignment: Command.Pure<
"center" | "start" | "end" | "left" | "right" | null
>
Set the selected textblocks to the given alignment. "left" and
"right" will be normalized to "start" or "end" depending on
the editor's text direction. The default implementation uses the
Alignment mark.
setDirection: Command.Pure<"auto" | "ltr" | "rtl" | null>
Set the text direction for the selected textblocks. null will
remove an explicit direction mark, defaulting the blocks back to
the editor's base direction. The default implementation uses the
Direction mark.
toggleList: Command.Pure<Plot.Tag>
Toggle list wrapping with the given list tag for the selected
blocks.
Inline Mark Commands
Commands that toggle inline marks.
toggleMark: Command.Pure<Mark>
Toggle the given mark. If there is no selection, it is added to
the cursor's active marks, or removed if it is already in there.
Otherwise, if any selected content allows for the mark to be
added, it is added. If not, remove the mark from the selection.
toggleEmphasis: Command.Pure
Toggle emphasis. The default implementation uses the Emphasis mark.
toggleStrong: Command.Pure
Toggle strong emphasis. The default implementation uses the Strong mark.
toggleUnderline: Command.Pure
Toggle underlining. The default implementation uses the Underline mark.
Utility Functions
Functions that may be useful when implementing your own commands.
listIsActive: (
listTag: Plot.Tag
) => (state: GardState) => boolean
Returns true when all selected textblocks are wrapped in a list of
the given type.
liftEmptyBlock(state: GardState): false | Transaction.Spec
If the cursor is in an empty textblock that can be lifted out of a
parent, return a transaction that does this.
splitTextblock(
state: GardState,
splitListItem: boolean = true
): false | Transaction.Spec
Split the textblock at the cursor position, if any. If the
textblock is the first child of a list item, also split that item,
unless splitListItem is false.
deleteSelection(state: GardState): false | Transaction.Spec
Returns a transaction that deletes the selection, or false if the
selection is empty.
deleteBackward(
state: GardState,
word: boolean = false
): false | Transaction.Spec
Create a transaction that deletes the atomic node or text
cluster/word in front of the cursor, if possible.
deleteForward(
state: GardState,
word: boolean = false
): false | Transaction.Spec
Return a transaction that deletes the element (text cluster or
leaf node) after the cursor, if any.
deleteEmptyTextblock(
state: GardState,
dir: 1 | -1 = -1
): false | Transaction.Spec
If the cursor is inside an empty textblock, return a transaction
that deletes the entire block. dir determines which way the
cursor moves after the deletion.
joinBackward(state: GardState): false | Transaction.Spec
If the cursor is at the start of a textblock that can be joined to
a textblock before it, return a transaction to performs this join.
joinListItems(state: GardState): false | Transaction.Spec
If the cursor is at the start of a list item that has another item
before it, return a transaction that joins those two items.
joinForward(state: GardState): false | Transaction.Spec
If the cursor is at the end of a textblock that can be joined to
the textblock after it, return a transaction that performs this
join.
joinBlocks(before: Pos.Plot, after: Pos.Plot): ChangeSet.Spec
Join two adjacent (only separated by first a sequence of block end
tokens and then a sequence of block open tokens) block plots.
selectedTextblocks(state: GardState): Pos.Plot[]
Get an array of all textblocks that contain part of the selection.
clearNonFitting(
schema: Schema,
node: Pos.Plot,
type: Plot.Type
): ChangeSet.Spec
Remove all content in node that is not allowed to appear in
type.
findWrappable(
from: Pos,
to: Pos,
wrapper: Plot.Tag
): {from: Pos, to: Pos} | null
Find a way to wrap the blocks betwen from and to in a node
with the given tag. Returns precise start and end positions where
a wrap is possible, or null if none is possible.
wrapBlockRange(
range: {from: Pos, to: Pos},
wrapper: Plot.Tag
): ChangeSet.Spec[]
Wrap the given range in the given wrapper tag. The caller is
responsible for verifying that this is actually a valid wrapping.
It is recommended to use findWrappable for finding wrap
positions in non-trivial situations.
findUnwrappable(
schema: Schema,
from: Pos,
to: Pos,
query?: Node.Query
): Pos.Plot[] | null
Find the set of block nodes around the given range that match the
predicate (if any) and can be unwrapped, meaning their content
gets moved out to a parent node.
doUnwrapBlock(
block: Pos.Plot,
from?: number,
to?: number
): ChangeSet.Spec
Unwrap the given block node, or the node's children between from
and to.
canAddMarkInRange(
doc: Plot.Doc,
from: number,
to: number,
mark: Mark.Type | Mark
): boolean
Query whether the given range has any content to which the given
mark or mark type could be added.
autoJoinBlocks(
state: GardState,
tr: Transaction.Spec
): Transaction.Spec
Post-process the given transaction spec to check for any block
boundaries touched by the changes in it that can be auto-joined. If any are found, the spec is
updated to perform those joins.
This module provides a collection of basic schema elements. It is
possible to define your own custom elements, but often convenient
to use the ones provided here, which come with support extensions
in the wordgard/schema module.
This module depends on only on wordgard/doc and is kept
separate from wordgard/schema so that you can access
the types without loading the editor code, when appropriate.
Block Structure
Nodes and marks for structuring documents on the block level.
Paragraph: Plot.Tag<null>
A paragraph. Part of the Content
group, allowing inline content, rendered as <p>.
Heading: Plot.Type<number>
Heading plot. Its parameter indicates the heading level. In the
Content group, allows inline
content, rendered as <h1> to <h6>.
CodeBlock: Plot.Tag<null>
A code block. Part of the Content
group, with the Code role. Rendered
as <pre>.
CodeBlockLanguage: Mark.Type<string>
A mark that assigns a language identifier (any string) to a code
block. Stored in the data-language attribute.
Blockquote: Plot.Tag<null>
Blockquote plot. Allows any Content
blocks inside of it, and is itself part of that group. Rendered as
<blockquote>.
ListItem: Plot.Tag<null>
Block list item. Allows any Content
blocks as content. Rendered as <li>.
InlineListItem: Plot.Tag<null>
List item with inline content. You'll want to have either this
plot type or ListItem in your schema, not both. Rendered
as <li>.
OrderedList: Plot.Type<number>
Ordered list plot. Part of the Content group, allows either ListItem or InlineListItem as content. Rendered as <ol>, with the parameter
providing the start attribute.
BulletList: Plot.Tag<null>
HorizontalRule: Leaf<null>
A horizontal separator. Part of the Content group. Renders as <hr>.
Alignment: Mark.Type<"center" | "end">
Text alignment. Applies to any textblock or figure. Not having
this mark implies aligning to the start of the block. Renders as a
text-align style.
Direction: Mark.Type<"auto" | "ltr" | "rtl">
The text direction for a textblock. Not having this mark means the
document's base direction is used. "auto" means the direction is
derived from the first character in the block that has a strong
direction. Rendered using the dir attribute.
Doc: Plot.Type<null>
InlineDoc: Plot.Type<null>
A document type that has inline content, so the entire document is
a single textblock.
Inline Structure
Marks and nodes for inline content.
LineBreak: Leaf<null>
A hard line break. Renders as <br> and has the LineBreak role.
Emphasis: Mark<null>
Emphasis mark. Rendered using the <em> element.
Strong: Mark<null>
Strong emphasis mark. Rendered using the <strong> element.
Underline: Mark<null>
Underline mark. Rendered using <u>.
Strikethrough: Mark<null>
Strikethrough mark. Rendered as an <s> element.
Superscript: Mark<null>
Superscript text. Rendered using <sup>.
Subscript: Mark<null>
Subscript text. Rendered using <sub>.
Link: Mark.Type<string>
Link markup. The parameter is the link's target. Rendered as an
<a> element with the target in href.
Code: Mark<null>
Code font text. Rendered as <code>.
Color: Mark.Type<string>
Text color mark. Rendered with the color style.
BackgroundColor: Mark.Type<string>
Text background color. Rendered with the background-color style.
Images
Image types and their marks.
Image: Leaf.Type<string>
An inline image leaf. The string parameter is the image's source
URI. Rendered as <img>.
Figure: Leaf.Type<string>
A block image leaf. Renders as a <figure> element with an
<img> in it. The parameter holds the image URI.
CaptionedFigure: Plot.Type<string>
A textblock plot representing a captioned image. The image URI is
the plot's parameter, and the caption the content (inline).
Assigned to the Content group by
default. Renders as a <figure> element with <img> and
<figcaption> elements as children.
ImageAlt: Mark.Type<string>
Image alt text. Renders as an alt attribute.
ImageSize: Mark.Type<number>
Image size, as a width in pixels. Renders as a width style.
Tables
Nodes and marks used to define tables. See
wordgard/table for the supporting extensions that make
table editing possible.
Cell: Plot.Tag<null>
Table cell plot. Allows inline content, and is in the TableCell group. Rendered as <td>
: Plot.Tag<null>
Table header cell. Like Cell, but rendered as <th>.
BlockCell: Plot.Tag<null>
Table cell with block content. Rendered as <td>. You can use
either this one or Cell in your schema, not both.
: Plot.Tag<null>
Table header cell with block content. Rendered as <th>.
TableRow: Plot.Tag<null>
Table row. Allows nodes with the TableCell group as content. Rendered as <tr>.
Table: Plot.Tag<null>
A table. Contains TableRows, is part of the Content group. Rendered as
<table><tbody>.
ColSpan: Mark.Type<number>
Table cell column span. Rendered with the colspan attribute.
Must hold a positive integer. Is assumed
to be 1 when missing.
RowSpan: Mark.Type<number>
Table cell row span. Rendered with the rowspan attribute.
Is assumed to be 1 when missing.
This module provides editing support extensions and convenient
bundle functions for most of the schema elements defined in
wordgard/types.
Block Structure
Extension bundle functions and individual extensions related to
block-level document structure.
paragraph(): GardState.Extension
paragraph.keyBinding: KeyBinding
Binds Ctrl-Shift-0 to switching the selected textblocks to
regular paragraphs.
paragraph.button: Menu.Button
Button in the textblockStyle menu that makes the selected textblocks
paragraphs.
heading(): GardState.Extension
heading.keyBindings: KeyBinding[]
Binds Ctrl-Shift-1 through Ctrl-Shift-6 to make the selected
textblocks headings of the given level.
heading.button1: Menu.Button
heading.button2: Menu.Button
Menu button that switches to a level 2 heading.
heading.button3: Menu.Button
Menu button that switches to a level 3 heading.
heading.createOnHash: InputRule
Input rule that will switch to a heading when one to six hash
characters, followed by a space, are typed at the start of a
textblock, using the number of hash characters to determine the
heading level.
codeBlock(): GardState.Extension
codeBlock.keyBinding: KeyBinding
Binds Ctrl-Shift-\ to switch the selected textblocks to a code
block.
codeBlock.button: Menu.Button
codeBlock.createOnBackticks: InputRule
Input rule that switches the current textblock to a code block
when you type three backticks at its start.
alignment(): GardState.Extension
alignment.keyBindings: KeyBinding[]
Bind Mod-Shift-l to left-align, Mod-Shift-r to right-align,
and Mod-Shift-e to center.
alignment.buttonStart: Menu.Button
A button that sets text alignments to start.
alignment.buttonEnd: Menu.Button
A button that sets text alignment to end.
alignment.buttonCenter: Menu.Button
Menu button that sets text alignment to center.
alignment.button: Menu.Submenu
direction(): GardState.Extension
Add support for selecting a text direction per textblock. Includes
the Direction mark, the extension to make the editor interpret that, and a menu button.
direction.textblockDir: GardState.Extension
This extension makes the editor state understand the effect the
Direction mark has on text direction, so that cursor
motion and such behaves correctly in blocks that have it.
direction.buttonLTR: Menu.Button
Menu button to choose a left-to-right text direction.
direction.buttonRTL: Menu.Button
Button to choose a right-to-left text direction.
direction.buttonAuto: Menu.Button
Button that enables automatic text direction, where the content
of the textblock determines direction.
direction.button: Menu.Submenu
blockquote(): GardState.Extension
blockquote.button: Menu.Button
Menu button that toggles a blockquote wrapper.
blockquote.createOnGT: InputRule
Input rule that wraps the current block in a blockquote when you
type a greater-than sign followed by a space at its start.
blockquote.theme: GardState.Extension
Simple style that shows a border next to blockquotes to make
them easy to see.
horizontalRule(): GardState.Extension
horizontalRule.createOnDashes: InputRule
Input rule that will create a horizontal rule if you type three
dashes into an empty textblock.
bulletList(config: {
blockItems?: boolean
By defaults, list items contain block content. Set this to false to allow only inline content.
} = {}): GardState.Extension[]
bulletList.createOnDash: InputRule
This rule wraps the current textblock in a bullet list when the
user starts it by typing an optional space, a dash, and then another
space.
bulletList.toggleButton: Menu.Button
A menu button that toggles bullet list wrapping for the selection.
orderedList(config: {
blockItems?: boolean
} = {}): GardState.Extension[]
Returns extensions that enable ordered list support, including the
schema elements, menu button,
and input rule.
orderedList.createOnNumber: InputRule
An input rule that, when the user starts a textblock with an
optional space, a number, a period, and a space, wraps the block
in an ordered list.
orderedList.toggleButton: Menu.Button
A menu button that toggles an ordered list wrapper for the
selected textblocks.
blockDoc(): GardState.Extension
Schema element that provides the outer document plot
type for a document with block content.
inlineDoc(): GardState.Extension
Outer document schema element for a document
that contains only inline content.
Inline Structure
Extensions related to inline content.
lineBreak(): GardState.Extension
strong(): GardState.Extension
strong.keyBinding: KeyBinding
Binds Mod-b to toggle strong emphasis.
strong.button: Menu.Button
A menu button that toggles strong emphasis.
emphasis(): GardState.Extension
emphasis.keyBinding: KeyBinding
Binds Mod-i to toggle emphasis.
emphasis.button: Menu.Button
Menu button that toggles emphasis.
code(): GardState.Extension
code.keyBinding: KeyBinding
Binds Mod-` to toggle the code font mark.
code.button: Menu.Button
Menu button for toggling code font.
underline(): GardState.Extension
underline.keyBinding: KeyBinding
Binds Mod-u to toggle the underline mark.
underline.button: Menu.Button
Menu button for toggling underline.
strikethrough(): GardState.Extension
strikethrough.keyBinding: KeyBinding
Binds Mod-/ to toggle strikethrough.
strikethrough.button: Menu.Button
Menu button that toggles the strikethrough mark.
superscript(): GardState.Extension
superscript.keyBinding: KeyBinding
Binds Mod-. to toggle superscript.
superscript.button: Menu.Button
Menu button for toggling superscript.
subscript(): GardState.Extension
subscript.keyBinding: KeyBinding
Binds Mod-, to toggle the subscript mark.
subscript.button: Menu.Button
Menu button for toggling subscript.
color(): GardState.Extension
color.button: Menu.Submenu
A button that shows a color picker to change the text color.
backgroundColor(): GardState.Extension
backgroundColor.button: Menu.Submenu
A button that expands a submenu with a color picker that can be
used to change the text background color.
ColorPicker.width: GardState.Facet<number, number>
Facet used to configure the width (in color widgets) of the
color picker. Defaults to 10.
ColorPicker.options: GardState.Facet<
readonly ColorPicker.Option[],
readonly ColorPicker.Option[]
>
Facet used to configure color picker options. The default is a
collection of 80 colors, starting with an empty one for clearing
color.
ColorPicker.theme: GardState.Extension
Base theme for the color picker.
link(): GardState.Extension
link.keyBinding: KeyBinding
Binds Mod-k to toggle the link mark.
link.button: Menu.Button
Menu button that will remove the link mark from the selection if
present, or prompt for a target and make the current selection a
link.
link.tooltip: GardState.Extension
Extension that displays a tooltip with the link target below the
cursor when that is in a link.
link.pasteOver: GardState.Extension
Registers a paste handler that, when a URI is pasted over a
selection, will add a link to the selection instead of replacing
it with the pasted text.
Images
Support for images and a dialog to create or modify them.
image(): GardState.Extension
image.keyBinding: KeyBinding
Binds Ctrl-Alt-i to open the image insert/update dialog.
image.button: Menu.Button
A menu button that opens a dialog to insert an image or figure,
or to adjust the currently selected image or figure.
image.dropHandler: GardState.Extension
A custom drop handler that checks whether an image file is being
dropped. When an uploader has been
defined, it will feed the file to that, and insert an image with
the resulting URI when it finishes.
image.insert: Command
The command that opens the image dialog.
image.uploader: GardState.Facet<(
file: File,
wg: Wordgard,
progress: (percent: number) => void
) => Promise<string>>
A facet that you can use to register a handler for image
uploads. Your function will be passed a file object, an editor
instance, and a function that it can call to indicate progress,
and should return a promise that resolves to a URI for the
uploaded image.
figure(conf: {
captioned?: boolean
} = {}): GardState.Extension
Support for block figures. Adds the same support extensions as
image, but includes the Figure schema element instead.
imageResizing(): GardState.Extension
imageResizing.resizeCommand: (
by: number,
relative: boolean = false
) => Command
Returns a command that resizes the currently selected image or
figure. When relative is fale, by indicates a pixel amount,
which can be positive or negative. When it is true, by is a
scaling factor, which should be between 0 and 1 to shrink the
image, and greater than1 to grow it.
imageResizing.keyBindings: KeyBinding[]
Binds Ctrl-Alt-l (Ctrl-Cmd-l on Mac) to grow an image by
10%, and Ctrl-Alt-k (Ctrl-Cmd-k on Mac) to shrink it by 10%.
imageResizing.dragHandle: GardState.Extension
Extension that displays a drag handle when the user hovers over
an image or figure, which can be dragged to adjust the element's
width.
Bundles
Helper functions that bundle the extensions provided by this
module into groups, so that they can easily be included in a
configuration.
basicMarks(): GardState.Extension
inlineMarks(): GardState.Extension
basicSchema(): GardState.Extension
inlineSchema(): GardState.Extension
fullSchema(): GardState.Extension
Returns an extension with all the schema elements included in this
module, in a block document. Note that new
elements may appear in this schema as new versions of the library
add new features.