prosemirror-changeset
Advanced tools
+6
-0
@@ -0,1 +1,7 @@ | ||
| ## 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) | ||
@@ -2,0 +8,0 @@ |
+120
-252
| 'use strict'; | ||
| function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } | ||
| 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."); } | ||
| 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); } | ||
| function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } | ||
| function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } | ||
| 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; } | ||
| 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; } | ||
| 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); } | ||
| function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
| 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); } } | ||
| 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); } } | ||
| function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } | ||
| function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } | ||
| Object.defineProperty(exports, '__esModule', { | ||
| value: true | ||
| }); | ||
| function tokens(frag, start, end, target) { | ||
| function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } | ||
| 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); } | ||
| var DefaultEncoder = { | ||
| encodeCharacter: function encodeCharacter(_char) { | ||
| return _char; | ||
| }, | ||
| encodeNodeStart: function encodeNodeStart(node) { | ||
| return node.type.name; | ||
| }, | ||
| encodeNodeEnd: function encodeNodeEnd() { | ||
| return -1; | ||
| }, | ||
| compareTokens: function compareTokens(a, b) { | ||
| return a === b; | ||
| } | ||
| }; | ||
| function tokens(frag, encoder, start, end, target) { | ||
| for (var i = 0, off = 0; i < frag.childCount; i++) { | ||
| var child = frag.child(i), | ||
| endOff = off + child.nodeSize; | ||
| endOff = off + child.nodeSize; | ||
| var from = Math.max(off, start), | ||
| to = Math.min(endOff, end); | ||
| to = Math.min(endOff, end); | ||
| if (from < to) { | ||
| if (child.isText) { | ||
| for (var j = from; j < to; j++) { | ||
| target.push(child.text.charCodeAt(j - off)); | ||
| } | ||
| for (var j = from; j < to; j++) target.push(encoder.encodeCharacter(child.text.charCodeAt(j - off), child.marks)); | ||
| } else if (child.isLeaf) { | ||
| target.push(child.type.name); | ||
| target.push(encoder.encodeNodeStart(child)); | ||
| } else { | ||
| if (from == off) target.push(child.type.name); | ||
| tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target); | ||
| if (to == endOff) target.push(-1); | ||
| if (from == off) target.push(encoder.encodeNodeStart(child)); | ||
| tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target); | ||
| if (to == endOff) target.push(encoder.encodeNodeEnd(child)); | ||
| } | ||
| } | ||
| off = endOff; | ||
| } | ||
| return target; | ||
| } | ||
| var MAX_DIFF_SIZE = 5000; | ||
| function minUnchanged(sizeA, sizeB) { | ||
| return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10))); | ||
| } | ||
| function computeDiff(fragA, fragB, range) { | ||
| var tokA = tokens(fragA, range.fromA, range.toA, []); | ||
| var tokB = tokens(fragB, range.fromB, range.toB, []); | ||
| var encoder = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DefaultEncoder; | ||
| var tokA = tokens(fragA, encoder, range.fromA, range.toA, []); | ||
| var tokB = tokens(fragB, encoder, range.fromB, range.toB, []); | ||
| var start = 0, | ||
| endA = tokA.length, | ||
| endB = tokB.length; | ||
| while (start < tokA.length && start < tokB.length && tokA[start] === tokB[start]) { | ||
| start++; | ||
| } | ||
| endA = tokA.length, | ||
| endB = tokB.length; | ||
| var cmp = encoder.compareTokens; | ||
| while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++; | ||
| if (start == tokA.length && start == tokB.length) return []; | ||
| while (endA > start && endB > start && tokA[endA - 1] === tokB[endB - 1]) { | ||
| endA--, endB--; | ||
| } | ||
| while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--; | ||
| if (endA == start || endB == start || endA == endB && endA == start + 1) return [range.slice(start, endA, start, endB)]; | ||
| var lenA = endA - start, | ||
| lenB = endB - start; | ||
| lenB = endB - start; | ||
| var max = Math.min(MAX_DIFF_SIZE, lenA + lenB), | ||
| off = max + 1; | ||
| off = max + 1; | ||
| var history = []; | ||
| var frontier = []; | ||
| for (var len = off * 2, i = 0; i < len; i++) { | ||
| frontier[i] = -1; | ||
| } | ||
| for (var len = off * 2, i = 0; i < len; i++) frontier[i] = -1; | ||
| for (var size = 0; size <= max; size++) { | ||
| for (var diag = -size; diag <= size; diag += 2) { | ||
| var next = frontier[diag + 1 + max], | ||
| prev = frontier[diag - 1 + max]; | ||
| var x = next < prev ? prev : next + 1, | ||
| y = x + diag; | ||
| while (x < lenA && y < lenB && tokA[start + x] === tokB[start + y]) { | ||
| x++, y++; | ||
| } | ||
| frontier[diag + max] = x; | ||
| if (x >= lenA && y >= lenB) { | ||
| var _ret = function () { | ||
| var _loop = function _loop(_diag) { | ||
| var next = frontier[_diag + 1 + max], | ||
| prev = frontier[_diag - 1 + max]; | ||
| var x = next < prev ? prev : next + 1, | ||
| y = x + _diag; | ||
| while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y])) x++, y++; | ||
| frontier[_diag + max] = x; | ||
| if (x >= lenA && y >= lenB) { | ||
| var diff = [], | ||
| minSpan = minUnchanged(endA - start, endB - start); | ||
| minSpan = minUnchanged(endA - start, endB - start); | ||
| var fromA = -1, | ||
| toA = -1, | ||
| fromB = -1, | ||
| toB = -1; | ||
| toA = -1, | ||
| fromB = -1, | ||
| toB = -1; | ||
| var add = function add(fA, tA, fB, tB) { | ||
@@ -123,22 +100,18 @@ if (fromA > -1 && fromA < tA + minSpan) { | ||
| }; | ||
| for (var _i = size - 1; _i >= 0; _i--) { | ||
| var _next = frontier[diag + 1 + max], | ||
| _prev = frontier[diag - 1 + max]; | ||
| var _next = frontier[_diag + 1 + max], | ||
| _prev = frontier[_diag - 1 + max]; | ||
| if (_next < _prev) { | ||
| diag--; | ||
| _diag--; | ||
| x = _prev + start; | ||
| y = x + diag; | ||
| y = x + _diag; | ||
| add(x, x, y, y + 1); | ||
| } else { | ||
| diag++; | ||
| _diag++; | ||
| x = _next + start; | ||
| y = x + diag; | ||
| y = x + _diag; | ||
| add(x, x + 1, y, y); | ||
| } | ||
| frontier = history[_i >> 1]; | ||
| } | ||
| if (fromA > -1) diff.push(range.slice(fromA, toA, fromB, toB)); | ||
@@ -148,22 +121,20 @@ return { | ||
| }; | ||
| }(); | ||
| if (_typeof(_ret) === "object") return _ret.v; | ||
| } | ||
| } | ||
| diag = _diag; | ||
| }, | ||
| _ret; | ||
| for (var diag = -size; diag <= size; diag += 2) { | ||
| _ret = _loop(diag); | ||
| if (_ret) return _ret.v; | ||
| } | ||
| if (size % 2 == 0) history.push(frontier.slice()); | ||
| } | ||
| return [range.slice(start, endA, start, endB)]; | ||
| } | ||
| var Span = function () { | ||
| function Span(length, data) { | ||
| _classCallCheck(this, Span); | ||
| this.length = length; | ||
| this.data = data; | ||
| } | ||
| _createClass(Span, [{ | ||
@@ -180,6 +151,5 @@ key: "cut", | ||
| var result = []; | ||
| for (var i = 0, off = 0; off < to; i++) { | ||
| var span = spans[i], | ||
| end = off + span.length; | ||
| end = off + span.length; | ||
| var overlap = Math.min(to, end) - Math.max(from, off); | ||
@@ -189,3 +159,2 @@ if (overlap > 0) result.push(span.cut(overlap)); | ||
| } | ||
| return result; | ||
@@ -202,7 +171,3 @@ } | ||
| result.push(new Span(a[a.length - 1].length + b[0].length, combined)); | ||
| for (var i = 1; i < b.length; i++) { | ||
| result.push(b[i]); | ||
| } | ||
| for (var i = 1; i < b.length; i++) result.push(b[i]); | ||
| return result; | ||
@@ -214,20 +179,12 @@ } | ||
| var len = 0; | ||
| for (var i = 0; i < spans.length; i++) { | ||
| len += spans[i].length; | ||
| } | ||
| for (var i = 0; i < spans.length; i++) len += spans[i].length; | ||
| return len; | ||
| } | ||
| }]); | ||
| return Span; | ||
| }(); | ||
| Span.none = []; | ||
| var Change = function () { | ||
| function Change(fromA, toA, fromB, toB, deleted, inserted) { | ||
| _classCallCheck(this, Change); | ||
| this.fromA = fromA; | ||
@@ -240,3 +197,2 @@ this.toA = toA; | ||
| } | ||
| _createClass(Change, [{ | ||
@@ -264,3 +220,2 @@ key: "lenA", | ||
| var result = []; | ||
| for (var iX = 0, iY = 0, curX = x[0], curY = y[0];;) { | ||
@@ -275,3 +230,2 @@ if (!curX && !curY) { | ||
| var _off = iX ? x[iX - 1].toB - x[iX - 1].toA : 0; | ||
| result.push(_off == 0 ? curY : new Change(curY.fromA - _off, curY.toA - _off, curY.fromB, curY.toB, curY.deleted, curY.inserted)); | ||
@@ -282,10 +236,9 @@ curY = iY++ == y.length ? null : y[iY]; | ||
| var fromA = Math.min(curX.fromA, curY.fromA - (iX ? x[iX - 1].toB - x[iX - 1].toA : 0)), | ||
| toA = fromA; | ||
| toA = fromA; | ||
| var fromB = Math.min(curY.fromB, curX.fromB + (iY ? y[iY - 1].toB - y[iY - 1].toA : 0)), | ||
| toB = fromB; | ||
| toB = fromB; | ||
| var deleted = Span.none, | ||
| inserted = Span.none; | ||
| inserted = Span.none; | ||
| var enteredX = false, | ||
| enteredY = false; | ||
| enteredY = false; | ||
| for (;;) { | ||
@@ -296,5 +249,4 @@ var nextX = !curX ? 2e8 : pos >= curX.fromB ? curX.toB : curX.fromB; | ||
| var inX = curX && pos >= curX.fromB, | ||
| inY = curY && pos >= curY.fromA; | ||
| inY = curY && pos >= curY.fromA; | ||
| if (!inX && !inY) break; | ||
| if (inX && pos == curX.fromB && !enteredX) { | ||
@@ -305,3 +257,2 @@ deleted = Span.join(deleted, curX.deleted, combine); | ||
| } | ||
| if (inX && !inY) { | ||
@@ -311,3 +262,2 @@ inserted = Span.join(inserted, Span.slice(curX.inserted, pos - curX.fromB, next - curX.fromB), combine); | ||
| } | ||
| if (inY && pos == curY.fromA && !enteredY) { | ||
@@ -318,3 +268,2 @@ inserted = Span.join(inserted, curY.inserted, combine); | ||
| } | ||
| if (inY && !inX) { | ||
@@ -324,3 +273,2 @@ deleted = Span.join(deleted, Span.slice(curY.deleted, pos - curY.fromA, next - curY.fromA), combine); | ||
| } | ||
| if (inX && next == curX.toB) { | ||
@@ -330,3 +278,2 @@ curX = iX++ == x.length ? null : x[iX]; | ||
| } | ||
| if (inY && next == curY.toA) { | ||
@@ -336,6 +283,4 @@ curY = iY++ == y.length ? null : y[iY]; | ||
| } | ||
| pos = next; | ||
| } | ||
| if (fromA < toA || fromB < toB) result.push(new Change(fromA, toA, fromB, toB, deleted, inserted)); | ||
@@ -346,14 +291,9 @@ } | ||
| }]); | ||
| return Change; | ||
| }(); | ||
| var letter; | ||
| try { | ||
| letter = new RegExp("[\\p{Alphabetic}_]", "u"); | ||
| } catch (_) {} | ||
| var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; | ||
| function isLetter(code) { | ||
@@ -365,13 +305,10 @@ if (code < 128) return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 79 && code <= 122; | ||
| } | ||
| function getText(frag, start, end) { | ||
| var out = ""; | ||
| function convert(frag, start, end) { | ||
| for (var i = 0, off = 0; i < frag.childCount; i++) { | ||
| var child = frag.child(i), | ||
| endOff = off + child.nodeSize; | ||
| endOff = off + child.nodeSize; | ||
| var from = Math.max(off, start), | ||
| to = Math.min(endOff, end); | ||
| to = Math.min(endOff, end); | ||
| if (from < to) { | ||
@@ -388,30 +325,19 @@ if (child.isText) { | ||
| } | ||
| off = endOff; | ||
| } | ||
| } | ||
| convert(frag, start, end); | ||
| return out; | ||
| } | ||
| var MAX_SIMPLIFY_DISTANCE = 30; | ||
| function simplifyChanges(changes, doc) { | ||
| var result = []; | ||
| for (var i = 0; i < changes.length; i++) { | ||
| var end = changes[i].toB, | ||
| start = i; | ||
| while (i < changes.length - 1 && changes[i + 1].fromB <= end + MAX_SIMPLIFY_DISTANCE) { | ||
| end = changes[++i].toB; | ||
| } | ||
| start = i; | ||
| while (i < changes.length - 1 && changes[i + 1].fromB <= end + MAX_SIMPLIFY_DISTANCE) end = changes[++i].toB; | ||
| simplifyAdjacentChanges(changes, start, i + 1, doc, result); | ||
| } | ||
| return result; | ||
| } | ||
| function simplifyAdjacentChanges(changes, from, to, doc, target) { | ||
@@ -421,14 +347,11 @@ var start = Math.max(0, changes[from].fromB - MAX_SIMPLIFY_DISTANCE); | ||
| var text = getText(doc.content, start, end); | ||
| for (var i = from; i < to; i++) { | ||
| var startI = i, | ||
| last = changes[i], | ||
| deleted = last.lenA, | ||
| inserted = last.lenB; | ||
| last = changes[i], | ||
| deleted = last.lenA, | ||
| inserted = last.lenB; | ||
| while (i < to - 1) { | ||
| var next = changes[i + 1], | ||
| boundary = false; | ||
| boundary = false; | ||
| var prevLetter = last.toB == end ? false : isLetter(text.charCodeAt(last.toB - 1 - start)); | ||
| for (var pos = last.toB; !boundary && pos < next.fromB; pos++) { | ||
@@ -439,3 +362,2 @@ var nextLetter = pos == end ? false : isLetter(text.charCodeAt(pos - start)); | ||
| } | ||
| if (boundary) break; | ||
@@ -447,31 +369,19 @@ deleted += next.lenA; | ||
| } | ||
| if (inserted > 0 && deleted > 0 && !(inserted == 1 && deleted == 1)) { | ||
| var _from = changes[startI].fromB, | ||
| _to = changes[i].toB; | ||
| if (_from < end && isLetter(text.charCodeAt(_from - start))) while (_from > start && isLetter(text.charCodeAt(_from - 1 - start))) { | ||
| _from--; | ||
| } | ||
| if (_to > start && isLetter(text.charCodeAt(_to - 1 - start))) while (_to < end && isLetter(text.charCodeAt(_to - start))) { | ||
| _to++; | ||
| } | ||
| _to = changes[i].toB; | ||
| if (_from < end && isLetter(text.charCodeAt(_from - start))) while (_from > start && isLetter(text.charCodeAt(_from - 1 - start))) _from--; | ||
| if (_to > start && isLetter(text.charCodeAt(_to - 1 - start))) while (_to < end && isLetter(text.charCodeAt(_to - start))) _to++; | ||
| var joined = fillChange(changes.slice(startI, i + 1), _from, _to); | ||
| var _last = target.length ? target[target.length - 1] : null; | ||
| 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); | ||
| } else { | ||
| for (var j = startI; j <= i; j++) { | ||
| target.push(changes[j]); | ||
| } | ||
| for (var j = startI; j <= i; j++) target.push(changes[j]); | ||
| } | ||
| } | ||
| return changes; | ||
| } | ||
| function combine(a, b) { | ||
| return a === b ? a : null; | ||
| } | ||
| function fillChange(changes, fromB, toB) { | ||
@@ -482,10 +392,9 @@ var fromA = changes[0].fromA - (changes[0].fromB - fromB); | ||
| var deleted = Span.none, | ||
| inserted = Span.none; | ||
| inserted = Span.none; | ||
| var delData = (changes[0].deleted.length ? changes[0].deleted : changes[0].inserted)[0].data; | ||
| var insData = (changes[0].inserted.length ? changes[0].inserted : changes[0].deleted)[0].data; | ||
| for (var posA = fromA, posB = fromB, i = 0;; i++) { | ||
| var next = i == changes.length ? null : changes[i]; | ||
| var endA = next ? next.fromA : toA, | ||
| endB = next ? next.fromB : toB; | ||
| endB = next ? next.fromB : toB; | ||
| if (endA > posA) deleted = Span.join(deleted, [new Span(endA - posA, delData)], combine); | ||
@@ -501,14 +410,10 @@ if (endB > posB) inserted = Span.join(inserted, [new Span(endB - posB, insData)], combine); | ||
| } | ||
| return new Change(fromA, toA, fromB, toB, deleted, inserted); | ||
| } | ||
| var ChangeSet = function () { | ||
| function ChangeSet(config, changes) { | ||
| _classCallCheck(this, ChangeSet); | ||
| this.config = config; | ||
| this.changes = changes; | ||
| } | ||
| _createClass(ChangeSet, [{ | ||
@@ -518,6 +423,4 @@ key: "addSteps", | ||
| var _this = this; | ||
| var stepChanges = []; | ||
| var _loop = function _loop(i) { | ||
| var _loop2 = function _loop2() { | ||
| var d = Array.isArray(data) ? data[i] : data; | ||
@@ -530,7 +433,5 @@ var off = 0; | ||
| }; | ||
| for (var i = 0; i < maps.length; i++) { | ||
| _loop(i); | ||
| _loop2(); | ||
| } | ||
| if (stepChanges.length == 0) return this; | ||
@@ -540,41 +441,30 @@ var newChanges = mergeAll(stepChanges, this.config.combine); | ||
| var updated = changes; | ||
| var _loop2 = function _loop2(_i3) { | ||
| var change = updated[_i3]; | ||
| if (change.fromA == change.toA || change.fromB == change.toB || !newChanges.some(function (r) { | ||
| return r.toB > change.fromB && r.fromB < change.toB; | ||
| })) { | ||
| var _loop3 = function _loop3(_i3) { | ||
| var change = updated[_i3]; | ||
| if (change.fromA == change.toA || change.fromB == change.toB || !newChanges.some(function (r) { | ||
| return r.toB > change.fromB && r.fromB < change.toB; | ||
| })) { | ||
| _i2 = _i3; | ||
| return 0; | ||
| } | ||
| var diff = computeDiff(_this.config.doc.content, newDoc.content, change, _this.config.encoder); | ||
| if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB) { | ||
| _i2 = _i3; | ||
| return 0; | ||
| } | ||
| if (updated == changes) updated = changes.slice(); | ||
| if (diff.length == 1) { | ||
| updated[_i3] = diff[0]; | ||
| } else { | ||
| var _updated; | ||
| (_updated = updated).splice.apply(_updated, [_i3, 1].concat(_toConsumableArray(diff))); | ||
| _i3 += diff.length - 1; | ||
| } | ||
| _i2 = _i3; | ||
| return "continue"; | ||
| } | ||
| var diff = computeDiff(_this.config.doc.content, newDoc.content, change); | ||
| if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB) { | ||
| _i2 = _i3; | ||
| return "continue"; | ||
| } | ||
| if (updated == changes) updated = changes.slice(); | ||
| if (diff.length == 1) { | ||
| updated[_i3] = diff[0]; | ||
| } else { | ||
| var _updated; | ||
| (_updated = updated).splice.apply(_updated, [_i3, 1].concat(_toConsumableArray(diff))); | ||
| _i3 += diff.length - 1; | ||
| } | ||
| _i2 = _i3; | ||
| }; | ||
| }, | ||
| _ret2; | ||
| for (var _i2 = 0; _i2 < updated.length; _i2++) { | ||
| var _ret2 = _loop2(_i2); | ||
| if (_ret2 === "continue") continue; | ||
| _ret2 = _loop3(_i2); | ||
| if (_ret2 === 0) continue; | ||
| } | ||
| return new ChangeSet(this.config, updated); | ||
@@ -594,3 +484,2 @@ } | ||
| }; | ||
| return new ChangeSet(this.config, this.changes.map(function (ch) { | ||
@@ -606,10 +495,7 @@ return new Change(ch.fromA, ch.toA, ch.fromB, ch.toB, ch.deleted.map(mapSpan), ch.inserted.map(mapSpan)); | ||
| var moved = touched ? touched.toB - touched.fromB - (touched.toA - touched.fromA) : 0; | ||
| function map(p) { | ||
| return !touched || p <= touched.fromA ? p : p + moved; | ||
| } | ||
| var from = touched ? touched.fromB : 2e8, | ||
| to = touched ? touched.toB : -2e8; | ||
| to = touched ? touched.toB : -2e8; | ||
| function add(start) { | ||
@@ -620,10 +506,7 @@ var end = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : start; | ||
| } | ||
| var rA = this.changes, | ||
| rB = b.changes; | ||
| rB = b.changes; | ||
| for (var iA = 0, iB = 0; iA < rA.length && iB < rB.length;) { | ||
| var rangeA = rA[iA], | ||
| rangeB = rB[iB]; | ||
| rangeB = rB[iB]; | ||
| if (rangeA && rangeB && sameRanges(rangeA, rangeB, map)) { | ||
@@ -640,3 +523,2 @@ iA++; | ||
| } | ||
| return from <= to ? { | ||
@@ -653,14 +535,13 @@ from: from, | ||
| }; | ||
| var tokenEncoder = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DefaultEncoder; | ||
| return new ChangeSet({ | ||
| combine: combine, | ||
| doc: doc | ||
| doc: doc, | ||
| encoder: tokenEncoder | ||
| }, []); | ||
| } | ||
| }]); | ||
| return ChangeSet; | ||
| }(); | ||
| ChangeSet.computeDiff = computeDiff; | ||
| function mergeAll(ranges, combine) { | ||
@@ -673,10 +554,7 @@ var start = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
| } | ||
| function endRange(maps) { | ||
| var from = 2e8, | ||
| to = -2e8; | ||
| to = -2e8; | ||
| for (var i = 0; i < maps.length; i++) { | ||
| var map = maps[i]; | ||
| if (from != 2e8) { | ||
@@ -686,3 +564,2 @@ from = map.map(from, -1); | ||
| } | ||
| map.forEach(function (_s, _e, start, end) { | ||
@@ -693,3 +570,2 @@ from = Math.min(from, start); | ||
| } | ||
| return from == 2e8 ? null : { | ||
@@ -700,3 +576,2 @@ from: from, | ||
| } | ||
| function touchedRange(maps) { | ||
@@ -715,17 +590,10 @@ var b = endRange(maps); | ||
| } | ||
| function sameRanges(a, b, map) { | ||
| return map(a.fromB) == b.fromB && map(a.toB) == b.toB && sameSpans(a.deleted, b.deleted) && sameSpans(a.inserted, b.inserted); | ||
| } | ||
| function sameSpans(a, b) { | ||
| if (a.length != b.length) return false; | ||
| for (var i = 0; i < a.length; i++) { | ||
| if (a[i].length != b[i].length || a[i].data !== b[i].data) return false; | ||
| } | ||
| for (var i = 0; i < a.length; i++) if (a[i].length != b[i].length || a[i].data !== b[i].data) return false; | ||
| return true; | ||
| } | ||
| exports.Change = Change; | ||
@@ -732,0 +600,0 @@ exports.ChangeSet = ChangeSet; |
+42
-4
@@ -1,2 +0,2 @@ | ||
| import { Node } from 'prosemirror-model'; | ||
| import { Mark, Node } from 'prosemirror-model'; | ||
| import { StepMap } from 'prosemirror-transform'; | ||
@@ -44,3 +44,3 @@ | ||
| Data associated with the inserted content. Length adds up to | ||
| `this.toB - this.toA`. | ||
| `this.toB - this.fromB`. | ||
| */ | ||
@@ -57,2 +57,34 @@ readonly inserted: readonly Span<Data>[]; | ||
| /** | ||
| 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. | ||
| */ | ||
| interface TokenEncoder<T> { | ||
| /** | ||
| Encode a given character, with the given marks applied. | ||
| */ | ||
| encodeCharacter(char: number, marks: readonly Mark[]): T; | ||
| /** | ||
| Encode the start of a node or, if this is a leaf node, the | ||
| entire node. | ||
| */ | ||
| encodeNodeStart(node: Node): T; | ||
| /** | ||
| Encode the end token for the given node. It is valid to encode | ||
| every end token in the same way. | ||
| */ | ||
| encodeNodeEnd(node: Node): T; | ||
| /** | ||
| Compare the given tokens. Should return true when they count as | ||
| equal. | ||
| */ | ||
| compareTokens(a: T, b: T): boolean; | ||
| } | ||
| /** | ||
| Simplifies a set of changes for presentation. This makes the | ||
@@ -113,9 +145,15 @@ assumption that having both insertions and deletions within a word | ||
| 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. | ||
| */ | ||
| static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data): ChangeSet<Data>; | ||
| static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>): ChangeSet<Data>; | ||
| } | ||
| export { Change, ChangeSet, Span, simplifyChanges }; | ||
| export { Change, ChangeSet, Span, type TokenEncoder, simplifyChanges }; |
+42
-4
@@ -1,2 +0,2 @@ | ||
| import { Node } from 'prosemirror-model'; | ||
| import { Mark, Node } from 'prosemirror-model'; | ||
| import { StepMap } from 'prosemirror-transform'; | ||
@@ -44,3 +44,3 @@ | ||
| Data associated with the inserted content. Length adds up to | ||
| `this.toB - this.toA`. | ||
| `this.toB - this.fromB`. | ||
| */ | ||
@@ -57,2 +57,34 @@ readonly inserted: readonly Span<Data>[]; | ||
| /** | ||
| 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. | ||
| */ | ||
| interface TokenEncoder<T> { | ||
| /** | ||
| Encode a given character, with the given marks applied. | ||
| */ | ||
| encodeCharacter(char: number, marks: readonly Mark[]): T; | ||
| /** | ||
| Encode the start of a node or, if this is a leaf node, the | ||
| entire node. | ||
| */ | ||
| encodeNodeStart(node: Node): T; | ||
| /** | ||
| Encode the end token for the given node. It is valid to encode | ||
| every end token in the same way. | ||
| */ | ||
| encodeNodeEnd(node: Node): T; | ||
| /** | ||
| Compare the given tokens. Should return true when they count as | ||
| equal. | ||
| */ | ||
| compareTokens(a: T, b: T): boolean; | ||
| } | ||
| /** | ||
| Simplifies a set of changes for presentation. This makes the | ||
@@ -113,9 +145,15 @@ assumption that having both insertions and deletions within a word | ||
| 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. | ||
| */ | ||
| static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data): ChangeSet<Data>; | ||
| static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>): ChangeSet<Data>; | ||
| } | ||
| export { Change, ChangeSet, Span, simplifyChanges }; | ||
| export { Change, ChangeSet, Span, type TokenEncoder, simplifyChanges }; |
+29
-16
@@ -0,5 +1,11 @@ | ||
| const DefaultEncoder = { | ||
| encodeCharacter: char => char, | ||
| encodeNodeStart: node => node.type.name, | ||
| encodeNodeEnd: () => -1, | ||
| compareTokens: (a, b) => a === b | ||
| }; | ||
| // Convert the given range of a fragment to tokens, where node open | ||
| // tokens are encoded as strings holding the node name, characters as | ||
| // their character code, and node close tokens as -1. | ||
| function tokens(frag, start, end, target) { | ||
| function tokens(frag, encoder, start, end, target) { | ||
| for (let i = 0, off = 0; i < frag.childCount; i++) { | ||
@@ -11,13 +17,13 @@ let child = frag.child(i), endOff = off + child.nodeSize; | ||
| for (let j = from; j < to; j++) | ||
| target.push(child.text.charCodeAt(j - off)); | ||
| target.push(encoder.encodeCharacter(child.text.charCodeAt(j - off), child.marks)); | ||
| } | ||
| else if (child.isLeaf) { | ||
| target.push(child.type.name); | ||
| target.push(encoder.encodeNodeStart(child)); | ||
| } | ||
| else { | ||
| if (from == off) | ||
| target.push(child.type.name); | ||
| tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target); | ||
| target.push(encoder.encodeNodeStart(child)); | ||
| tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target); | ||
| if (to == endOff) | ||
| target.push(-1); | ||
| target.push(encoder.encodeNodeEnd(child)); | ||
| } | ||
@@ -41,12 +47,13 @@ } | ||
| } | ||
| function computeDiff(fragA, fragB, range) { | ||
| let tokA = tokens(fragA, range.fromA, range.toA, []); | ||
| let tokB = tokens(fragB, range.fromB, range.toB, []); | ||
| function computeDiff(fragA, fragB, range, encoder = DefaultEncoder) { | ||
| let tokA = tokens(fragA, encoder, range.fromA, range.toA, []); | ||
| let tokB = tokens(fragB, encoder, range.fromB, range.toB, []); | ||
| // Scan from both sides to cheaply eliminate work | ||
| let start = 0, endA = tokA.length, endB = tokB.length; | ||
| while (start < tokA.length && start < tokB.length && tokA[start] === tokB[start]) | ||
| let cmp = encoder.compareTokens; | ||
| while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) | ||
| start++; | ||
| if (start == tokA.length && start == tokB.length) | ||
| return []; | ||
| while (endA > start && endB > start && tokA[endA - 1] === tokB[endB - 1]) | ||
| while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) | ||
| endA--, endB--; | ||
@@ -70,3 +77,3 @@ // If the result is simple _or_ too big to cheaply compute, return | ||
| let x = next < prev ? prev : next + 1, y = x + diag; | ||
| while (x < lenA && y < lenB && tokA[start + x] === tokB[start + y]) | ||
| while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y])) | ||
| x++, y++; | ||
@@ -231,3 +238,3 @@ frontier[diag + max] = x; | ||
| Data associated with the inserted content. Length adds up to | ||
| `this.toB - this.toA`. | ||
| `this.toB - this.fromB`. | ||
| */ | ||
@@ -556,3 +563,3 @@ inserted) { | ||
| continue; | ||
| let diff = computeDiff(this.config.doc.content, newDoc.content, change); | ||
| let diff = computeDiff(this.config.doc.content, newDoc.content, change, this.config.encoder); | ||
| // Fast path: If they are completely different, don't do anything | ||
@@ -630,8 +637,14 @@ if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB) | ||
| 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. | ||
| */ | ||
| static create(doc, combine = (a, b) => a === b ? a : null) { | ||
| return new ChangeSet({ combine, doc }, []); | ||
| static create(doc, combine = (a, b) => a === b ? a : null, tokenEncoder = DefaultEncoder) { | ||
| return new ChangeSet({ combine, doc, encoder: tokenEncoder }, []); | ||
| } | ||
@@ -638,0 +651,0 @@ } |
+4
-3
| { | ||
| "name": "prosemirror-changeset", | ||
| "version": "2.2.1", | ||
| "version": "2.3.0", | ||
| "description": "Distills a series of editing steps into deleted and added ranges", | ||
@@ -32,3 +32,4 @@ "type": "module", | ||
| "prosemirror-model": "^1.0.0", | ||
| "prosemirror-test-builder": "^1.0.0" | ||
| "prosemirror-test-builder": "^1.0.0", | ||
| "builddocs": "^1.0.8" | ||
| }, | ||
@@ -38,4 +39,4 @@ "scripts": { | ||
| "prepare": "pm-buildhelper src/changeset.ts", | ||
| "build-readme": "FIXME" | ||
| "build-readme": "builddocs --format markdown --main src/README.md src/changeset.ts > README.md" | ||
| } | ||
| } |
+35
-2
@@ -44,3 +44,3 @@ # prosemirror-changeset | ||
| Data associated with the inserted content. Length adds up to | ||
| `this.toB - this.toA`. | ||
| `this.toB - this.fromB`. | ||
@@ -100,4 +100,5 @@ * `static `**`merge`**`<Data>(x: readonly Change[], y: readonly Change[], combine: fn(dataA: Data, dataB: Data) → Data) → readonly Change[]`\ | ||
| * `static `**`create`**`<Data = any>(doc: Node, combine?: fn(dataA: Data, dataB: Data) → Data = (a, b) => a === b ? a : null as any) → ChangeSet`\ | ||
| * `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`\ | ||
| Create a changeset with the given base object and configuration. | ||
| The `combine` function is used to compare and combine metadata—it | ||
@@ -107,3 +108,8 @@ should return null when metadata isn't compatible, and a combined | ||
| 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[]`\ | ||
@@ -117,1 +123,28 @@ Simplifies a set of changes for presentation. This makes the | ||
| ### 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. | ||
+1
-1
@@ -69,3 +69,3 @@ /// Stores metadata for a part of a change. | ||
| /// Data associated with the inserted content. Length adds up to | ||
| /// `this.toB - this.toA`. | ||
| /// `this.toB - this.fromB`. | ||
| readonly inserted: readonly Span<Data>[] | ||
@@ -72,0 +72,0 @@ ) {} |
+20
-5
| import {Node} from "prosemirror-model" | ||
| import {StepMap} from "prosemirror-transform" | ||
| import {computeDiff} from "./diff" | ||
| import {computeDiff, TokenEncoder, DefaultEncoder} from "./diff" | ||
| import {Change, Span} from "./change" | ||
| export {Change, Span} | ||
| export {simplifyChanges} from "./simplify" | ||
| export {TokenEncoder} | ||
@@ -16,3 +17,7 @@ /// A change set tracks the changes to a document from a given point | ||
| /// @internal | ||
| readonly config: {doc: Node, combine: (dataA: Data, dataB: Data) => Data}, | ||
| readonly config: { | ||
| doc: Node, | ||
| combine: (dataA: Data, dataB: Data) => Data, | ||
| encoder: TokenEncoder<any> | ||
| }, | ||
| /// Replaced regions. | ||
@@ -73,3 +78,3 @@ readonly changes: readonly Change<Data>[] | ||
| !newChanges.some(r => r.toB > change.fromB && r.fromB < change.toB)) continue | ||
| let diff = computeDiff(this.config.doc.content, newDoc.content, change) | ||
| let diff = computeDiff(this.config.doc.content, newDoc.content, change, this.config.encoder) | ||
@@ -137,7 +142,17 @@ // Fast path: If they are completely different, don't do anything | ||
| /// 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. | ||
| static create<Data = any>(doc: Node, combine: (dataA: Data, dataB: Data) => Data = (a, b) => a === b ? a : null as any) { | ||
| return new ChangeSet({combine, doc}, []) | ||
| /// | ||
| /// 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. | ||
| static create<Data = any>( | ||
| doc: Node, | ||
| combine: (dataA: Data, dataB: Data) => Data = (a, b) => a === b ? a : null as any, | ||
| tokenEncoder: TokenEncoder<any> = DefaultEncoder | ||
| ) { | ||
| return new ChangeSet({combine, doc, encoder: tokenEncoder}, []) | ||
| } | ||
@@ -144,0 +159,0 @@ |
+43
-13
@@ -1,8 +0,37 @@ | ||
| import {Fragment} from "prosemirror-model" | ||
| import {Fragment, Node, Mark} from "prosemirror-model" | ||
| import {Change} from "./change" | ||
| /// 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. | ||
| export interface TokenEncoder<T> { | ||
| /// Encode a given character, with the given marks applied. | ||
| encodeCharacter(char: number, marks: readonly Mark[]): T | ||
| /// Encode the start of a node or, if this is a leaf node, the | ||
| /// entire node. | ||
| encodeNodeStart(node: Node): T | ||
| /// Encode the end token for the given node. It is valid to encode | ||
| /// every end token in the same way. | ||
| encodeNodeEnd(node: Node): T | ||
| /// Compare the given tokens. Should return true when they count as | ||
| /// equal. | ||
| compareTokens(a: T, b: T): boolean | ||
| } | ||
| export const DefaultEncoder: TokenEncoder<number | string> = { | ||
| encodeCharacter: char => char, | ||
| encodeNodeStart: node => node.type.name, | ||
| encodeNodeEnd: () => -1, | ||
| compareTokens: (a, b) => a === b | ||
| } | ||
| // Convert the given range of a fragment to tokens, where node open | ||
| // tokens are encoded as strings holding the node name, characters as | ||
| // their character code, and node close tokens as -1. | ||
| function tokens(frag: Fragment, start: number, end: number, target: (number | string)[]) { | ||
| function tokens<T>(frag: Fragment, encoder: TokenEncoder<T>, start: number, end: number, target: T[]) { | ||
| for (let i = 0, off = 0; i < frag.childCount; i++) { | ||
@@ -13,9 +42,9 @@ let child = frag.child(i), endOff = off + child.nodeSize | ||
| if (child.isText) { | ||
| for (let j = from; j < to; j++) target.push(child.text!.charCodeAt(j - off)) | ||
| for (let j = from; j < to; j++) target.push(encoder.encodeCharacter(child.text!.charCodeAt(j - off), child.marks)) | ||
| } else if (child.isLeaf) { | ||
| target.push(child.type.name) | ||
| target.push(encoder.encodeNodeStart(child)) | ||
| } else { | ||
| if (from == off) target.push(child.type.name) | ||
| tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target) | ||
| if (to == endOff) target.push(-1) | ||
| if (from == off) target.push(encoder.encodeNodeStart(child)) | ||
| tokens(child.content, encoder, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target) | ||
| if (to == endOff) target.push(encoder.encodeNodeEnd(child)) | ||
| } | ||
@@ -42,11 +71,12 @@ } | ||
| export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change) { | ||
| let tokA = tokens(fragA, range.fromA, range.toA, []) | ||
| let tokB = tokens(fragB, range.fromB, range.toB, []) | ||
| export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change, encoder: TokenEncoder<any> = DefaultEncoder) { | ||
| let tokA = tokens(fragA, encoder, range.fromA, range.toA, []) | ||
| let tokB = tokens(fragB, encoder, range.fromB, range.toB, []) | ||
| // Scan from both sides to cheaply eliminate work | ||
| let start = 0, endA = tokA.length, endB = tokB.length | ||
| while (start < tokA.length && start < tokB.length && tokA[start] === tokB[start]) start++ | ||
| let cmp = encoder.compareTokens | ||
| while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++ | ||
| if (start == tokA.length && start == tokB.length) return [] | ||
| while (endA > start && endB > start && tokA[endA - 1] === tokB[endB - 1]) endA--, endB-- | ||
| while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB-- | ||
| // If the result is simple _or_ too big to cheaply compute, return | ||
@@ -71,3 +101,3 @@ // the remaining region as the diff | ||
| let x = next < prev ? prev : next + 1, y = x + diag | ||
| while (x < lenA && y < lenB && tokA[start + x] === tokB[start + y]) x++, y++ | ||
| while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y])) x++, y++ | ||
| frontier[diag + max] = x | ||
@@ -74,0 +104,0 @@ // Found a match |
+3
-1
@@ -28,2 +28,4 @@ # prosemirror-changeset | ||
| @simplifyChanges | ||
| @simplifyChanges | ||
| @TokenEncoder |
119668
6.69%2360
3.78%147
28.95%4
33.33%