Paid features
Version history
npm i @matrajs/versions Snapshots of the document, a real diff between any two of them — block pairing and then word runs inside a changed block, not a line-by-line text compare — and a restore that lands as a single undo step.
Setting it up
import { createEditor, starterKit } from '@matrajs/core'
import { localVersionStore, versions } from '@matrajs/versions'
const editor = createEditor({
extensions: [
...starterKit,
versions({
idleMs: 30_000,
keep: 50,
store: localVersionStore('doc-42'),
onChange: (state) => render(state.versions, state.diff),
}),
] as const,
}) | Option | Type | |
|---|---|---|
idleMs | number | null |
Snapshot once the document has been still this long. null turns automatic snapshots
off and leaves them to snapshotVersion. A version per keystroke is not
history, it is a keylogger with a nicer name.
|
keep | number | How many to hold. The oldest go first · the very first one never does. |
store | VersionStore | Where the list lives between visits. Without one it lives until reload. |
onChange | (state) => void | Called whenever the list or the preview changes. |
now | () => number | Where "now" comes from. Injected rather than reached for, because a test that has to sleep to make two versions differ is a test that fails on a slow machine. |
A first version — Opened — exists from the moment the editor does, mounted or not,
so a headless editor in a test, a server render or an import job always has a "before" to compare
against.
Commands
| Command | |
|---|---|
snapshotVersion(label?) |
Takes one now. Returns false when nothing has moved since the last version
— a list of identical entries is a list nobody will scroll.
|
restoreVersion(id) |
Puts that document back. Takes a Before restore snapshot first, so the way
back is the version list rather than undo · and undo is not where anybody thinks to look
for their work.
|
previewVersion(id | null) | Decorate the document with the diff against that version. null clears it. |
forgetVersion(id) | Drop one from the list. |
import { versionList } from '@matrajs/versions'
editor.commands.snapshotVersion('before the rewrite')
const [first] = versionList(editor)
editor.commands.previewVersion(first.id) // show what changed
editor.commands.restoreVersion(first.id) // go back · one undo step A version
| Field | Type | |
|---|---|---|
id | number | Continues from what the store loaded, rather than restarting at one and colliding. |
label | string | Defaults to Snapshot. |
at | number | Epoch milliseconds, from the clock you supplied. |
doc | DocNode | The document as it was. |
size | number | Characters at the time. |
Persisting
localVersionStore(key) is a localStorage implementation, and the interface
is two methods if you would rather keep them on a server.
interface VersionStore {
/** Sync, so the first render already has them. */
load(): Version[] | null
/** Called whenever the list changes · debouncing it is yours to decide. */
save(versions: Version[]): void
} The diff on its own
The diff is exported separately, so a review screen can compare two documents without an editor anywhere near it — on a server, in a worker, in a test.
import { diffDocs } from '@matrajs/versions'
const diff = diffDocs(before, after)
// { blocks, added, removed, changed, same } | Export | |
|---|---|
diffDocs(a, b) | DocDiff — blocks paired, with word runs inside each changed one. |
diffWords(a, b) | WordRun[] · { kind, text } where kind is added, removed
or same. |
textOf(node) | The text of a node, the way a reader sees it. |
sizeOf(node) | Its size in document coordinates. |
blockStarts(doc) | Where each top-level block begins. |
versionClasses | { added, changed, removed } — the class names the preview decorations
use. |
versionDiffCSS | The stylesheet for them. See Styling. |
A BlockChange carries before and after indices — one of
them -1 when the block is new or gone — so a side-by-side view can line the two documents
up without matching text a second time.