Skip to content

Type Editor


Type Editor / @type-editor/history

@type-editor/history

A refactored version of ProseMirror's prosemirror-history module, providing undo/redo history functionality for rich text editors.

Installation

bash
npm install @type-editor/history

Overview

This module implements a selective undo/redo history for ProseMirror-based editors. Unlike a simple rollback mechanism, this history is selective, meaning it can undo specific changes while keeping other, later changes intact. This is essential for collaborative editing scenarios where multiple users may be editing simultaneously.

How It Works

ProseMirror's history isn't simply a way to roll back to a previous state, because ProseMirror supports applying changes without adding them to the history (for example during collaboration).

Each history 'Branch' (one for undo, one for redo) maintains an array of 'Items' that can optionally hold a step (an actual undoable change) and always hold a position map (needed to adjust positions for changes below them).

An item that has both a step and a selection bookmark marks the start of an 'event' — a group of changes that will be undone or redone together.

Usage

Basic Setup

typescript
import { history, undo, redo } from "@type-editor/history";
import { keymap } from "@type-editor/keymap";
import { EditorState } from "@type-editor/state";

const state = EditorState.create({
  schema,
  plugins: [
    history(),
    keymap({
      "Mod-z": undo,
      "Mod-Shift-z": redo,
    }),
  ],
});

Configuration Options

The history() plugin accepts an optional configuration object:

typescript
history({
  depth: 100, // Maximum number of history events (default: 100)
  newGroupDelay: 500, // Delay in ms before starting a new group (default: 500)
});
OptionTypeDefaultDescription
depthnumber100The number of history events to keep before the oldest events are discarded
newGroupDelaynumber500The delay in milliseconds between changes after which a new history group should be started. Adjacent changes within this delay are grouped together.

Commands

Undo Commands

CommandDescription
undoUndoes the last change and scrolls the selection into view. Typically bound to Mod-z.
undoNoScrollUndoes the last change without scrolling. Useful when you want to handle scrolling manually.

Redo Commands

CommandDescription
redoRedoes the last undone change and scrolls the selection into view. Typically bound to Mod-Shift-z (or Mod-y on Windows).
redoNoScrollRedoes the last undone change without scrolling.

Example

typescript
import { undo, redo, undoNoScroll, redoNoScroll } from "@type-editor/history";
import { keymap } from "@type-editor/keymap";

const historyKeymap = keymap({
  "Mod-z": undo,
  "Mod-Shift-z": redo,
  "Mod-y": redo, // Alternative redo binding for Windows
});

Helper Functions

undoDepth

Returns the number of undoable events available in the editor's history.

typescript
import { undoDepth } from "@type-editor/history";

const canUndo = undoDepth(state) > 0;
console.log(`You can undo ${undoDepth(state)} changes`);

redoDepth

Returns the number of redoable events available in the editor's history.

typescript
import { redoDepth } from "@type-editor/history";

const canRedo = redoDepth(state) > 0;
console.log(`You can redo ${redoDepth(state)} changes`);

closeHistory

Forces subsequent changes to be recorded as a separate history event, preventing them from being merged with the previous event.

typescript
import { closeHistory } from "@type-editor/history";

// Force the next change to start a new history event
const tr = closeHistory(state.tr);
dispatch(tr.insertText("new text"));

isHistoryTransaction

Returns true if the given transaction was generated by the history plugin (i.e., an undo or redo operation).

typescript
import { isHistoryTransaction } from '@type-editor/history';

// In a plugin's appendTransaction
appendTransaction(transactions, oldState, newState) {
  if (transactions.some(isHistoryTransaction)) {
    // Handle undo/redo transactions differently
  }
}

Controlling History

Preventing Changes from Being Recorded

You can prevent a transaction from being added to the history by setting the addToHistory metadata to false:

typescript
const tr = state.tr.insertText("text").setMeta("addToHistory", false);
dispatch(tr);

This is useful for:

  • Collaborative changes from other users
  • UI state changes that shouldn't be undoable
  • Decorations or annotations

Grouping Changes

Changes made within the newGroupDelay window (default 500ms) are automatically grouped together as a single undo event. You can force a new group to start by using closeHistory:

typescript
import { closeHistory } from "@type-editor/history";

// First change
dispatch(state.tr.insertText("Hello"));

// Force next change to be a separate event
dispatch(closeHistory(state.tr));

// This will be a separate undo event
dispatch(state.tr.insertText(" World"));

API Reference

Exports

ExportTypeDescription
historyfunctionCreates the history plugin
undoCommandUndo command with scroll
undoNoScrollCommandUndo command without scroll
redoCommandRedo command with scroll
redoNoScrollCommandRedo command without scroll
undoDepthfunctionGet number of undo events
redoDepthfunctionGet number of redo events
closeHistoryfunctionClose current history event
isHistoryTransactionfunctionCheck if transaction is from history
HistoryStateclassThe history state class
HistoryOptionstypeConfiguration options interface
mustPreserveItemsfunctionCheck if items should be preserved

License

MIT

Modules

ModuleDescription

commands/redo

commands/redo-no-scroll

commands/undo

commands/undo-no-scroll

commands/util/build-command

helper/close-history

helper/is-history-transaction

helper/must-preserve-items

helper/redo-depth

helper/undo-depth

plugin/apply-transaction

plugin/handle-history-input-event

plugin/history-plugin

plugin/history-plugin-key

state/Branch

state/create-rope-sequence

state/HistoryState

types/HistoryEventState

types/HistoryOptions

types/RopeSequence