prosemirror-changeset 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +28 -22
- package/dist/index.cjs +696 -0
- package/dist/index.d.ts +118 -0
- package/dist/{index.mjs → index.es.js} +10 -10
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +617 -550
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
- package/src/{change.js → change.ts} +63 -53
- package/src/{changeset.js → changeset.ts} +73 -67
- package/src/{diff.js → diff.ts} +10 -8
- package/src/{simplify.js → simplify.ts} +17 -17
- package/test/{test-changed-range.js → test-changed-range.ts} +8 -7
- package/test/{test-changes.js → test-changes.ts} +10 -8
- package/test/{test-diff.js → test-diff.ts} +6 -5
- package/test/{test-merge.js → test-merge.ts} +4 -4
- package/test/{test-simplify.js → test-simplify.ts} +7 -6
- package/dist/index.mjs.map +0 -1
- package/rollup.config.js +0 -14
- package/test/mocha.opts +0 -1
|
@@ -1,31 +1,34 @@
|
|
|
1
|
+
import {Node} from "prosemirror-model"
|
|
2
|
+
import {StepMap} from "prosemirror-transform"
|
|
1
3
|
import {computeDiff} from "./diff"
|
|
2
4
|
import {Change, Span} from "./change"
|
|
3
5
|
export {Change, Span}
|
|
4
6
|
export {simplifyChanges} from "./simplify"
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export class ChangeSet {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
8
|
+
/// A change set tracks the changes to a document from a given point
|
|
9
|
+
/// in the past. It condenses a number of step maps down to a flat
|
|
10
|
+
/// sequence of replacements, and simplifies replacments that
|
|
11
|
+
/// partially undo themselves by comparing their content.
|
|
12
|
+
export class ChangeSet<Data = any> {
|
|
13
|
+
/// @internal
|
|
14
|
+
constructor(
|
|
15
|
+
/// @internal
|
|
16
|
+
readonly config: {doc: Node, combine: (dataA: Data, dataB: Data) => Data},
|
|
17
|
+
/// Replaced regions.
|
|
18
|
+
readonly changes: readonly Change<Data>[]
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
/// Computes a new changeset by adding the given step maps and
|
|
22
|
+
/// metadata (either as an array, per-map, or as a single value to be
|
|
23
|
+
/// associated with all maps) to the current set. Will not mutate the
|
|
24
|
+
/// old set.
|
|
25
|
+
///
|
|
26
|
+
/// Note that due to simplification that happens after each add,
|
|
27
|
+
/// incrementally adding steps might create a different final set
|
|
28
|
+
/// than adding all those changes at once, since different document
|
|
29
|
+
/// tokens might be matched during simplification depending on the
|
|
30
|
+
/// boundaries of the current changed ranges.
|
|
31
|
+
addSteps(newDoc: Node, maps: readonly StepMap[], data: Data | readonly Data[]): ChangeSet<Data> {
|
|
29
32
|
// This works by inspecting the position maps for the changes,
|
|
30
33
|
// which indicate what parts of the document were replaced by new
|
|
31
34
|
// content, and the size of that new content. It uses these to
|
|
@@ -40,7 +43,7 @@ export class ChangeSet {
|
|
|
40
43
|
// a diff to try to minimize the change by dropping matching
|
|
41
44
|
// pieces of the old and new document from the change.
|
|
42
45
|
|
|
43
|
-
let stepChanges = []
|
|
46
|
+
let stepChanges: Change<Data>[] = []
|
|
44
47
|
// Add spans for new steps.
|
|
45
48
|
for (let i = 0; i < maps.length; i++) {
|
|
46
49
|
let d = Array.isArray(data) ? data[i] : data
|
|
@@ -58,10 +61,11 @@ export class ChangeSet {
|
|
|
58
61
|
|
|
59
62
|
let newChanges = mergeAll(stepChanges, this.config.combine)
|
|
60
63
|
let changes = Change.merge(this.changes, newChanges, this.config.combine)
|
|
64
|
+
let updated: Change<Data>[] = changes as Change<Data>[]
|
|
61
65
|
|
|
62
66
|
// Minimize changes when possible
|
|
63
|
-
for (let i = 0; i <
|
|
64
|
-
let change =
|
|
67
|
+
for (let i = 0; i < updated.length; i++) {
|
|
68
|
+
let change = updated[i]
|
|
65
69
|
if (change.fromA == change.toA || change.fromB == change.toB ||
|
|
66
70
|
// Only look at changes that touch newly added changed ranges
|
|
67
71
|
!newChanges.some(r => r.toB > change.fromB && r.fromB < change.toB)) continue
|
|
@@ -71,48 +75,48 @@ export class ChangeSet {
|
|
|
71
75
|
if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB)
|
|
72
76
|
continue
|
|
73
77
|
|
|
78
|
+
if (updated == changes) updated = changes.slice()
|
|
74
79
|
if (diff.length == 1) {
|
|
75
|
-
|
|
80
|
+
updated[i] = diff[0]
|
|
76
81
|
} else {
|
|
77
|
-
|
|
82
|
+
updated.splice(i, 1, ...diff)
|
|
78
83
|
i += diff.length - 1
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
return new ChangeSet(this.config,
|
|
87
|
+
return new ChangeSet(this.config, updated)
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
/// The starting document of the change set.
|
|
91
|
+
get startDoc(): Node { return this.config.doc }
|
|
92
|
+
|
|
93
|
+
/// Map the span's data values in the given set through a function
|
|
94
|
+
/// and construct a new set with the resulting data.
|
|
95
|
+
map(f: (range: Span<Data>) => Data): ChangeSet<Data> {
|
|
96
|
+
let mapSpan = (span: Span<Data>) => {
|
|
97
|
+
let newData = f(span)
|
|
98
|
+
return newData === span.data ? span : new Span(span.length, newData)
|
|
99
|
+
}
|
|
100
|
+
return new ChangeSet(this.config, this.changes.map((ch: Change<Data>) => {
|
|
101
|
+
return new Change(ch.fromA, ch.toA, ch.fromB, ch.toB, ch.deleted.map(mapSpan), ch.inserted.map(mapSpan))
|
|
97
102
|
}))
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
changedRange(b, maps) {
|
|
105
|
+
/// Compare two changesets and return the range in which they are
|
|
106
|
+
/// changed, if any. If the document changed between the maps, pass
|
|
107
|
+
/// the maps for the steps that changed it as second argument, and
|
|
108
|
+
/// make sure the method is called on the old set and passed the new
|
|
109
|
+
/// set. The returned positions will be in new document coordinates.
|
|
110
|
+
changedRange(b: ChangeSet, maps?: readonly StepMap[]): {from: number, to: number} | null {
|
|
107
111
|
if (b == this) return null
|
|
108
112
|
let touched = maps && touchedRange(maps)
|
|
109
113
|
let moved = touched ? (touched.toB - touched.fromB) - (touched.toA - touched.fromA) : 0
|
|
110
|
-
function map(p) {
|
|
114
|
+
function map(p: number) {
|
|
111
115
|
return !touched || p <= touched.fromA ? p : p + moved
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
let from = touched ? touched.fromB : 2e8, to = touched ? touched.toB : -2e8
|
|
115
|
-
function add(start, end = start) {
|
|
119
|
+
function add(start: number, end = start) {
|
|
116
120
|
from = Math.min(start, from); to = Math.max(end, to)
|
|
117
121
|
}
|
|
118
122
|
|
|
@@ -127,29 +131,31 @@ export class ChangeSet {
|
|
|
127
131
|
return from <= to ? {from, to} : null
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return new ChangeSet({combine, doc}, [], [])
|
|
134
|
+
/// Create a changeset with the given base object and configuration.
|
|
135
|
+
/// The `combine` function is used to compare and combine metadata—it
|
|
136
|
+
/// should return null when metadata isn't compatible, and a combined
|
|
137
|
+
/// version for a merged range when it is.
|
|
138
|
+
static create<Data = any>(doc: Node, combine: (dataA: Data, dataB: Data) => Data = (a, b) => a === b ? a : null as any) {
|
|
139
|
+
return new ChangeSet({combine, doc}, [])
|
|
137
140
|
}
|
|
138
|
-
}
|
|
139
141
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
/// Exported for testing @internal
|
|
143
|
+
static computeDiff = computeDiff
|
|
144
|
+
}
|
|
142
145
|
|
|
143
|
-
// : ([[Change]], (any, any) → any, number, number) → [Change]
|
|
144
146
|
// Divide-and-conquer approach to merging a series of ranges.
|
|
145
|
-
function mergeAll(
|
|
147
|
+
function mergeAll<Data>(
|
|
148
|
+
ranges: readonly Change<Data>[],
|
|
149
|
+
combine: (dA: Data, dB: Data) => Data,
|
|
150
|
+
start = 0, end = ranges.length
|
|
151
|
+
): readonly Change<Data>[] {
|
|
146
152
|
if (end == start + 1) return [ranges[start]]
|
|
147
153
|
let mid = (start + end) >> 1
|
|
148
154
|
return Change.merge(mergeAll(ranges, combine, start, mid),
|
|
149
155
|
mergeAll(ranges, combine, mid, end), combine)
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
function endRange(maps) {
|
|
158
|
+
function endRange(maps: readonly StepMap[]) {
|
|
153
159
|
let from = 2e8, to = -2e8
|
|
154
160
|
for (let i = 0; i < maps.length; i++) {
|
|
155
161
|
let map = maps[i]
|
|
@@ -165,19 +171,19 @@ function endRange(maps) {
|
|
|
165
171
|
return from == 2e8 ? null : {from, to}
|
|
166
172
|
}
|
|
167
173
|
|
|
168
|
-
function touchedRange(maps) {
|
|
174
|
+
function touchedRange(maps: readonly StepMap[]) {
|
|
169
175
|
let b = endRange(maps)
|
|
170
176
|
if (!b) return null
|
|
171
|
-
let a = endRange(maps.map(m => m.invert()).reverse())
|
|
177
|
+
let a = endRange(maps.map(m => m.invert()).reverse())!
|
|
172
178
|
return {fromA: a.from, toA: a.to, fromB: b.from, toB: b.to}
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
function sameRanges(a
|
|
181
|
+
function sameRanges<Data>(a: Change<Data>, b: Change<Data>, map: (pos: number) => number) {
|
|
176
182
|
return map(a.fromB) == b.fromB && map(a.toB) == b.toB &&
|
|
177
183
|
sameSpans(a.deleted, b.deleted) && sameSpans(a.inserted, b.inserted)
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
function sameSpans(a, b) {
|
|
186
|
+
function sameSpans<Data>(a: readonly Span<Data>[], b: readonly Span<Data>[]) {
|
|
181
187
|
if (a.length != b.length) return false
|
|
182
188
|
for (let i = 0; i < a.length; i++)
|
|
183
189
|
if (a[i].length != b[i].length || a[i].data !== b[i].data) return false
|
package/src/{diff.js → diff.ts}
RENAMED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import {Fragment} from "prosemirror-model"
|
|
2
|
+
import {Change} from "./change"
|
|
3
|
+
|
|
1
4
|
// Convert the given range of a fragment to tokens, where node open
|
|
2
5
|
// tokens are encoded as strings holding the node name, characters as
|
|
3
6
|
// their character code, and node close tokens as -1.
|
|
4
|
-
function tokens(frag, start, end, target) {
|
|
7
|
+
function tokens(frag: Fragment, start: number, end: number, target: (number | string)[]) {
|
|
5
8
|
for (let i = 0, off = 0; i < frag.childCount; i++) {
|
|
6
9
|
let child = frag.child(i), endOff = off + child.nodeSize
|
|
7
10
|
let from = Math.max(off, start), to = Math.min(endOff, end)
|
|
8
11
|
if (from < to) {
|
|
9
12
|
if (child.isText) {
|
|
10
|
-
for (let j = from; j < to; j++) target.push(child.text
|
|
13
|
+
for (let j = from; j < to; j++) target.push(child.text!.charCodeAt(j - off))
|
|
11
14
|
} else if (child.isLeaf) {
|
|
12
15
|
target.push(child.type.name)
|
|
13
16
|
} else {
|
|
@@ -31,12 +34,11 @@ const MAX_DIFF_SIZE = 5000
|
|
|
31
34
|
// idea is to make it higher in bigger replacements, so that you don't
|
|
32
35
|
// get a diff soup of coincidentally identical letters when replacing
|
|
33
36
|
// a paragraph.
|
|
34
|
-
function minUnchanged(sizeA, sizeB) {
|
|
37
|
+
function minUnchanged(sizeA: number, sizeB: number) {
|
|
35
38
|
return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)))
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
export function computeDiff(fragA, fragB, range) {
|
|
41
|
+
export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change) {
|
|
40
42
|
let tokA = tokens(fragA, range.fromA, range.toA, [])
|
|
41
43
|
let tokB = tokens(fragB, range.fromB, range.toB, [])
|
|
42
44
|
|
|
@@ -56,8 +58,8 @@ export function computeDiff(fragA, fragB, range) {
|
|
|
56
58
|
|
|
57
59
|
let lenA = endA - start, lenB = endB - start
|
|
58
60
|
let max = Math.min(MAX_DIFF_SIZE, lenA + lenB), off = max + 1
|
|
59
|
-
let history = []
|
|
60
|
-
let frontier = []
|
|
61
|
+
let history: number[][] = []
|
|
62
|
+
let frontier: number[] = []
|
|
61
63
|
for (let len = off * 2, i = 0; i < len; i++) frontier[i] = -1
|
|
62
64
|
|
|
63
65
|
for (let size = 0; size <= max; size++) {
|
|
@@ -73,7 +75,7 @@ export function computeDiff(fragA, fragB, range) {
|
|
|
73
75
|
// Used to add steps to a diff one at a time, back to front, merging
|
|
74
76
|
// ones that are less than minSpan tokens apart
|
|
75
77
|
let fromA = -1, toA = -1, fromB = -1, toB = -1
|
|
76
|
-
let add = (fA, tA, fB, tB) => {
|
|
78
|
+
let add = (fA: number, tA: number, fB: number, tB: number) => {
|
|
77
79
|
if (fromA > -1 && fromA < tA + minSpan) {
|
|
78
80
|
fromA = fA; fromB = fB
|
|
79
81
|
} else {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import {Fragment, Node} from "prosemirror-model"
|
|
1
2
|
import {Span, Change} from "./change"
|
|
2
3
|
|
|
3
|
-
let letter
|
|
4
|
+
let letter: RegExp | undefined
|
|
4
5
|
// If the runtime support unicode properties in regexps, that's a good
|
|
5
6
|
// source of info on whether something is a letter.
|
|
6
7
|
try { letter = new RegExp("[\\p{Alphabetic}_]", "u") } catch(_) {}
|
|
@@ -9,7 +10,7 @@ try { letter = new RegExp("[\\p{Alphabetic}_]", "u") } catch(_) {}
|
|
|
9
10
|
// or if it is part of these common single-case scripts.
|
|
10
11
|
const nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/
|
|
11
12
|
|
|
12
|
-
function isLetter(code) {
|
|
13
|
+
function isLetter(code: number) {
|
|
13
14
|
if (code < 128)
|
|
14
15
|
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 79 && code <= 122
|
|
15
16
|
let ch = String.fromCharCode(code)
|
|
@@ -20,15 +21,15 @@ function isLetter(code) {
|
|
|
20
21
|
// Convert a range of document into a string, so that we can easily
|
|
21
22
|
// access characters at a given position. Treat non-text tokens as
|
|
22
23
|
// spaces so that they aren't considered part of a word.
|
|
23
|
-
function getText(frag, start, end) {
|
|
24
|
+
function getText(frag: Fragment, start: number, end: number) {
|
|
24
25
|
let out = ""
|
|
25
|
-
function convert(frag, start, end) {
|
|
26
|
+
function convert(frag: Fragment, start: number, end: number) {
|
|
26
27
|
for (let i = 0, off = 0; i < frag.childCount; i++) {
|
|
27
28
|
let child = frag.child(i), endOff = off + child.nodeSize
|
|
28
29
|
let from = Math.max(off, start), to = Math.min(endOff, end)
|
|
29
30
|
if (from < to) {
|
|
30
31
|
if (child.isText) {
|
|
31
|
-
out += child.text
|
|
32
|
+
out += child.text!.slice(Math.max(0, start - off), Math.min(child.text!.length, end - off))
|
|
32
33
|
} else if (child.isLeaf) {
|
|
33
34
|
out += " "
|
|
34
35
|
} else {
|
|
@@ -48,15 +49,14 @@ function getText(frag, start, end) {
|
|
|
48
49
|
// candidates for merging.
|
|
49
50
|
const MAX_SIMPLIFY_DISTANCE = 30
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let result = []
|
|
52
|
+
/// Simplifies a set of changes for presentation. This makes the
|
|
53
|
+
/// assumption that having both insertions and deletions within a word
|
|
54
|
+
/// is confusing, and, when such changes occur without a word boundary
|
|
55
|
+
/// between them, they should be expanded to cover the entire set of
|
|
56
|
+
/// words (in the new document) they touch. An exception is made for
|
|
57
|
+
/// single-character replacements.
|
|
58
|
+
export function simplifyChanges(changes: readonly Change[], doc: Node) {
|
|
59
|
+
let result: Change[] = []
|
|
60
60
|
for (let i = 0; i < changes.length; i++) {
|
|
61
61
|
let end = changes[i].toB, start = i
|
|
62
62
|
while (i < changes.length - 1 && changes[i + 1].fromB <= end + MAX_SIMPLIFY_DISTANCE)
|
|
@@ -66,7 +66,7 @@ export function simplifyChanges(changes, doc) {
|
|
|
66
66
|
return result
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function simplifyAdjacentChanges(changes, from, to, doc, target) {
|
|
69
|
+
function simplifyAdjacentChanges(changes: readonly Change[], from: number, to: number, doc: Node, target: Change[]) {
|
|
70
70
|
let start = Math.max(0, changes[from].fromB - MAX_SIMPLIFY_DISTANCE)
|
|
71
71
|
let end = Math.min(doc.content.size, changes[to - 1].toB + MAX_SIMPLIFY_DISTANCE)
|
|
72
72
|
let text = getText(doc.content, start, end)
|
|
@@ -107,9 +107,9 @@ function simplifyAdjacentChanges(changes, from, to, doc, target) {
|
|
|
107
107
|
return changes
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
function combine(a, b) { return a === b ? a : null }
|
|
110
|
+
function combine<T>(a: T, b: T): T { return a === b ? a : null as any }
|
|
111
111
|
|
|
112
|
-
function fillChange(changes, fromB, toB) {
|
|
112
|
+
function fillChange(changes: readonly Change[], fromB: number, toB: number) {
|
|
113
113
|
let fromA = changes[0].fromA - (changes[0].fromB - fromB)
|
|
114
114
|
let last = changes[changes.length - 1]
|
|
115
115
|
let toA = last.toA + (toB - last.toB)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import ist from "ist"
|
|
2
|
+
import {schema, doc, p} from "prosemirror-test-builder"
|
|
3
|
+
import {Transform} from "prosemirror-transform"
|
|
4
|
+
import {Node} from "prosemirror-model"
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
import {ChangeSet} from "prosemirror-changeset"
|
|
6
7
|
|
|
7
|
-
function mk(doc, change) {
|
|
8
|
+
function mk(doc: Node, change: (tr: Transform) => Transform): {doc0: Node, tr: Transform, data: any[], set0: ChangeSet, set: ChangeSet} {
|
|
8
9
|
let tr = change(new Transform(doc))
|
|
9
10
|
let data = new Array(tr.steps.length).fill("a")
|
|
10
11
|
let set0 = ChangeSet.create(doc)
|
|
@@ -12,7 +13,7 @@ function mk(doc, change) {
|
|
|
12
13
|
set: set0.addSteps(tr.doc, tr.mapping.maps, data)}
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function same(a, b) {
|
|
16
|
+
function same(a: any, b: any) {
|
|
16
17
|
ist(JSON.stringify(a), JSON.stringify(b))
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -42,6 +43,6 @@ describe("ChangeSet.changedRange", () => {
|
|
|
42
43
|
|
|
43
44
|
it("detects changes in deletions", () => {
|
|
44
45
|
let {set} = mk(doc(p("abc")), tr => tr.delete(2, 3))
|
|
45
|
-
same(set.changedRange(set.map(() => "b"
|
|
46
|
+
same(set.changedRange(set.map(() => "b")), {from: 2, to: 2})
|
|
46
47
|
})
|
|
47
48
|
})
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import ist from "ist"
|
|
2
|
+
import {schema, doc, p, blockquote, h1} from "prosemirror-test-builder"
|
|
3
|
+
import {Transform} from "prosemirror-transform"
|
|
4
|
+
import {Node} from "prosemirror-model"
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
import {ChangeSet} from "prosemirror-changeset"
|
|
6
7
|
|
|
7
8
|
describe("ChangeSet", () => {
|
|
8
9
|
it("finds a single insertion",
|
|
@@ -181,11 +182,12 @@ describe("ChangeSet", () => {
|
|
|
181
182
|
|
|
182
183
|
it("correctly handles steps with multiple map entries", find(doc(p()), [
|
|
183
184
|
tr => tr.replaceWith(1, 1, t("ab")),
|
|
184
|
-
tr => tr.wrap(tr.doc.resolve(1).blockRange()
|
|
185
|
+
tr => tr.wrap(tr.doc.resolve(1).blockRange()!, [{type: schema.nodes.blockquote}])
|
|
185
186
|
], [[0, 0, 0, 1], [1, 1, 2, 4], [2, 2, 5, 6]]))
|
|
186
187
|
})
|
|
187
188
|
|
|
188
|
-
function find(doc, build
|
|
189
|
+
function find(doc: Node, build: ((tr: Transform) => void) | ((tr: Transform) => void)[],
|
|
190
|
+
changes: any[], sep?: number[] | boolean) {
|
|
189
191
|
return () => {
|
|
190
192
|
let set = ChangeSet.create(doc), curDoc = doc
|
|
191
193
|
if (!Array.isArray(build)) build = [build]
|
|
@@ -198,7 +200,7 @@ function find(doc, build, changes, sep) {
|
|
|
198
200
|
|
|
199
201
|
let owner = sep && changes.length && changes[0].length > 4
|
|
200
202
|
ist(JSON.stringify(set.changes.map(ch => {
|
|
201
|
-
let range = [ch.fromA, ch.toA, ch.fromB, ch.toB]
|
|
203
|
+
let range: any[] = [ch.fromA, ch.toA, ch.fromB, ch.toB]
|
|
202
204
|
if (owner) range.push(ch.deleted.map(d => [d.length, d.data]),
|
|
203
205
|
ch.inserted.map(d => [d.length, d.data]))
|
|
204
206
|
return range
|
|
@@ -206,4 +208,4 @@ function find(doc, build, changes, sep) {
|
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
210
|
|
|
209
|
-
function t(str) { return schema.text(str) }
|
|
211
|
+
function t(str: string) { return schema.text(str) }
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import ist from "ist"
|
|
2
|
+
import {doc, p, em, strong, h1, h2} from "prosemirror-test-builder"
|
|
3
|
+
import {Node} from "prosemirror-model"
|
|
4
|
+
import {Span, Change, ChangeSet} from "prosemirror-changeset"
|
|
5
|
+
const {computeDiff} = ChangeSet
|
|
5
6
|
|
|
6
7
|
describe("computeDiff", () => {
|
|
7
|
-
function test(doc1, doc2, ...ranges) {
|
|
8
|
+
function test(doc1: Node, doc2: Node, ...ranges: number[][]) {
|
|
8
9
|
let diff = computeDiff(doc1.content, doc2.content,
|
|
9
10
|
new Change(0, doc1.content.size, 0, doc2.content.size,
|
|
10
11
|
[new Span(doc1.content.size, 0)],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import ist from "ist"
|
|
2
|
+
import {Change, Span} from "prosemirror-changeset"
|
|
3
3
|
|
|
4
4
|
describe("mergeChanges", () => {
|
|
5
5
|
it("can merge simple insertions", () => test(
|
|
@@ -43,13 +43,13 @@ describe("mergeChanges", () => {
|
|
|
43
43
|
))
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
-
function range(array, author = 0) {
|
|
46
|
+
function range(array: number[], author = 0) {
|
|
47
47
|
let [fromA, toA] = array
|
|
48
48
|
let [fromB, toB] = array.length > 2 ? array.slice(2) : array
|
|
49
49
|
return new Change(fromA, toA, fromB, toB, [new Span(toA - fromA, author)], [new Span(toB - fromB, author)])
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function test(changeA, changeB, expected) {
|
|
52
|
+
function test(changeA: number[][], changeB: number[][], expected: number[][]) {
|
|
53
53
|
const result = Change.merge(changeA.map(range), changeB.map(range), a => a)
|
|
54
54
|
.map(r => [r.fromA, r.toA, r.fromB, r.toB])
|
|
55
55
|
ist(JSON.stringify(result), JSON.stringify(expected))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import ist from "ist"
|
|
2
|
+
import {doc, p, img} from "prosemirror-test-builder"
|
|
3
|
+
import {Node} from "prosemirror-model"
|
|
4
|
+
import {simplifyChanges, Change, Span} from "prosemirror-changeset"
|
|
4
5
|
|
|
5
6
|
describe("simplifyChanges", () => {
|
|
6
7
|
it("doesn't change insertion-only changes", () => test(
|
|
@@ -25,7 +26,7 @@ describe("simplifyChanges", () => {
|
|
|
25
26
|
[[7, 10]], doc(p("one two ----- four")), [[5, 10]]))
|
|
26
27
|
|
|
27
28
|
it("treats leaf nodes as non-words", () => test(
|
|
28
|
-
[[2, 3], [6, 7]], doc(p("one", img, "two")), [[2, 3], [6, 7]]))
|
|
29
|
+
[[2, 3], [6, 7]], doc(p("one", img(), "two")), [[2, 3], [6, 7]]))
|
|
29
30
|
|
|
30
31
|
it("treats node boundaries as non-words", () => test(
|
|
31
32
|
[[2, 3], [7, 8]], doc(p("one"), p("two")), [[2, 3], [7, 8]]))
|
|
@@ -55,13 +56,13 @@ describe("simplifyChanges", () => {
|
|
|
55
56
|
})
|
|
56
57
|
})
|
|
57
58
|
|
|
58
|
-
function range(array, author = 0) {
|
|
59
|
+
function range(array: number[], author = 0) {
|
|
59
60
|
let [fromA, toA] = array
|
|
60
61
|
let [fromB, toB] = array.length > 2 ? array.slice(2) : array
|
|
61
62
|
return new Change(fromA, toA, fromB, toB, [new Span(toA - fromA, author)], [new Span(toB - fromB, author)])
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
function test(changes, doc, result) {
|
|
65
|
+
function test(changes: number[][], doc: Node, result: number[][]) {
|
|
65
66
|
let ranges = changes.map(range)
|
|
66
67
|
ist(JSON.stringify(simplifyChanges(ranges, doc).map((r, i) => {
|
|
67
68
|
if (result[i] && result[i].length > 2) return [r.fromA, r.toA, r.fromB, r.toB]
|