prosemirror-changeset 2.2.1 → 2.3.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 +6 -0
- package/README.md +35 -2
- package/dist/index.cjs +120 -252
- package/dist/index.d.cts +42 -4
- package/dist/index.d.ts +42 -4
- package/dist/index.js +29 -16
- package/package.json +4 -3
- package/src/README.md +3 -1
- package/src/change.ts +1 -1
- package/src/changeset.ts +20 -5
- package/src/diff.ts +43 -13
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ A replaced range with metadata associated with it.
|
|
|
41
41
|
|
|
42
42
|
* **`inserted`**`: readonly Span[]`\
|
|
43
43
|
Data associated with the inserted content. Length adds up to
|
|
44
|
-
`this.toB - this.
|
|
44
|
+
`this.toB - this.fromB`.
|
|
45
45
|
|
|
46
46
|
* `static `**`merge`**`<Data>(x: readonly Change[], y: readonly Change[], combine: fn(dataA: Data, dataB: Data) → Data) → readonly Change[]`\
|
|
47
47
|
This merges two changesets (the end document of x should be the
|
|
@@ -96,12 +96,18 @@ partially undo themselves by comparing their content.
|
|
|
96
96
|
make sure the method is called on the old set and passed the new
|
|
97
97
|
set. The returned positions will be in new document coordinates.
|
|
98
98
|
|
|
99
|
-
* `static `**`create`**`<Data = any>(doc: Node, combine?: fn(dataA: Data, dataB: Data) → Data = (a, b) => a === b ? a : null as any) → ChangeSet`\
|
|
99
|
+
* `static `**`create`**`<Data = any>(doc: Node, combine?: fn(dataA: Data, dataB: Data) → Data = (a, b) => a === b ? a : null as any, tokenEncoder?: TokenEncoder = DefaultEncoder) → ChangeSet`\
|
|
100
100
|
Create a changeset with the given base object and configuration.
|
|
101
|
+
|
|
101
102
|
The `combine` function is used to compare and combine metadata—it
|
|
102
103
|
should return null when metadata isn't compatible, and a combined
|
|
103
104
|
version for a merged range when it is.
|
|
104
105
|
|
|
106
|
+
When given, a token encoder determines how document tokens are
|
|
107
|
+
serialized and compared when diffing the content produced by
|
|
108
|
+
changes. The default is to just compare nodes by name and text
|
|
109
|
+
by character, ignoring marks and attributes.
|
|
110
|
+
|
|
105
111
|
|
|
106
112
|
* **`simplifyChanges`**`(changes: readonly Change[], doc: Node) → Change[]`\
|
|
107
113
|
Simplifies a set of changes for presentation. This makes the
|
|
@@ -111,3 +117,30 @@ partially undo themselves by comparing their content.
|
|
|
111
117
|
words (in the new document) they touch. An exception is made for
|
|
112
118
|
single-character replacements.
|
|
113
119
|
|
|
120
|
+
|
|
121
|
+
### interface TokenEncoder`<T>`
|
|
122
|
+
|
|
123
|
+
A token encoder can be passed when creating a `ChangeSet` in order
|
|
124
|
+
to influence the way the library runs its diffing algorithm. The
|
|
125
|
+
encoder determines how document tokens (such as nodes and
|
|
126
|
+
characters) are encoded and compared.
|
|
127
|
+
|
|
128
|
+
Note that both the encoding and the comparison may run a lot, and
|
|
129
|
+
doing non-trivial work in these functions could impact
|
|
130
|
+
performance.
|
|
131
|
+
|
|
132
|
+
* **`encodeCharacter`**`(char: number, marks: readonly Mark[]) → T`\
|
|
133
|
+
Encode a given character, with the given marks applied.
|
|
134
|
+
|
|
135
|
+
* **`encodeNodeStart`**`(node: Node) → T`\
|
|
136
|
+
Encode the start of a node or, if this is a leaf node, the
|
|
137
|
+
entire node.
|
|
138
|
+
|
|
139
|
+
* **`encodeNodeEnd`**`(node: Node) → T`\
|
|
140
|
+
Encode the end token for the given node. It is valid to encode
|
|
141
|
+
every end token in the same way.
|
|
142
|
+
|
|
143
|
+
* **`compareTokens`**`(a: T, b: T) → boolean`\
|
|
144
|
+
Compare the given tokens. Should return true when they count as
|
|
145
|
+
equal.
|
|
146
|
+
|
package/dist/index.cjs
CHANGED
|
@@ -1,113 +1,90 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
|
|
4
|
-
|
|
5
4
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
6
|
-
|
|
7
5
|
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
8
|
-
|
|
9
6
|
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
|
|
10
|
-
|
|
11
7
|
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
|
12
|
-
|
|
13
|
-
function
|
|
14
|
-
|
|
8
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
9
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
15
10
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
16
|
-
|
|
17
|
-
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
|
18
|
-
|
|
11
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
19
12
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
20
|
-
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
|
14
|
+
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
15
|
+
var DefaultEncoder = {
|
|
16
|
+
encodeCharacter: function encodeCharacter(_char) {
|
|
17
|
+
return _char;
|
|
18
|
+
},
|
|
19
|
+
encodeNodeStart: function encodeNodeStart(node) {
|
|
20
|
+
return node.type.name;
|
|
21
|
+
},
|
|
22
|
+
encodeNodeEnd: function encodeNodeEnd() {
|
|
23
|
+
return -1;
|
|
24
|
+
},
|
|
25
|
+
compareTokens: function compareTokens(a, b) {
|
|
26
|
+
return a === b;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function tokens(frag, encoder, start, end, target) {
|
|
28
30
|
for (var i = 0, off = 0; i < frag.childCount; i++) {
|
|
29
31
|
var child = frag.child(i),
|
|
30
|
-
|
|
32
|
+
endOff = off + child.nodeSize;
|
|
31
33
|
var from = Math.max(off, start),
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
to = Math.min(endOff, end);
|
|
34
35
|
if (from < to) {
|
|
35
36
|
if (child.isText) {
|
|
36
|
-
for (var j = from; j < to; j++)
|
|
37
|
-
target.push(child.text.charCodeAt(j - off));
|
|
38
|
-
}
|
|
37
|
+
for (var j = from; j < to; j++) target.push(encoder.encodeCharacter(child.text.charCodeAt(j - off), child.marks));
|
|
39
38
|
} else if (child.isLeaf) {
|
|
40
|
-
target.push(child
|
|
39
|
+
target.push(encoder.encodeNodeStart(child));
|
|
41
40
|
} else {
|
|
42
|
-
if (from == off) target.push(child
|
|
43
|
-
tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
|
|
44
|
-
if (to == endOff) target.push(
|
|
41
|
+
if (from == off) target.push(encoder.encodeNodeStart(child));
|
|
42
|
+
tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
|
|
43
|
+
if (to == endOff) target.push(encoder.encodeNodeEnd(child));
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
|
-
|
|
48
46
|
off = endOff;
|
|
49
47
|
}
|
|
50
|
-
|
|
51
48
|
return target;
|
|
52
49
|
}
|
|
53
|
-
|
|
54
50
|
var MAX_DIFF_SIZE = 5000;
|
|
55
|
-
|
|
56
51
|
function minUnchanged(sizeA, sizeB) {
|
|
57
52
|
return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)));
|
|
58
53
|
}
|
|
59
|
-
|
|
60
54
|
function computeDiff(fragA, fragB, range) {
|
|
61
|
-
var
|
|
62
|
-
var
|
|
55
|
+
var encoder = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DefaultEncoder;
|
|
56
|
+
var tokA = tokens(fragA, encoder, range.fromA, range.toA, []);
|
|
57
|
+
var tokB = tokens(fragB, encoder, range.fromB, range.toB, []);
|
|
63
58
|
var start = 0,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
while (start < tokA.length && start < tokB.length && tokA[start]
|
|
68
|
-
start++;
|
|
69
|
-
}
|
|
70
|
-
|
|
59
|
+
endA = tokA.length,
|
|
60
|
+
endB = tokB.length;
|
|
61
|
+
var cmp = encoder.compareTokens;
|
|
62
|
+
while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++;
|
|
71
63
|
if (start == tokA.length && start == tokB.length) return [];
|
|
72
|
-
|
|
73
|
-
while (endA > start && endB > start && tokA[endA - 1] === tokB[endB - 1]) {
|
|
74
|
-
endA--, endB--;
|
|
75
|
-
}
|
|
76
|
-
|
|
64
|
+
while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--;
|
|
77
65
|
if (endA == start || endB == start || endA == endB && endA == start + 1) return [range.slice(start, endA, start, endB)];
|
|
78
66
|
var lenA = endA - start,
|
|
79
|
-
|
|
67
|
+
lenB = endB - start;
|
|
80
68
|
var max = Math.min(MAX_DIFF_SIZE, lenA + lenB),
|
|
81
|
-
|
|
69
|
+
off = max + 1;
|
|
82
70
|
var history = [];
|
|
83
71
|
var frontier = [];
|
|
84
|
-
|
|
85
|
-
for (var len = off * 2, i = 0; i < len; i++) {
|
|
86
|
-
frontier[i] = -1;
|
|
87
|
-
}
|
|
88
|
-
|
|
72
|
+
for (var len = off * 2, i = 0; i < len; i++) frontier[i] = -1;
|
|
89
73
|
for (var size = 0; size <= max; size++) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
prev = frontier[
|
|
93
|
-
|
|
94
|
-
y = x +
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
x
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
frontier[diag + max] = x;
|
|
101
|
-
|
|
102
|
-
if (x >= lenA && y >= lenB) {
|
|
103
|
-
var _ret = function () {
|
|
74
|
+
var _loop = function _loop(_diag) {
|
|
75
|
+
var next = frontier[_diag + 1 + max],
|
|
76
|
+
prev = frontier[_diag - 1 + max];
|
|
77
|
+
var x = next < prev ? prev : next + 1,
|
|
78
|
+
y = x + _diag;
|
|
79
|
+
while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y])) x++, y++;
|
|
80
|
+
frontier[_diag + max] = x;
|
|
81
|
+
if (x >= lenA && y >= lenB) {
|
|
104
82
|
var diff = [],
|
|
105
|
-
|
|
83
|
+
minSpan = minUnchanged(endA - start, endB - start);
|
|
106
84
|
var fromA = -1,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
85
|
+
toA = -1,
|
|
86
|
+
fromB = -1,
|
|
87
|
+
toB = -1;
|
|
111
88
|
var add = function add(fA, tA, fB, tB) {
|
|
112
89
|
if (fromA > -1 && fromA < tA + minSpan) {
|
|
113
90
|
fromA = fA;
|
|
@@ -120,50 +97,44 @@ function computeDiff(fragA, fragB, range) {
|
|
|
120
97
|
toB = tB;
|
|
121
98
|
}
|
|
122
99
|
};
|
|
123
|
-
|
|
124
100
|
for (var _i = size - 1; _i >= 0; _i--) {
|
|
125
|
-
var _next = frontier[
|
|
126
|
-
|
|
127
|
-
|
|
101
|
+
var _next = frontier[_diag + 1 + max],
|
|
102
|
+
_prev = frontier[_diag - 1 + max];
|
|
128
103
|
if (_next < _prev) {
|
|
129
|
-
|
|
104
|
+
_diag--;
|
|
130
105
|
x = _prev + start;
|
|
131
|
-
y = x +
|
|
106
|
+
y = x + _diag;
|
|
132
107
|
add(x, x, y, y + 1);
|
|
133
108
|
} else {
|
|
134
|
-
|
|
109
|
+
_diag++;
|
|
135
110
|
x = _next + start;
|
|
136
|
-
y = x +
|
|
111
|
+
y = x + _diag;
|
|
137
112
|
add(x, x + 1, y, y);
|
|
138
113
|
}
|
|
139
|
-
|
|
140
114
|
frontier = history[_i >> 1];
|
|
141
115
|
}
|
|
142
|
-
|
|
143
116
|
if (fromA > -1) diff.push(range.slice(fromA, toA, fromB, toB));
|
|
144
117
|
return {
|
|
145
118
|
v: diff.reverse()
|
|
146
119
|
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
120
|
+
}
|
|
121
|
+
diag = _diag;
|
|
122
|
+
},
|
|
123
|
+
_ret;
|
|
124
|
+
for (var diag = -size; diag <= size; diag += 2) {
|
|
125
|
+
_ret = _loop(diag);
|
|
126
|
+
if (_ret) return _ret.v;
|
|
151
127
|
}
|
|
152
|
-
|
|
153
128
|
if (size % 2 == 0) history.push(frontier.slice());
|
|
154
129
|
}
|
|
155
|
-
|
|
156
130
|
return [range.slice(start, endA, start, endB)];
|
|
157
131
|
}
|
|
158
|
-
|
|
159
132
|
var Span = function () {
|
|
160
133
|
function Span(length, data) {
|
|
161
134
|
_classCallCheck(this, Span);
|
|
162
|
-
|
|
163
135
|
this.length = length;
|
|
164
136
|
this.data = data;
|
|
165
137
|
}
|
|
166
|
-
|
|
167
138
|
_createClass(Span, [{
|
|
168
139
|
key: "cut",
|
|
169
140
|
value: function cut(length) {
|
|
@@ -175,15 +146,13 @@ var Span = function () {
|
|
|
175
146
|
if (from == to) return Span.none;
|
|
176
147
|
if (from == 0 && to == Span.len(spans)) return spans;
|
|
177
148
|
var result = [];
|
|
178
|
-
|
|
179
149
|
for (var i = 0, off = 0; off < to; i++) {
|
|
180
150
|
var span = spans[i],
|
|
181
|
-
|
|
151
|
+
end = off + span.length;
|
|
182
152
|
var overlap = Math.min(to, end) - Math.max(from, off);
|
|
183
153
|
if (overlap > 0) result.push(span.cut(overlap));
|
|
184
154
|
off = end;
|
|
185
155
|
}
|
|
186
|
-
|
|
187
156
|
return result;
|
|
188
157
|
}
|
|
189
158
|
}, {
|
|
@@ -195,35 +164,23 @@ var Span = function () {
|
|
|
195
164
|
if (combined == null) return a.concat(b);
|
|
196
165
|
var result = a.slice(0, a.length - 1);
|
|
197
166
|
result.push(new Span(a[a.length - 1].length + b[0].length, combined));
|
|
198
|
-
|
|
199
|
-
for (var i = 1; i < b.length; i++) {
|
|
200
|
-
result.push(b[i]);
|
|
201
|
-
}
|
|
202
|
-
|
|
167
|
+
for (var i = 1; i < b.length; i++) result.push(b[i]);
|
|
203
168
|
return result;
|
|
204
169
|
}
|
|
205
170
|
}, {
|
|
206
171
|
key: "len",
|
|
207
172
|
value: function len(spans) {
|
|
208
173
|
var len = 0;
|
|
209
|
-
|
|
210
|
-
for (var i = 0; i < spans.length; i++) {
|
|
211
|
-
len += spans[i].length;
|
|
212
|
-
}
|
|
213
|
-
|
|
174
|
+
for (var i = 0; i < spans.length; i++) len += spans[i].length;
|
|
214
175
|
return len;
|
|
215
176
|
}
|
|
216
177
|
}]);
|
|
217
|
-
|
|
218
178
|
return Span;
|
|
219
179
|
}();
|
|
220
|
-
|
|
221
180
|
Span.none = [];
|
|
222
|
-
|
|
223
181
|
var Change = function () {
|
|
224
182
|
function Change(fromA, toA, fromB, toB, deleted, inserted) {
|
|
225
183
|
_classCallCheck(this, Change);
|
|
226
|
-
|
|
227
184
|
this.fromA = fromA;
|
|
228
185
|
this.toA = toA;
|
|
229
186
|
this.fromB = fromB;
|
|
@@ -231,7 +188,6 @@ var Change = function () {
|
|
|
231
188
|
this.deleted = deleted;
|
|
232
189
|
this.inserted = inserted;
|
|
233
190
|
}
|
|
234
|
-
|
|
235
191
|
_createClass(Change, [{
|
|
236
192
|
key: "lenA",
|
|
237
193
|
get: function get() {
|
|
@@ -254,7 +210,6 @@ var Change = function () {
|
|
|
254
210
|
if (x.length == 0) return y;
|
|
255
211
|
if (y.length == 0) return x;
|
|
256
212
|
var result = [];
|
|
257
|
-
|
|
258
213
|
for (var iX = 0, iY = 0, curX = x[0], curY = y[0];;) {
|
|
259
214
|
if (!curX && !curY) {
|
|
260
215
|
return result;
|
|
@@ -264,97 +219,79 @@ var Change = function () {
|
|
|
264
219
|
curX = iX++ == x.length ? null : x[iX];
|
|
265
220
|
} else if (curY && (!curX || curY.toA < curX.fromB)) {
|
|
266
221
|
var _off = iX ? x[iX - 1].toB - x[iX - 1].toA : 0;
|
|
267
|
-
|
|
268
222
|
result.push(_off == 0 ? curY : new Change(curY.fromA - _off, curY.toA - _off, curY.fromB, curY.toB, curY.deleted, curY.inserted));
|
|
269
223
|
curY = iY++ == y.length ? null : y[iY];
|
|
270
224
|
} else {
|
|
271
225
|
var pos = Math.min(curX.fromB, curY.fromA);
|
|
272
226
|
var fromA = Math.min(curX.fromA, curY.fromA - (iX ? x[iX - 1].toB - x[iX - 1].toA : 0)),
|
|
273
|
-
|
|
227
|
+
toA = fromA;
|
|
274
228
|
var fromB = Math.min(curY.fromB, curX.fromB + (iY ? y[iY - 1].toB - y[iY - 1].toA : 0)),
|
|
275
|
-
|
|
229
|
+
toB = fromB;
|
|
276
230
|
var deleted = Span.none,
|
|
277
|
-
|
|
231
|
+
inserted = Span.none;
|
|
278
232
|
var enteredX = false,
|
|
279
|
-
|
|
280
|
-
|
|
233
|
+
enteredY = false;
|
|
281
234
|
for (;;) {
|
|
282
235
|
var nextX = !curX ? 2e8 : pos >= curX.fromB ? curX.toB : curX.fromB;
|
|
283
236
|
var nextY = !curY ? 2e8 : pos >= curY.fromA ? curY.toA : curY.fromA;
|
|
284
237
|
var next = Math.min(nextX, nextY);
|
|
285
238
|
var inX = curX && pos >= curX.fromB,
|
|
286
|
-
|
|
239
|
+
inY = curY && pos >= curY.fromA;
|
|
287
240
|
if (!inX && !inY) break;
|
|
288
|
-
|
|
289
241
|
if (inX && pos == curX.fromB && !enteredX) {
|
|
290
242
|
deleted = Span.join(deleted, curX.deleted, combine);
|
|
291
243
|
toA += curX.lenA;
|
|
292
244
|
enteredX = true;
|
|
293
245
|
}
|
|
294
|
-
|
|
295
246
|
if (inX && !inY) {
|
|
296
247
|
inserted = Span.join(inserted, Span.slice(curX.inserted, pos - curX.fromB, next - curX.fromB), combine);
|
|
297
248
|
toB += next - pos;
|
|
298
249
|
}
|
|
299
|
-
|
|
300
250
|
if (inY && pos == curY.fromA && !enteredY) {
|
|
301
251
|
inserted = Span.join(inserted, curY.inserted, combine);
|
|
302
252
|
toB += curY.lenB;
|
|
303
253
|
enteredY = true;
|
|
304
254
|
}
|
|
305
|
-
|
|
306
255
|
if (inY && !inX) {
|
|
307
256
|
deleted = Span.join(deleted, Span.slice(curY.deleted, pos - curY.fromA, next - curY.fromA), combine);
|
|
308
257
|
toA += next - pos;
|
|
309
258
|
}
|
|
310
|
-
|
|
311
259
|
if (inX && next == curX.toB) {
|
|
312
260
|
curX = iX++ == x.length ? null : x[iX];
|
|
313
261
|
enteredX = false;
|
|
314
262
|
}
|
|
315
|
-
|
|
316
263
|
if (inY && next == curY.toA) {
|
|
317
264
|
curY = iY++ == y.length ? null : y[iY];
|
|
318
265
|
enteredY = false;
|
|
319
266
|
}
|
|
320
|
-
|
|
321
267
|
pos = next;
|
|
322
268
|
}
|
|
323
|
-
|
|
324
269
|
if (fromA < toA || fromB < toB) result.push(new Change(fromA, toA, fromB, toB, deleted, inserted));
|
|
325
270
|
}
|
|
326
271
|
}
|
|
327
272
|
}
|
|
328
273
|
}]);
|
|
329
|
-
|
|
330
274
|
return Change;
|
|
331
275
|
}();
|
|
332
|
-
|
|
333
276
|
var letter;
|
|
334
|
-
|
|
335
277
|
try {
|
|
336
278
|
letter = new RegExp("[\\p{Alphabetic}_]", "u");
|
|
337
279
|
} catch (_) {}
|
|
338
|
-
|
|
339
280
|
var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
|
|
340
|
-
|
|
341
281
|
function isLetter(code) {
|
|
342
282
|
if (code < 128) return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 79 && code <= 122;
|
|
343
283
|
var ch = String.fromCharCode(code);
|
|
344
284
|
if (letter) return letter.test(ch);
|
|
345
285
|
return ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch);
|
|
346
286
|
}
|
|
347
|
-
|
|
348
287
|
function getText(frag, start, end) {
|
|
349
288
|
var out = "";
|
|
350
|
-
|
|
351
289
|
function convert(frag, start, end) {
|
|
352
290
|
for (var i = 0, off = 0; i < frag.childCount; i++) {
|
|
353
291
|
var child = frag.child(i),
|
|
354
|
-
|
|
292
|
+
endOff = off + child.nodeSize;
|
|
355
293
|
var from = Math.max(off, start),
|
|
356
|
-
|
|
357
|
-
|
|
294
|
+
to = Math.min(endOff, end);
|
|
358
295
|
if (from < to) {
|
|
359
296
|
if (child.isText) {
|
|
360
297
|
out += child.text.slice(Math.max(0, start - off), Math.min(child.text.length, end - off));
|
|
@@ -366,104 +303,76 @@ function getText(frag, start, end) {
|
|
|
366
303
|
if (to == endOff) out += " ";
|
|
367
304
|
}
|
|
368
305
|
}
|
|
369
|
-
|
|
370
306
|
off = endOff;
|
|
371
307
|
}
|
|
372
308
|
}
|
|
373
|
-
|
|
374
309
|
convert(frag, start, end);
|
|
375
310
|
return out;
|
|
376
311
|
}
|
|
377
|
-
|
|
378
312
|
var MAX_SIMPLIFY_DISTANCE = 30;
|
|
379
|
-
|
|
380
313
|
function simplifyChanges(changes, doc) {
|
|
381
314
|
var result = [];
|
|
382
|
-
|
|
383
315
|
for (var i = 0; i < changes.length; i++) {
|
|
384
316
|
var end = changes[i].toB,
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
while (i < changes.length - 1 && changes[i + 1].fromB <= end + MAX_SIMPLIFY_DISTANCE) {
|
|
388
|
-
end = changes[++i].toB;
|
|
389
|
-
}
|
|
390
|
-
|
|
317
|
+
start = i;
|
|
318
|
+
while (i < changes.length - 1 && changes[i + 1].fromB <= end + MAX_SIMPLIFY_DISTANCE) end = changes[++i].toB;
|
|
391
319
|
simplifyAdjacentChanges(changes, start, i + 1, doc, result);
|
|
392
320
|
}
|
|
393
|
-
|
|
394
321
|
return result;
|
|
395
322
|
}
|
|
396
|
-
|
|
397
323
|
function simplifyAdjacentChanges(changes, from, to, doc, target) {
|
|
398
324
|
var start = Math.max(0, changes[from].fromB - MAX_SIMPLIFY_DISTANCE);
|
|
399
325
|
var end = Math.min(doc.content.size, changes[to - 1].toB + MAX_SIMPLIFY_DISTANCE);
|
|
400
326
|
var text = getText(doc.content, start, end);
|
|
401
|
-
|
|
402
327
|
for (var i = from; i < to; i++) {
|
|
403
328
|
var startI = i,
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
329
|
+
last = changes[i],
|
|
330
|
+
deleted = last.lenA,
|
|
331
|
+
inserted = last.lenB;
|
|
408
332
|
while (i < to - 1) {
|
|
409
333
|
var next = changes[i + 1],
|
|
410
|
-
|
|
334
|
+
boundary = false;
|
|
411
335
|
var prevLetter = last.toB == end ? false : isLetter(text.charCodeAt(last.toB - 1 - start));
|
|
412
|
-
|
|
413
336
|
for (var pos = last.toB; !boundary && pos < next.fromB; pos++) {
|
|
414
337
|
var nextLetter = pos == end ? false : isLetter(text.charCodeAt(pos - start));
|
|
415
338
|
if ((!prevLetter || !nextLetter) && pos != changes[startI].fromB) boundary = true;
|
|
416
339
|
prevLetter = nextLetter;
|
|
417
340
|
}
|
|
418
|
-
|
|
419
341
|
if (boundary) break;
|
|
420
342
|
deleted += next.lenA;
|
|
421
343
|
inserted += next.lenB;
|
|
422
344
|
last = next;
|
|
423
345
|
i++;
|
|
424
346
|
}
|
|
425
|
-
|
|
426
347
|
if (inserted > 0 && deleted > 0 && !(inserted == 1 && deleted == 1)) {
|
|
427
348
|
var _from = changes[startI].fromB,
|
|
428
|
-
|
|
429
|
-
if (_from < end && isLetter(text.charCodeAt(_from - start))) while (_from > start && isLetter(text.charCodeAt(_from - 1 - start)))
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
if (_to > start && isLetter(text.charCodeAt(_to - 1 - start))) while (_to < end && isLetter(text.charCodeAt(_to - start))) {
|
|
433
|
-
_to++;
|
|
434
|
-
}
|
|
349
|
+
_to = changes[i].toB;
|
|
350
|
+
if (_from < end && isLetter(text.charCodeAt(_from - start))) while (_from > start && isLetter(text.charCodeAt(_from - 1 - start))) _from--;
|
|
351
|
+
if (_to > start && isLetter(text.charCodeAt(_to - 1 - start))) while (_to < end && isLetter(text.charCodeAt(_to - start))) _to++;
|
|
435
352
|
var joined = fillChange(changes.slice(startI, i + 1), _from, _to);
|
|
436
|
-
|
|
437
353
|
var _last = target.length ? target[target.length - 1] : null;
|
|
438
|
-
|
|
439
354
|
if (_last && _last.toA == joined.fromA) target[target.length - 1] = new Change(_last.fromA, joined.toA, _last.fromB, joined.toB, _last.deleted.concat(joined.deleted), _last.inserted.concat(joined.inserted));else target.push(joined);
|
|
440
355
|
} else {
|
|
441
|
-
for (var j = startI; j <= i; j++)
|
|
442
|
-
target.push(changes[j]);
|
|
443
|
-
}
|
|
356
|
+
for (var j = startI; j <= i; j++) target.push(changes[j]);
|
|
444
357
|
}
|
|
445
358
|
}
|
|
446
|
-
|
|
447
359
|
return changes;
|
|
448
360
|
}
|
|
449
|
-
|
|
450
361
|
function combine(a, b) {
|
|
451
362
|
return a === b ? a : null;
|
|
452
363
|
}
|
|
453
|
-
|
|
454
364
|
function fillChange(changes, fromB, toB) {
|
|
455
365
|
var fromA = changes[0].fromA - (changes[0].fromB - fromB);
|
|
456
366
|
var last = changes[changes.length - 1];
|
|
457
367
|
var toA = last.toA + (toB - last.toB);
|
|
458
368
|
var deleted = Span.none,
|
|
459
|
-
|
|
369
|
+
inserted = Span.none;
|
|
460
370
|
var delData = (changes[0].deleted.length ? changes[0].deleted : changes[0].inserted)[0].data;
|
|
461
371
|
var insData = (changes[0].inserted.length ? changes[0].inserted : changes[0].deleted)[0].data;
|
|
462
|
-
|
|
463
372
|
for (var posA = fromA, posB = fromB, i = 0;; i++) {
|
|
464
373
|
var next = i == changes.length ? null : changes[i];
|
|
465
374
|
var endA = next ? next.fromA : toA,
|
|
466
|
-
|
|
375
|
+
endB = next ? next.fromB : toB;
|
|
467
376
|
if (endA > posA) deleted = Span.join(deleted, [new Span(endA - posA, delData)], combine);
|
|
468
377
|
if (endB > posB) inserted = Span.join(inserted, [new Span(endB - posB, insData)], combine);
|
|
469
378
|
if (!next) break;
|
|
@@ -474,26 +383,20 @@ function fillChange(changes, fromB, toB) {
|
|
|
474
383
|
posA = next.toA;
|
|
475
384
|
posB = next.toB;
|
|
476
385
|
}
|
|
477
|
-
|
|
478
386
|
return new Change(fromA, toA, fromB, toB, deleted, inserted);
|
|
479
387
|
}
|
|
480
|
-
|
|
481
388
|
var ChangeSet = function () {
|
|
482
389
|
function ChangeSet(config, changes) {
|
|
483
390
|
_classCallCheck(this, ChangeSet);
|
|
484
|
-
|
|
485
391
|
this.config = config;
|
|
486
392
|
this.changes = changes;
|
|
487
393
|
}
|
|
488
|
-
|
|
489
394
|
_createClass(ChangeSet, [{
|
|
490
395
|
key: "addSteps",
|
|
491
396
|
value: function addSteps(newDoc, maps, data) {
|
|
492
397
|
var _this = this;
|
|
493
|
-
|
|
494
398
|
var stepChanges = [];
|
|
495
|
-
|
|
496
|
-
var _loop = function _loop(i) {
|
|
399
|
+
var _loop2 = function _loop2() {
|
|
497
400
|
var d = Array.isArray(data) ? data[i] : data;
|
|
498
401
|
var off = 0;
|
|
499
402
|
maps[i].forEach(function (fromA, toA, fromB, toB) {
|
|
@@ -501,54 +404,41 @@ var ChangeSet = function () {
|
|
|
501
404
|
off = toB - fromB - (toA - fromA);
|
|
502
405
|
});
|
|
503
406
|
};
|
|
504
|
-
|
|
505
407
|
for (var i = 0; i < maps.length; i++) {
|
|
506
|
-
|
|
408
|
+
_loop2();
|
|
507
409
|
}
|
|
508
|
-
|
|
509
410
|
if (stepChanges.length == 0) return this;
|
|
510
411
|
var newChanges = mergeAll(stepChanges, this.config.combine);
|
|
511
412
|
var changes = Change.merge(this.changes, newChanges, this.config.combine);
|
|
512
413
|
var updated = changes;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
414
|
+
var _loop3 = function _loop3(_i3) {
|
|
415
|
+
var change = updated[_i3];
|
|
416
|
+
if (change.fromA == change.toA || change.fromB == change.toB || !newChanges.some(function (r) {
|
|
417
|
+
return r.toB > change.fromB && r.fromB < change.toB;
|
|
418
|
+
})) {
|
|
419
|
+
_i2 = _i3;
|
|
420
|
+
return 0;
|
|
421
|
+
}
|
|
422
|
+
var diff = computeDiff(_this.config.doc.content, newDoc.content, change, _this.config.encoder);
|
|
423
|
+
if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB) {
|
|
424
|
+
_i2 = _i3;
|
|
425
|
+
return 0;
|
|
426
|
+
}
|
|
427
|
+
if (updated == changes) updated = changes.slice();
|
|
428
|
+
if (diff.length == 1) {
|
|
429
|
+
updated[_i3] = diff[0];
|
|
430
|
+
} else {
|
|
431
|
+
var _updated;
|
|
432
|
+
(_updated = updated).splice.apply(_updated, [_i3, 1].concat(_toConsumableArray(diff)));
|
|
433
|
+
_i3 += diff.length - 1;
|
|
434
|
+
}
|
|
527
435
|
_i2 = _i3;
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (updated == changes) updated = changes.slice();
|
|
532
|
-
|
|
533
|
-
if (diff.length == 1) {
|
|
534
|
-
updated[_i3] = diff[0];
|
|
535
|
-
} else {
|
|
536
|
-
var _updated;
|
|
537
|
-
|
|
538
|
-
(_updated = updated).splice.apply(_updated, [_i3, 1].concat(_toConsumableArray(diff)));
|
|
539
|
-
|
|
540
|
-
_i3 += diff.length - 1;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
_i2 = _i3;
|
|
544
|
-
};
|
|
545
|
-
|
|
436
|
+
},
|
|
437
|
+
_ret2;
|
|
546
438
|
for (var _i2 = 0; _i2 < updated.length; _i2++) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (_ret2 === "continue") continue;
|
|
439
|
+
_ret2 = _loop3(_i2);
|
|
440
|
+
if (_ret2 === 0) continue;
|
|
550
441
|
}
|
|
551
|
-
|
|
552
442
|
return new ChangeSet(this.config, updated);
|
|
553
443
|
}
|
|
554
444
|
}, {
|
|
@@ -563,7 +453,6 @@ var ChangeSet = function () {
|
|
|
563
453
|
var newData = f(span);
|
|
564
454
|
return newData === span.data ? span : new Span(span.length, newData);
|
|
565
455
|
};
|
|
566
|
-
|
|
567
456
|
return new ChangeSet(this.config, this.changes.map(function (ch) {
|
|
568
457
|
return new Change(ch.fromA, ch.toA, ch.fromB, ch.toB, ch.deleted.map(mapSpan), ch.inserted.map(mapSpan));
|
|
569
458
|
}));
|
|
@@ -574,27 +463,21 @@ var ChangeSet = function () {
|
|
|
574
463
|
if (b == this) return null;
|
|
575
464
|
var touched = maps && touchedRange(maps);
|
|
576
465
|
var moved = touched ? touched.toB - touched.fromB - (touched.toA - touched.fromA) : 0;
|
|
577
|
-
|
|
578
466
|
function map(p) {
|
|
579
467
|
return !touched || p <= touched.fromA ? p : p + moved;
|
|
580
468
|
}
|
|
581
|
-
|
|
582
469
|
var from = touched ? touched.fromB : 2e8,
|
|
583
|
-
|
|
584
|
-
|
|
470
|
+
to = touched ? touched.toB : -2e8;
|
|
585
471
|
function add(start) {
|
|
586
472
|
var end = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : start;
|
|
587
473
|
from = Math.min(start, from);
|
|
588
474
|
to = Math.max(end, to);
|
|
589
475
|
}
|
|
590
|
-
|
|
591
476
|
var rA = this.changes,
|
|
592
|
-
|
|
593
|
-
|
|
477
|
+
rB = b.changes;
|
|
594
478
|
for (var iA = 0, iB = 0; iA < rA.length && iB < rB.length;) {
|
|
595
479
|
var rangeA = rA[iA],
|
|
596
|
-
|
|
597
|
-
|
|
480
|
+
rangeB = rB[iB];
|
|
598
481
|
if (rangeA && rangeB && sameRanges(rangeA, rangeB, map)) {
|
|
599
482
|
iA++;
|
|
600
483
|
iB++;
|
|
@@ -606,7 +489,6 @@ var ChangeSet = function () {
|
|
|
606
489
|
iA++;
|
|
607
490
|
}
|
|
608
491
|
}
|
|
609
|
-
|
|
610
492
|
return from <= to ? {
|
|
611
493
|
from: from,
|
|
612
494
|
to: to
|
|
@@ -618,18 +500,17 @@ var ChangeSet = function () {
|
|
|
618
500
|
var combine = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (a, b) {
|
|
619
501
|
return a === b ? a : null;
|
|
620
502
|
};
|
|
503
|
+
var tokenEncoder = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DefaultEncoder;
|
|
621
504
|
return new ChangeSet({
|
|
622
505
|
combine: combine,
|
|
623
|
-
doc: doc
|
|
506
|
+
doc: doc,
|
|
507
|
+
encoder: tokenEncoder
|
|
624
508
|
}, []);
|
|
625
509
|
}
|
|
626
510
|
}]);
|
|
627
|
-
|
|
628
511
|
return ChangeSet;
|
|
629
512
|
}();
|
|
630
|
-
|
|
631
513
|
ChangeSet.computeDiff = computeDiff;
|
|
632
|
-
|
|
633
514
|
function mergeAll(ranges, combine) {
|
|
634
515
|
var start = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
635
516
|
var end = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ranges.length;
|
|
@@ -637,31 +518,25 @@ function mergeAll(ranges, combine) {
|
|
|
637
518
|
var mid = start + end >> 1;
|
|
638
519
|
return Change.merge(mergeAll(ranges, combine, start, mid), mergeAll(ranges, combine, mid, end), combine);
|
|
639
520
|
}
|
|
640
|
-
|
|
641
521
|
function endRange(maps) {
|
|
642
522
|
var from = 2e8,
|
|
643
|
-
|
|
644
|
-
|
|
523
|
+
to = -2e8;
|
|
645
524
|
for (var i = 0; i < maps.length; i++) {
|
|
646
525
|
var map = maps[i];
|
|
647
|
-
|
|
648
526
|
if (from != 2e8) {
|
|
649
527
|
from = map.map(from, -1);
|
|
650
528
|
to = map.map(to, 1);
|
|
651
529
|
}
|
|
652
|
-
|
|
653
530
|
map.forEach(function (_s, _e, start, end) {
|
|
654
531
|
from = Math.min(from, start);
|
|
655
532
|
to = Math.max(to, end);
|
|
656
533
|
});
|
|
657
534
|
}
|
|
658
|
-
|
|
659
535
|
return from == 2e8 ? null : {
|
|
660
536
|
from: from,
|
|
661
537
|
to: to
|
|
662
538
|
};
|
|
663
539
|
}
|
|
664
|
-
|
|
665
540
|
function touchedRange(maps) {
|
|
666
541
|
var b = endRange(maps);
|
|
667
542
|
if (!b) return null;
|
|
@@ -675,21 +550,14 @@ function touchedRange(maps) {
|
|
|
675
550
|
toB: b.to
|
|
676
551
|
};
|
|
677
552
|
}
|
|
678
|
-
|
|
679
553
|
function sameRanges(a, b, map) {
|
|
680
554
|
return map(a.fromB) == b.fromB && map(a.toB) == b.toB && sameSpans(a.deleted, b.deleted) && sameSpans(a.inserted, b.inserted);
|
|
681
555
|
}
|
|
682
|
-
|
|
683
556
|
function sameSpans(a, b) {
|
|
684
557
|
if (a.length != b.length) return false;
|
|
685
|
-
|
|
686
|
-
for (var i = 0; i < a.length; i++) {
|
|
687
|
-
if (a[i].length != b[i].length || a[i].data !== b[i].data) return false;
|
|
688
|
-
}
|
|
689
|
-
|
|
558
|
+
for (var i = 0; i < a.length; i++) if (a[i].length != b[i].length || a[i].data !== b[i].data) return false;
|
|
690
559
|
return true;
|
|
691
560
|
}
|
|
692
|
-
|
|
693
561
|
exports.Change = Change;
|
|
694
562
|
exports.ChangeSet = ChangeSet;
|
|
695
563
|
exports.Span = Span;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Node } from 'prosemirror-model';
|
|
1
|
+
import { Mark, Node } from 'prosemirror-model';
|
|
2
2
|
import { StepMap } from 'prosemirror-transform';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -41,7 +41,7 @@ declare class Change<Data = any> {
|
|
|
41
41
|
readonly deleted: readonly Span<Data>[];
|
|
42
42
|
/**
|
|
43
43
|
Data associated with the inserted content. Length adds up to
|
|
44
|
-
`this.toB - this.
|
|
44
|
+
`this.toB - this.fromB`.
|
|
45
45
|
*/
|
|
46
46
|
readonly inserted: readonly Span<Data>[];
|
|
47
47
|
/**
|
|
@@ -52,6 +52,38 @@ declare class Change<Data = any> {
|
|
|
52
52
|
static merge<Data>(x: readonly Change<Data>[], y: readonly Change<Data>[], combine: (dataA: Data, dataB: Data) => Data): readonly Change<Data>[];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
A token encoder can be passed when creating a `ChangeSet` in order
|
|
57
|
+
to influence the way the library runs its diffing algorithm. The
|
|
58
|
+
encoder determines how document tokens (such as nodes and
|
|
59
|
+
characters) are encoded and compared.
|
|
60
|
+
|
|
61
|
+
Note that both the encoding and the comparison may run a lot, and
|
|
62
|
+
doing non-trivial work in these functions could impact
|
|
63
|
+
performance.
|
|
64
|
+
*/
|
|
65
|
+
interface TokenEncoder<T> {
|
|
66
|
+
/**
|
|
67
|
+
Encode a given character, with the given marks applied.
|
|
68
|
+
*/
|
|
69
|
+
encodeCharacter(char: number, marks: readonly Mark[]): T;
|
|
70
|
+
/**
|
|
71
|
+
Encode the start of a node or, if this is a leaf node, the
|
|
72
|
+
entire node.
|
|
73
|
+
*/
|
|
74
|
+
encodeNodeStart(node: Node): T;
|
|
75
|
+
/**
|
|
76
|
+
Encode the end token for the given node. It is valid to encode
|
|
77
|
+
every end token in the same way.
|
|
78
|
+
*/
|
|
79
|
+
encodeNodeEnd(node: Node): T;
|
|
80
|
+
/**
|
|
81
|
+
Compare the given tokens. Should return true when they count as
|
|
82
|
+
equal.
|
|
83
|
+
*/
|
|
84
|
+
compareTokens(a: T, b: T): boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
/**
|
|
56
88
|
Simplifies a set of changes for presentation. This makes the
|
|
57
89
|
assumption that having both insertions and deletions within a word
|
|
@@ -108,11 +140,17 @@ declare class ChangeSet<Data = any> {
|
|
|
108
140
|
} | null;
|
|
109
141
|
/**
|
|
110
142
|
Create a changeset with the given base object and configuration.
|
|
143
|
+
|
|
111
144
|
The `combine` function is used to compare and combine metadata—it
|
|
112
145
|
should return null when metadata isn't compatible, and a combined
|
|
113
146
|
version for a merged range when it is.
|
|
147
|
+
|
|
148
|
+
When given, a token encoder determines how document tokens are
|
|
149
|
+
serialized and compared when diffing the content produced by
|
|
150
|
+
changes. The default is to just compare nodes by name and text
|
|
151
|
+
by character, ignoring marks and attributes.
|
|
114
152
|
*/
|
|
115
|
-
static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data): ChangeSet<Data>;
|
|
153
|
+
static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>): ChangeSet<Data>;
|
|
116
154
|
}
|
|
117
155
|
|
|
118
|
-
export { Change, ChangeSet, Span, simplifyChanges };
|
|
156
|
+
export { Change, ChangeSet, Span, type TokenEncoder, simplifyChanges };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Node } from 'prosemirror-model';
|
|
1
|
+
import { Mark, Node } from 'prosemirror-model';
|
|
2
2
|
import { StepMap } from 'prosemirror-transform';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -41,7 +41,7 @@ declare class Change<Data = any> {
|
|
|
41
41
|
readonly deleted: readonly Span<Data>[];
|
|
42
42
|
/**
|
|
43
43
|
Data associated with the inserted content. Length adds up to
|
|
44
|
-
`this.toB - this.
|
|
44
|
+
`this.toB - this.fromB`.
|
|
45
45
|
*/
|
|
46
46
|
readonly inserted: readonly Span<Data>[];
|
|
47
47
|
/**
|
|
@@ -52,6 +52,38 @@ declare class Change<Data = any> {
|
|
|
52
52
|
static merge<Data>(x: readonly Change<Data>[], y: readonly Change<Data>[], combine: (dataA: Data, dataB: Data) => Data): readonly Change<Data>[];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
A token encoder can be passed when creating a `ChangeSet` in order
|
|
57
|
+
to influence the way the library runs its diffing algorithm. The
|
|
58
|
+
encoder determines how document tokens (such as nodes and
|
|
59
|
+
characters) are encoded and compared.
|
|
60
|
+
|
|
61
|
+
Note that both the encoding and the comparison may run a lot, and
|
|
62
|
+
doing non-trivial work in these functions could impact
|
|
63
|
+
performance.
|
|
64
|
+
*/
|
|
65
|
+
interface TokenEncoder<T> {
|
|
66
|
+
/**
|
|
67
|
+
Encode a given character, with the given marks applied.
|
|
68
|
+
*/
|
|
69
|
+
encodeCharacter(char: number, marks: readonly Mark[]): T;
|
|
70
|
+
/**
|
|
71
|
+
Encode the start of a node or, if this is a leaf node, the
|
|
72
|
+
entire node.
|
|
73
|
+
*/
|
|
74
|
+
encodeNodeStart(node: Node): T;
|
|
75
|
+
/**
|
|
76
|
+
Encode the end token for the given node. It is valid to encode
|
|
77
|
+
every end token in the same way.
|
|
78
|
+
*/
|
|
79
|
+
encodeNodeEnd(node: Node): T;
|
|
80
|
+
/**
|
|
81
|
+
Compare the given tokens. Should return true when they count as
|
|
82
|
+
equal.
|
|
83
|
+
*/
|
|
84
|
+
compareTokens(a: T, b: T): boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
/**
|
|
56
88
|
Simplifies a set of changes for presentation. This makes the
|
|
57
89
|
assumption that having both insertions and deletions within a word
|
|
@@ -108,11 +140,17 @@ declare class ChangeSet<Data = any> {
|
|
|
108
140
|
} | null;
|
|
109
141
|
/**
|
|
110
142
|
Create a changeset with the given base object and configuration.
|
|
143
|
+
|
|
111
144
|
The `combine` function is used to compare and combine metadata—it
|
|
112
145
|
should return null when metadata isn't compatible, and a combined
|
|
113
146
|
version for a merged range when it is.
|
|
147
|
+
|
|
148
|
+
When given, a token encoder determines how document tokens are
|
|
149
|
+
serialized and compared when diffing the content produced by
|
|
150
|
+
changes. The default is to just compare nodes by name and text
|
|
151
|
+
by character, ignoring marks and attributes.
|
|
114
152
|
*/
|
|
115
|
-
static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data): ChangeSet<Data>;
|
|
153
|
+
static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>): ChangeSet<Data>;
|
|
116
154
|
}
|
|
117
155
|
|
|
118
|
-
export { Change, ChangeSet, Span, simplifyChanges };
|
|
156
|
+
export { Change, ChangeSet, Span, type TokenEncoder, simplifyChanges };
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
|
+
const DefaultEncoder = {
|
|
2
|
+
encodeCharacter: char => char,
|
|
3
|
+
encodeNodeStart: node => node.type.name,
|
|
4
|
+
encodeNodeEnd: () => -1,
|
|
5
|
+
compareTokens: (a, b) => a === b
|
|
6
|
+
};
|
|
1
7
|
// Convert the given range of a fragment to tokens, where node open
|
|
2
8
|
// tokens are encoded as strings holding the node name, characters as
|
|
3
9
|
// their character code, and node close tokens as -1.
|
|
4
|
-
function tokens(frag, start, end, target) {
|
|
10
|
+
function tokens(frag, encoder, start, end, target) {
|
|
5
11
|
for (let i = 0, off = 0; i < frag.childCount; i++) {
|
|
6
12
|
let child = frag.child(i), endOff = off + child.nodeSize;
|
|
7
13
|
let from = Math.max(off, start), to = Math.min(endOff, end);
|
|
8
14
|
if (from < to) {
|
|
9
15
|
if (child.isText) {
|
|
10
16
|
for (let j = from; j < to; j++)
|
|
11
|
-
target.push(child.text.charCodeAt(j - off));
|
|
17
|
+
target.push(encoder.encodeCharacter(child.text.charCodeAt(j - off), child.marks));
|
|
12
18
|
}
|
|
13
19
|
else if (child.isLeaf) {
|
|
14
|
-
target.push(child
|
|
20
|
+
target.push(encoder.encodeNodeStart(child));
|
|
15
21
|
}
|
|
16
22
|
else {
|
|
17
23
|
if (from == off)
|
|
18
|
-
target.push(child
|
|
19
|
-
tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
|
|
24
|
+
target.push(encoder.encodeNodeStart(child));
|
|
25
|
+
tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
|
|
20
26
|
if (to == endOff)
|
|
21
|
-
target.push(
|
|
27
|
+
target.push(encoder.encodeNodeEnd(child));
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
30
|
off = endOff;
|
|
@@ -37,16 +43,17 @@ const MAX_DIFF_SIZE = 5000;
|
|
|
37
43
|
function minUnchanged(sizeA, sizeB) {
|
|
38
44
|
return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)));
|
|
39
45
|
}
|
|
40
|
-
function computeDiff(fragA, fragB, range) {
|
|
41
|
-
let tokA = tokens(fragA, range.fromA, range.toA, []);
|
|
42
|
-
let tokB = tokens(fragB, range.fromB, range.toB, []);
|
|
46
|
+
function computeDiff(fragA, fragB, range, encoder = DefaultEncoder) {
|
|
47
|
+
let tokA = tokens(fragA, encoder, range.fromA, range.toA, []);
|
|
48
|
+
let tokB = tokens(fragB, encoder, range.fromB, range.toB, []);
|
|
43
49
|
// Scan from both sides to cheaply eliminate work
|
|
44
50
|
let start = 0, endA = tokA.length, endB = tokB.length;
|
|
45
|
-
|
|
51
|
+
let cmp = encoder.compareTokens;
|
|
52
|
+
while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start]))
|
|
46
53
|
start++;
|
|
47
54
|
if (start == tokA.length && start == tokB.length)
|
|
48
55
|
return [];
|
|
49
|
-
while (endA > start && endB > start && tokA[endA - 1]
|
|
56
|
+
while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1]))
|
|
50
57
|
endA--, endB--;
|
|
51
58
|
// If the result is simple _or_ too big to cheaply compute, return
|
|
52
59
|
// the remaining region as the diff
|
|
@@ -65,7 +72,7 @@ function computeDiff(fragA, fragB, range) {
|
|
|
65
72
|
for (let diag = -size; diag <= size; diag += 2) {
|
|
66
73
|
let next = frontier[diag + 1 + max], prev = frontier[diag - 1 + max];
|
|
67
74
|
let x = next < prev ? prev : next + 1, y = x + diag;
|
|
68
|
-
while (x < lenA && y < lenB && tokA[start + x]
|
|
75
|
+
while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y]))
|
|
69
76
|
x++, y++;
|
|
70
77
|
frontier[diag + max] = x;
|
|
71
78
|
// Found a match
|
|
@@ -225,7 +232,7 @@ class Change {
|
|
|
225
232
|
deleted,
|
|
226
233
|
/**
|
|
227
234
|
Data associated with the inserted content. Length adds up to
|
|
228
|
-
`this.toB - this.
|
|
235
|
+
`this.toB - this.fromB`.
|
|
229
236
|
*/
|
|
230
237
|
inserted) {
|
|
231
238
|
this.fromA = fromA;
|
|
@@ -549,7 +556,7 @@ class ChangeSet {
|
|
|
549
556
|
// Only look at changes that touch newly added changed ranges
|
|
550
557
|
!newChanges.some(r => r.toB > change.fromB && r.fromB < change.toB))
|
|
551
558
|
continue;
|
|
552
|
-
let diff = computeDiff(this.config.doc.content, newDoc.content, change);
|
|
559
|
+
let diff = computeDiff(this.config.doc.content, newDoc.content, change, this.config.encoder);
|
|
553
560
|
// Fast path: If they are completely different, don't do anything
|
|
554
561
|
if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB)
|
|
555
562
|
continue;
|
|
@@ -622,12 +629,18 @@ class ChangeSet {
|
|
|
622
629
|
}
|
|
623
630
|
/**
|
|
624
631
|
Create a changeset with the given base object and configuration.
|
|
632
|
+
|
|
625
633
|
The `combine` function is used to compare and combine metadata—it
|
|
626
634
|
should return null when metadata isn't compatible, and a combined
|
|
627
635
|
version for a merged range when it is.
|
|
636
|
+
|
|
637
|
+
When given, a token encoder determines how document tokens are
|
|
638
|
+
serialized and compared when diffing the content produced by
|
|
639
|
+
changes. The default is to just compare nodes by name and text
|
|
640
|
+
by character, ignoring marks and attributes.
|
|
628
641
|
*/
|
|
629
|
-
static create(doc, combine = (a, b) => a === b ? a : null) {
|
|
630
|
-
return new ChangeSet({ combine, doc }, []);
|
|
642
|
+
static create(doc, combine = (a, b) => a === b ? a : null, tokenEncoder = DefaultEncoder) {
|
|
643
|
+
return new ChangeSet({ combine, doc, encoder: tokenEncoder }, []);
|
|
631
644
|
}
|
|
632
645
|
}
|
|
633
646
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prosemirror-changeset",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Distills a series of editing steps into deleted and added ranges",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@prosemirror/buildhelper": "^0.1.5",
|
|
31
31
|
"prosemirror-model": "^1.0.0",
|
|
32
|
-
"prosemirror-test-builder": "^1.0.0"
|
|
32
|
+
"prosemirror-test-builder": "^1.0.0",
|
|
33
|
+
"builddocs": "^1.0.8"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"test": "pm-runtests",
|
|
36
37
|
"prepare": "pm-buildhelper src/changeset.ts",
|
|
37
|
-
"build-readme": "
|
|
38
|
+
"build-readme": "builddocs --format markdown --main src/README.md src/changeset.ts > README.md"
|
|
38
39
|
}
|
|
39
40
|
}
|
package/src/README.md
CHANGED
package/src/change.ts
CHANGED
|
@@ -66,7 +66,7 @@ export class Change<Data = any> {
|
|
|
66
66
|
/// spans adds up to `this.toA - this.fromA`.
|
|
67
67
|
readonly deleted: readonly Span<Data>[],
|
|
68
68
|
/// Data associated with the inserted content. Length adds up to
|
|
69
|
-
/// `this.toB - this.
|
|
69
|
+
/// `this.toB - this.fromB`.
|
|
70
70
|
readonly inserted: readonly Span<Data>[]
|
|
71
71
|
) {}
|
|
72
72
|
|
package/src/changeset.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {Node} from "prosemirror-model"
|
|
2
2
|
import {StepMap} from "prosemirror-transform"
|
|
3
|
-
import {computeDiff} from "./diff"
|
|
3
|
+
import {computeDiff, TokenEncoder, DefaultEncoder} from "./diff"
|
|
4
4
|
import {Change, Span} from "./change"
|
|
5
5
|
export {Change, Span}
|
|
6
6
|
export {simplifyChanges} from "./simplify"
|
|
7
|
+
export {TokenEncoder}
|
|
7
8
|
|
|
8
9
|
/// A change set tracks the changes to a document from a given point
|
|
9
10
|
/// in the past. It condenses a number of step maps down to a flat
|
|
@@ -13,7 +14,11 @@ export class ChangeSet<Data = any> {
|
|
|
13
14
|
/// @internal
|
|
14
15
|
constructor(
|
|
15
16
|
/// @internal
|
|
16
|
-
readonly config: {
|
|
17
|
+
readonly config: {
|
|
18
|
+
doc: Node,
|
|
19
|
+
combine: (dataA: Data, dataB: Data) => Data,
|
|
20
|
+
encoder: TokenEncoder<any>
|
|
21
|
+
},
|
|
17
22
|
/// Replaced regions.
|
|
18
23
|
readonly changes: readonly Change<Data>[]
|
|
19
24
|
) {}
|
|
@@ -69,7 +74,7 @@ export class ChangeSet<Data = any> {
|
|
|
69
74
|
if (change.fromA == change.toA || change.fromB == change.toB ||
|
|
70
75
|
// Only look at changes that touch newly added changed ranges
|
|
71
76
|
!newChanges.some(r => r.toB > change.fromB && r.fromB < change.toB)) continue
|
|
72
|
-
let diff = computeDiff(this.config.doc.content, newDoc.content, change)
|
|
77
|
+
let diff = computeDiff(this.config.doc.content, newDoc.content, change, this.config.encoder)
|
|
73
78
|
|
|
74
79
|
// Fast path: If they are completely different, don't do anything
|
|
75
80
|
if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB)
|
|
@@ -132,11 +137,21 @@ export class ChangeSet<Data = any> {
|
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
/// Create a changeset with the given base object and configuration.
|
|
140
|
+
///
|
|
135
141
|
/// The `combine` function is used to compare and combine metadata—it
|
|
136
142
|
/// should return null when metadata isn't compatible, and a combined
|
|
137
143
|
/// version for a merged range when it is.
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
///
|
|
145
|
+
/// When given, a token encoder determines how document tokens are
|
|
146
|
+
/// serialized and compared when diffing the content produced by
|
|
147
|
+
/// changes. The default is to just compare nodes by name and text
|
|
148
|
+
/// by character, ignoring marks and attributes.
|
|
149
|
+
static create<Data = any>(
|
|
150
|
+
doc: Node,
|
|
151
|
+
combine: (dataA: Data, dataB: Data) => Data = (a, b) => a === b ? a : null as any,
|
|
152
|
+
tokenEncoder: TokenEncoder<any> = DefaultEncoder
|
|
153
|
+
) {
|
|
154
|
+
return new ChangeSet({combine, doc, encoder: tokenEncoder}, [])
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
/// Exported for testing @internal
|
package/src/diff.ts
CHANGED
|
@@ -1,22 +1,51 @@
|
|
|
1
|
-
import {Fragment} from "prosemirror-model"
|
|
1
|
+
import {Fragment, Node, Mark} from "prosemirror-model"
|
|
2
2
|
import {Change} from "./change"
|
|
3
3
|
|
|
4
|
+
/// A token encoder can be passed when creating a `ChangeSet` in order
|
|
5
|
+
/// to influence the way the library runs its diffing algorithm. The
|
|
6
|
+
/// encoder determines how document tokens (such as nodes and
|
|
7
|
+
/// characters) are encoded and compared.
|
|
8
|
+
///
|
|
9
|
+
/// Note that both the encoding and the comparison may run a lot, and
|
|
10
|
+
/// doing non-trivial work in these functions could impact
|
|
11
|
+
/// performance.
|
|
12
|
+
export interface TokenEncoder<T> {
|
|
13
|
+
/// Encode a given character, with the given marks applied.
|
|
14
|
+
encodeCharacter(char: number, marks: readonly Mark[]): T
|
|
15
|
+
/// Encode the start of a node or, if this is a leaf node, the
|
|
16
|
+
/// entire node.
|
|
17
|
+
encodeNodeStart(node: Node): T
|
|
18
|
+
/// Encode the end token for the given node. It is valid to encode
|
|
19
|
+
/// every end token in the same way.
|
|
20
|
+
encodeNodeEnd(node: Node): T
|
|
21
|
+
/// Compare the given tokens. Should return true when they count as
|
|
22
|
+
/// equal.
|
|
23
|
+
compareTokens(a: T, b: T): boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const DefaultEncoder: TokenEncoder<number | string> = {
|
|
27
|
+
encodeCharacter: char => char,
|
|
28
|
+
encodeNodeStart: node => node.type.name,
|
|
29
|
+
encodeNodeEnd: () => -1,
|
|
30
|
+
compareTokens: (a, b) => a === b
|
|
31
|
+
}
|
|
32
|
+
|
|
4
33
|
// Convert the given range of a fragment to tokens, where node open
|
|
5
34
|
// tokens are encoded as strings holding the node name, characters as
|
|
6
35
|
// their character code, and node close tokens as -1.
|
|
7
|
-
function tokens(frag: Fragment, start: number, end: number, target:
|
|
36
|
+
function tokens<T>(frag: Fragment, encoder: TokenEncoder<T>, start: number, end: number, target: T[]) {
|
|
8
37
|
for (let i = 0, off = 0; i < frag.childCount; i++) {
|
|
9
38
|
let child = frag.child(i), endOff = off + child.nodeSize
|
|
10
39
|
let from = Math.max(off, start), to = Math.min(endOff, end)
|
|
11
40
|
if (from < to) {
|
|
12
41
|
if (child.isText) {
|
|
13
|
-
for (let j = from; j < to; j++) target.push(child.text!.charCodeAt(j - off))
|
|
42
|
+
for (let j = from; j < to; j++) target.push(encoder.encodeCharacter(child.text!.charCodeAt(j - off), child.marks))
|
|
14
43
|
} else if (child.isLeaf) {
|
|
15
|
-
target.push(child
|
|
44
|
+
target.push(encoder.encodeNodeStart(child))
|
|
16
45
|
} else {
|
|
17
|
-
if (from == off) target.push(child
|
|
18
|
-
tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target)
|
|
19
|
-
if (to == endOff) target.push(
|
|
46
|
+
if (from == off) target.push(encoder.encodeNodeStart(child))
|
|
47
|
+
tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target)
|
|
48
|
+
if (to == endOff) target.push(encoder.encodeNodeEnd(child))
|
|
20
49
|
}
|
|
21
50
|
}
|
|
22
51
|
off = endOff
|
|
@@ -38,15 +67,16 @@ function minUnchanged(sizeA: number, sizeB: number) {
|
|
|
38
67
|
return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)))
|
|
39
68
|
}
|
|
40
69
|
|
|
41
|
-
export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change) {
|
|
42
|
-
let tokA = tokens(fragA, range.fromA, range.toA, [])
|
|
43
|
-
let tokB = tokens(fragB, range.fromB, range.toB, [])
|
|
70
|
+
export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change, encoder: TokenEncoder<any> = DefaultEncoder) {
|
|
71
|
+
let tokA = tokens(fragA, encoder, range.fromA, range.toA, [])
|
|
72
|
+
let tokB = tokens(fragB, encoder, range.fromB, range.toB, [])
|
|
44
73
|
|
|
45
74
|
// Scan from both sides to cheaply eliminate work
|
|
46
75
|
let start = 0, endA = tokA.length, endB = tokB.length
|
|
47
|
-
|
|
76
|
+
let cmp = encoder.compareTokens
|
|
77
|
+
while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++
|
|
48
78
|
if (start == tokA.length && start == tokB.length) return []
|
|
49
|
-
while (endA > start && endB > start && tokA[endA - 1]
|
|
79
|
+
while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--
|
|
50
80
|
// If the result is simple _or_ too big to cheaply compute, return
|
|
51
81
|
// the remaining region as the diff
|
|
52
82
|
if (endA == start || endB == start || (endA == endB && endA == start + 1))
|
|
@@ -66,7 +96,7 @@ export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change) {
|
|
|
66
96
|
for (let diag = -size; diag <= size; diag += 2) {
|
|
67
97
|
let next = frontier[diag + 1 + max], prev = frontier[diag - 1 + max]
|
|
68
98
|
let x = next < prev ? prev : next + 1, y = x + diag
|
|
69
|
-
while (x < lenA && y < lenB && tokA[start + x]
|
|
99
|
+
while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y])) x++, y++
|
|
70
100
|
frontier[diag + max] = x
|
|
71
101
|
// Found a match
|
|
72
102
|
if (x >= lenA && y >= lenB) {
|