Reference

API

Everything public, on one page. No engine type appears in any signature here · that is the contract, not a coincidence.

createEditor(options)

OptionType
extensions T Required. The feature list · it decides the schema, the keymap and the type of `commands`.
content DocNode | string JSON or HTML. Anything the schema cannot hold is dropped on the way in. Parsing an HTML string needs a DOM · JSON and `fromMarkdown` output do not.
element HTMLElement Mount here immediately, instead of calling `mount` later.
editable boolean Defaults to true.
autofocus boolean | 'start' | 'end' Where to put the caret on mount.

Editor

MemberReturns
commands CommandsOf<T> & CoreCommands Every command from every extension you passed in, typed.
can CommandsOf<T> & CoreCommands The same commands, returning what they would return and changing nothing. For disabling a button before it is pressed.
isActive(name, attrs?) boolean Is this mark on, or is the caret inside a node of this type? Marks are looked up first.
batch(run) boolean Run several commands as one undo step. Rolls back entirely if any returns false.
getJSON() DocNode The document as plain JSON. No engine types.
getHTML() string Serialised HTML, through the same security gate as rendering.
getText() string Block-separated plain text.
setContent(content) void Replace the document with JSON or an HTML string.
selection Selection Current from, to, anchor, head and empty.
editable / setEditable(v) boolean / void Read-only mode.
on(event, fn) () => void 'change' | 'focus' | 'blur' | 'selectionChange'. Returns its own unsubscribe.
extensionState(name) S | undefined What a stateful extension is currently holding.
mount(element) void Attach to the DOM. Or pass `element` to createEditor and skip it.
destroy() void Detach and clean up.
unsafe { view, state, schema } Raw engine. Unstable, outside semver · needing it is a gap in the API above.

Core commands

Always present, whatever extensions you loaded. Every one returns a boolean.

CommandArguments
select(range) Range | Pos Move the selection.
insert(content, at?) DocNode | DocNode[] | string Insert at a position, or at the caret.
replace(range, content) Range, content Swap a range for something else.
remove(range?) Range? Delete a range, or the selection.
moveBlock(from, to) Pos, Pos Move a whole block · what a drag does.
focus() Put the caret back.
A command never throws. Positions that are not finite integers inside the document return false · NaN included, which slips past naive range checks because both NaN < 0 and NaN > size are false.

Ctx · what a command receives

doc / selection The document and where the caret is.
hasMark(name, attrs?) True if the mark covers the whole selection.
inNode(name, attrs?) True if the selection sits inside such a node.
addMark / removeMark / toggleMark Marks across a range.
setBlockType(name, attrs?) Turn the block into another type.
wrapIn(name, attrs?) / lift() Wrap the selection, or unwrap it.
insert / replace / delete / select The same primitives the core commands expose.
mark() A PosMarker. Map positions through changes made after this point.

What an extension may declare

commandsPlain functions of (ctx, …args) => boolean. Their types land on editor.commands and editor.can.
keysA binding to a command name or a function · { 'Mod-Shift-S': 'toggleSpoiler' }. A binding that returns false lets the next one try.
inputRulesA RegExp on the text before the caret and a handler. One undo step each.
attributesAttributes added to nodes and marks defined elsewhere, rendered and parsed.
handlePaste · handleDropClaim a paste or a drop before the editor parses it. Return true to keep it.
filterChangeVeto a change before it lands. Nothing is applied, recorded or redrawn when it returns false.
decorationsDraw over the document: inline attributes, node attributes, widgets. Never in the document.
stateinit and apply, reduced on every transaction · read with editor.extensionState(name).
nodeViewsRender nodes defined elsewhere with your own DOM, keyed by node name.
onCreate · onChange · onDestroyLifecycle, each handed the editor. onCreate runs when the editor mounts.
priorityLoad order. Higher loads first; a later binding for the same key wins.
nodes · marksA node declares content, group, attrs, parseDOM, toDOM, nodeView, listItem, marks, code; a mark declares inclusive, excludes, parseDOM, toDOM.

Types worth knowing

type Pos = number & { readonly __pos: unique symbol }
type Range = { from: Pos; to: Pos }

interface DocNode {
  type: string
  attrs?: Record<string, unknown>
  content?: DocNode[]
  text?: string
  marks?: DocMark[]
}

Pos is branded so a raw number cannot be passed by accident. Cast at the boundary where you genuinely know the number is a position.

Writing a position down

import { pos, range } from '@matrajs/core'

pos(0)         // number -> Pos
range(1, 6)    // (number, number) -> Range

Pos is branded so that arithmetic on one does not typecheck · these are how you write a literal without a cast. Not for carrying a position across an await: that is ctx.mark().

Functions that need no editor

import { toMarkdown, fromMarkdown, tableOfContents, assignIds } from '@matrajs/core'

toMarkdown(doc)          // DocNode  -> string
fromMarkdown(source)     // string   -> DocNode
tableOfContents(doc)     // DocNode  -> TocEntry[]
assignIds(doc, options)  // DocNode  -> DocNode with stable block ids

None of these touches the DOM, so all four run on a server.

Edit this page on GitHub