包详细信息

prosemirror-changeset

prosemirror7.4mMIT2.3.1

Distills a series of editing steps into deleted and added ranges

自述文件

prosemirror-changeset

This is a helper module that can turn a sequence of document changes into a set of insertions and deletions, for example to display them in a change-tracking interface. Such a set can be built up incrementally, in order to do such change tracking in a halfway performant way during live editing.

This code is licensed under an MIT licence.

Programming interface

Insertions and deletions are represented as ‘spans’—ranges in the document. The deleted spans refer to the original document, whereas the inserted ones point into the current document.

It is possible to associate arbitrary data values with such spans, for example to track the user that made the change, the timestamp at which it was made, or the step data necessary to invert it again.

class Change<Data = any>

A replaced range with metadata associated with it.

  • fromA: number\ The start of the range deleted/replaced in the old document.

  • toA: number\ The end of the range in the old document.

  • fromB: number\ The start of the range inserted in the new document.

  • toB: number\ The end of the range in the new document.

  • deleted: readonly Span[]\ Data associated with the deleted content. The length of these spans adds up to this.toA - this.fromA.

  • inserted: readonly Span[]\ Data associated with the inserted content. Length adds up to this.toB - this.fromB.

  • staticmerge<Data>(x: readonly Change[], y: readonly Change[], combine: fn(dataA: Data, dataB: Data) → Data) → readonly Change[]\ This merges two changesets (the end document of x should be the start document of y) into a single one spanning the start of x to the end of y.

class Span<Data = any>

Stores metadata for a part of a change.

  • length: number\ The length of this span.

  • data: Data\ The data associated with this span.

class ChangeSet<Data = any>

A change set tracks the changes to a document from a given point in the past. It condenses a number of step maps down to a flat sequence of replacements, and simplifies replacments that partially undo themselves by comparing their content.

  • changes: readonly Change[]\ Replaced regions.

  • addSteps(newDoc: Node, maps: readonly StepMap[], data: Data | readonly Data[]) → ChangeSet\ Computes a new changeset by adding the given step maps and metadata (either as an array, per-map, or as a single value to be associated with all maps) to the current set. Will not mutate the old set.

    Note that due to simplification that happens after each add, incrementally adding steps might create a different final set than adding all those changes at once, since different document tokens might be matched during simplification depending on the boundaries of the current changed ranges.

  • startDoc: Node\ The starting document of the change set.

  • map(f: fn(range: Span) → Data) → ChangeSet\ Map the span's data values in the given set through a function and construct a new set with the resulting data.

  • changedRange(b: ChangeSet, maps?: readonly StepMap[]) → {from: number, to: number}\ Compare two changesets and return the range in which they are changed, if any. If the document changed between the maps, pass the maps for the steps that changed it as second argument, and make sure the method is called on the old set and passed the new set. The returned positions will be in new document coordinates.

  • staticcreate<Data = any>(doc: Node, combine?: fn(dataA: Data, dataB: Data) → Data = (a, b) => a === b ? a : null as any, tokenEncoder?: TokenEncoder = DefaultEncoder) → ChangeSet\ Create a changeset with the given base object and configuration.

    The combine function is used to compare and combine metadata—it should return null when metadata isn't compatible, and a combined version for a merged range when it is.

    When given, a token encoder determines how document tokens are serialized and compared when diffing the content produced by changes. The default is to just compare nodes by name and text by character, ignoring marks and attributes.

  • simplifyChanges(changes: readonly Change[], doc: Node) → Change[]\ Simplifies a set of changes for presentation. This makes the assumption that having both insertions and deletions within a word is confusing, and, when such changes occur without a word boundary between them, they should be expanded to cover the entire set of words (in the new document) they touch. An exception is made for single-character replacements.

interface TokenEncoder<T>

A token encoder can be passed when creating a ChangeSet in order to influence the way the library runs its diffing algorithm. The encoder determines how document tokens (such as nodes and characters) are encoded and compared.

Note that both the encoding and the comparison may run a lot, and doing non-trivial work in these functions could impact performance.

  • encodeCharacter(char: number, marks: readonly Mark[]) → T\ Encode a given character, with the given marks applied.

  • encodeNodeStart(node: Node) → T\ Encode the start of a node or, if this is a leaf node, the entire node.

  • encodeNodeEnd(node: Node) → T\ Encode the end token for the given node. It is valid to encode every end token in the same way.

  • compareTokens(a: T, b: T) → boolean\ Compare the given tokens. Should return true when they count as equal.

