import { createEditor } from "@platejs/plite";
import { history } from "@platejs/plite-history";
const editor = createEditor({
extensions: [history()],
});
editor.read((state) => state.history.undos());
editor.update((tx) => {
tx.history.undo();
});
editor.update({ history: "skip" }).text.insert("draft");import type {
DocumentChange,
EditorEffect,
EditorSchemaIdentity,
Selection,
} from "@platejs/plite";
export interface History {
readonly redos: readonly Batch[];
readonly revision: number;
readonly schema: EditorSchemaIdentity;
readonly undos: readonly Batch[];
}
interface Batch {
readonly change: DocumentChange;
readonly effects: readonly EditorEffect[];
readonly selectionAfter: Selection;
readonly selectionAfterRoot?: string;
readonly selectionBefore: Selection;
readonly selectionBeforeRoot?: string;
}import type {
DocumentChange,
EditorEffect,
EditorSchemaIdentity,
Selection,
} from "@platejs/plite";
export interface History {
readonly redos: readonly Batch[];
readonly revision: number;
readonly schema: EditorSchemaIdentity;
readonly undos: readonly Batch[];
}
interface Batch {
readonly change: DocumentChange;
readonly effects: readonly EditorEffect[];
Returns true if the passed in value is a History object and acts as a
type guard.
Encode the installed undo and redo stacks as validated version 4 JSON. The
envelope records the editor's schema identity, and each batch stores its
canonical DocumentChange, registered effects, and selection envelopes.
Compatible typing and same-node property updates coalesce from canonical change classifications and changed ranges. Grouping fingerprints are runtime-only; loading persisted history starts a fresh automatic group.
Decode and validate history without mutating the editor. Effect codecs are
discovered from installed editor-extension resources, including state fields
and standalone effects. Install the returned immutable value through
tx.history.restore(...).
Decoding fails before reading any batch when the persisted schema identity does not match the editor. A matching schema ID and version with a different fingerprint means the schema semantics need a version bump.
const json = History.toJSON(editor);
localStorage.setItem("plite.history", JSON.stringify(json));
const decoded = History.fromJSON(
editor,
JSON.parse(localStorage.getItem("plite.history")!)
);
editor.update((tx) => {
tx.history.restore(decoded);
});const json = History.toJSON(editor);
localStorage.setItem("plite.history", JSON.stringify(json));
const decoded = History.fromJSON(
editor,
JSON.parse(localStorage.getItem("plite.history")!)
);
editor.update((tx) => {
tx.history.restore(decoded);
});Schema identity is always derived or named; null is invalid in memory and in
version 4 JSON. Schema identity, codec versions, effect keys, built-in selection
payloads, and JSON values are validated during decoding. Failed decoding leaves
editor state untouched.
Read the current undo and redo stacks.
Read the undo stack.
Read the redo stack.
Undo the previous history batch.
Redo the next history batch.
Replace both history branches when the surrounding update commits. Commit and snapshot subscribers observe the restored revision through that one update.
Do not save the current transaction to history.
Merge the current transaction into the previous compatible undo batch.
Start a fresh undo batch for the current transaction.
Run one update without saving it to history.
Run one update that merges into the previous compatible undo batch.
Run one update where the first write starts a fresh history batch, then the rest of the callback merges into that batch.
For one direct write, configure the update facade instead:
editor.update({ history: "skip" }).text.insert("draft");editor.update({ history: "skip" }).text.insert("draft");Undo the previous history batch outside a larger transaction.
Redo the next history batch outside a larger transaction.
Restore decoded history as one direct update.
Render proposed edits from local state, decorations, or sidecar UI until the user accepts them. Do not mutate document content for a preview and then try to make that preview history later.
Use a local defineStateField without a persistence codec and with
history: 'skip' for preview state. Cancel by clearing that field. Accept by
clearing the preview and applying the real document edit in one normal update.
const previewReplacement = defineStateField<string | null>({
key: "local.preview.replacement",
history: "skip",
initial: () => null,
});
editor.update((tx) => {
tx.setField(previewReplacement, null);
tx.text.delete({ at: selectedRange });
tx.text.insert(acceptedText);
});const previewReplacement = defineStateField<string | null>({
key: "local.preview.replacement",
history: "skip",
initial: () => null,
});
editor.update((tx) => {
tx.setField(previewReplacement, null);
tx.text.delete({ at: selectedRange });
tx.text.insert(acceptedText);
});Undo then restores the document content without resurrecting preview UI.