@hecto/richtext
v0.2.0
Published
Collaborative rich-text document model, commands, Markdown bridge, and undo history. No DOM.
Readme
@hecto/richtext
Document model, commands, Markdown bridge, and undo history for
collaborative rich text. No DOM — @hecto/editor is the projection.
pnpm add @hecto/richtextimport { createDocumentReplica } from '@hecto/richtext'
import { commandTable } from '@hecto/richtext/commands'
const replica = createDocumentReplica('alice')
const caret = {
blockId: 'missing',
charId: null,
side: 'before' as const,
}
const result = commandTable.insertText(
replica,
{ anchor: caret, head: caret },
{ text: 'hello' },
)
replica.apply(result.ops)
replica.snapshot().blocks[0]?.text // 'hello'What's in the box
././model—createDocumentReplica,HARD_BREAK,validateSnapshot,isSchemaValidSnapshot,orderKeyWarningsOf./commands—commandTable,mapSelection,COMMAND_NAMES./markdown—toBridgeNodes,fromBridgeNodes,BRIDGE_SITE./history—createHistory
Commands are pure: they return { ops, selection } and never call
replica.apply. The caller applies ops. Selection is
{ blockId, charId, side } so a remote insert earlier in the same block
does not need an index transform.
Model
A block is { id, type, attrs, order, parent, text }. Children are
other blocks whose parent is this id, ordered by a fractional
order key. Each block's text is a Fugue sequence at site
`${replica.site}:${blockId}`. hardBreak is the newline atom
'\n'. Empty blocks do not allocate a Fugue tree until an id is read or
text is written.
Marks follow Peritext: { id, type, value, start, end } anchored to
character ids. Bold grows at its end; link does not.
fromBridgeNodes mints ids at site 'bridge'. Applying those ops on
another site keeps text but drops marks. Unknown incoming node and mark
types are dropped.
Commands
insertText, deleteBackward, deleteForward, deleteWordBackward,
deleteWordForward, splitBlock, joinBackward, toggleMark,
setBlockType, indent, outdent, moveBlock, insertFragment.
insertText on an empty replica creates a paragraph. insertText('\n')
inserts a hard-break atom; splitBlock makes a sibling. order keys
are minted with @hecto/crdt/fractional between.
History
createHistory() undoes only the local user's operations. Push local
command results; call observeRemote for ops this replica did not mint.
A types, B types, A undoes → B's text stays.