更新日志

2.3.1 (2025-05-28)

Bug fixes

Improve diffing to not treat closing tokens of different node types as the same token.

2.3.0 (2025-05-05)

New features

Change sets can now be passed a custom token encoder that controls the way changed content is diffed.

2.2.1 (2023-05-17)

Bug fixes

Include CommonJS type declarations in the package to please new TypeScript resolution settings.

2.2.0 (2022-05-30)

New features

Include TypeScript type declarations.

2.1.2 (2019-11-20)

Bug fixes

Rename ES module files to use a .js extension, since Webpack gets confused by .mjs

2.1.1 (2019-11-19)

Bug fixes

The file referred to in the package's module field now is compiled down to ES5.

2.1.0 (2019-11-08)

New features

Add a module field to package json file.

2.0.4 (2019-03-12)

Bug fixes

Fixes an issue where steps that cause multiple changed ranges (such as ReplaceAroundStep) would cause invalid change sets.

Fix a bug in incremental change set updates that would cause incorrect results in a number of cases.

2.0.3 (2019-01-09)

Bug fixes

Make simplifyChanges merge adjacent simplified changes (which can occur when a word boundary is inserted).

2.0.2 (2019-01-08)

Bug fixes

Fix a bug in simplifyChanges that only occurred when the changes weren't at the start of the document.

2.0.1 (2019-01-07)

Bug fixes

Fixes issue in simplifyChanges where it sometimes returned nonsense when the inspected text wasn't at the start of the document.

2.0.0 (2019-01-04)

Bug fixes

Solves various cases where complicated edits could corrupt the set of changes due to the mapped positions of deletions not agreeing with the mapped positions of insertions.

New features

Moves to a more efficient diffing algorithm (Meyers), so that large replacements can be accurately diffed using reasonable time and memory.

You can now find the original document given to a ChangeSet with its startDoc accessor.

Breaking changes

The way change data is stored in ChangeSet objects works differently in this version. Instead of keeping deletions and insertions in separate arrays, the object holds an array of changes, which cover all the changed regions between the old and new document. Each change has start and end positions in both the old and the new document, and contains arrays of insertions and deletions within it.

This representation avoids a bunch of consistency problems that existed in the old approach, where keeping positions coherent in the deletion and insertion arrays was hard.

This means the deletions and insertions members in ChangeSet are gone, and instead there is a changes property which holds an array of Change objects. Each of these has fromA and toA properties indicating its extent in the old document, and fromB and toB properties pointing into the new document. Actual insertions and deletions are stored in inserted and deleted arrays in Change objects, which hold {data, length} objects, where the total length of deletions adds up to toA - fromA, and the total length of insertions to toB - fromB.

When creating a ChangeSet object, you no longer need to pass separate compare and combine callbacks. Instead, these are now represented using a single function that returns a combined data value or null when the values are not compatible.

1.2.1 (2018-11-15)

Bug fixes

Properly apply the heuristics for ignoring short matches when diffing, and adjust those heuristics to more agressively weed out tiny matches in large changes.

1.2.0 (2018-11-08)

New features

The new changedRange method can be used to compare two change sets and find out which range has changed.

1.1.0 (2018-11-07)

New features

Add a new method, ChangeSet.map to update the data associated with changed ranges.

1.0.5 (2018-09-25)

Bug fixes

Fix another issue where overlapping changes that can't be merged could produce a corrupt change set.

1.0.4 (2018-09-24)

Bug fixes

Fixes an issue where addSteps could produce invalid change sets when a new step's deleted range overlapped with an incompatible previous deletion.

1.0.3 (2017-11-10)

Bug fixes

Fix issue where deleting, inserting, and deleting the same content would lead to an inconsistent change set.

1.0.2 (2017-10-19)

Bug fixes

Fix a bug that caused addSteps to break when merging two insertions into a single deletion.

1.0.1 (2017-10-18)

Bug fixes

Fix crash in ChangeSet.addSteps.

1.0.0 (2017-10-13)

First stable release.