Type Editor / @type-editor/changeset
@type-editor/changeset
A refactored version of ProseMirror's prosemirror-changeset module, providing tools for tracking and comparing document changes over time.
Installation
npm install @type-editor/changesetOverview
This module provides utilities for tracking changes to a document from a given point in the past. It condenses step maps down to a flat sequence of replacements and simplifies replacements that partially undo themselves by comparing their content.
The ChangeSet maintains two coordinate systems:
- A coordinates: Positions in the original (starting) document
- B coordinates: Positions in the current (modified) document
Use Cases
- Track Changes: Visualize insertions and deletions in collaborative editing
- Change History: Build "diff view" features showing document modifications
- Attribution: Track which users made which changes when combined with metadata
Core Classes
ChangeSet
The main class for tracking document changes. A ChangeSet collects and simplifies a sequence of document modifications, making it easy to visualize what changed between two document states.
import { ChangeSet } from "@type-editor/changeset";
// Create a changeset from a starting document
const changeSet = ChangeSet.create(startDoc);
// Add steps as document changes occur
const updated = changeSet.addSteps(newDoc, stepMaps, metadata);
// Access the tracked changes
for (const change of updated.changes) {
console.log(
`Replaced ${change.fromA}-${change.toA} with content at ${change.fromB}-${change.toB}`,
);
}Methods
| Method | Description |
|---|---|
create(doc, combine?, tokenEncoder?) | Creates a new changeset tracking from the given document. |
addSteps(doc, stepMaps, data?) | Computes a new changeset by adding step maps and optional metadata. |
changes | The array of changes tracked from the starting document. |
startDoc | The starting document that changes are tracked relative to. |
Change
Represents a change between two document versions. A Change tracks a replaced range in the document, including both what was deleted from the old version and what was inserted in the new version.
interface Change<Data> {
fromA: number; // Start position in old document
toA: number; // End position in old document
fromB: number; // Start position in new document
toB: number; // End position in new document
deleted: ReadonlyArray<Span<Data>>; // Metadata spans for deleted content
inserted: ReadonlyArray<Span<Data>>; // Metadata spans for inserted content
}Methods
| Method | Description |
|---|---|
fromJSON(json) | Static method that deserializes a Change from its JSON representation. |
toJSON() | Serializes this Change to a JSON-compatible representation. |
slice(startA, endA, startB, endB) | Creates a sub-change by slicing ranges from both coordinate systems. |
Serialization Example
import { Change } from "@type-editor/changeset";
// Serialize a change to JSON
const json = change.toJSON();
// Deserialize a change from JSON
const restored = Change.fromJSON(json);Span
Stores metadata for a part of a change. A Span represents a contiguous range in a document with associated metadata.
import { Span } from "@type-editor/changeset";
const span = new Span(10, { author: "user1" });
console.log(span.length); // 10
console.log(span.data); // { author: 'user1' }Utility Functions
computeDiff
Computes the difference between two document fragments using Myers' diff algorithm.
import { computeDiff } from "@type-editor/changeset";
const changes = computeDiff(fragmentA, fragmentB, range);simplifyChanges
Simplifies a set of changes for presentation by expanding insertions and deletions to word boundaries.
import { simplifyChanges } from "@type-editor/changeset";
const simplified = simplifyChanges(changes, document);Token Encoder
A TokenEncoder can be provided when creating a ChangeSet to influence how the diffing algorithm compares document content. The default encoder compares nodes by name and text by character, ignoring marks and attributes.
import type { TokenEncoder } from "@type-editor/changeset";
const customEncoder: TokenEncoder<string> = {
encodeCharacter: (char, marks) => String.fromCharCode(char),
encodeNodeStart: (node) => node.type.name,
encodeNodeEnd: (node) => `/${node.type.name}`,
compareTokens: (a, b) => a === b,
};
const changeSet = ChangeSet.create(doc, combine, customEncoder);TokenEncoder Interface
| Method | Description |
|---|---|
encodeCharacter(char, marks) | Encode a character with its applied marks. |
encodeNodeStart(node) | Encode the start of a node or the entire leaf node. |
encodeNodeEnd(node) | Encode the end token for a node. |
compareTokens(a, b) | Compare two encoded tokens for equality. |
License
MIT
Modules
| Module | Description |
|---|---|
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ | |
‐ |