@atlaskit/collab-provider 10.9.2 → 10.9.3
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 +9 -0
- package/dist/cjs/channel.js +1 -1
- package/dist/cjs/document/document-service.js +17 -4
- package/dist/cjs/document/getConflictChanges.js +177 -0
- package/dist/cjs/errors/ncs-errors.js +1 -1
- package/dist/cjs/provider/commit-step.js +1 -1
- package/dist/cjs/version-wrapper.js +1 -1
- package/dist/es2019/channel.js +1 -1
- package/dist/es2019/document/document-service.js +18 -4
- package/dist/es2019/document/getConflictChanges.js +161 -0
- package/dist/es2019/errors/ncs-errors.js +1 -1
- package/dist/es2019/provider/commit-step.js +1 -1
- package/dist/es2019/version-wrapper.js +1 -1
- package/dist/esm/channel.js +1 -1
- package/dist/esm/document/document-service.js +17 -4
- package/dist/esm/document/getConflictChanges.js +170 -0
- package/dist/esm/errors/ncs-errors.js +1 -1
- package/dist/esm/provider/commit-step.js +1 -1
- package/dist/esm/version-wrapper.js +1 -1
- package/dist/types/document/getConflictChanges.d.ts +26 -0
- package/dist/types/provider/commit-step.d.ts +1 -1
- package/dist/types-ts4.5/document/getConflictChanges.d.ts +26 -0
- package/dist/types-ts4.5/provider/commit-step.d.ts +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @atlaskit/collab-provider
|
|
2
2
|
|
|
3
|
+
## 10.9.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#122605](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/122605)
|
|
8
|
+
[`1bf1493f744ce`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1bf1493f744ce) -
|
|
9
|
+
[ux] Add conflict metadata on reconnection
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
|
|
3
12
|
## 10.9.2
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/dist/cjs/channel.js
CHANGED
|
@@ -203,7 +203,7 @@ var Channel = exports.Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
203
203
|
var measure = (0, _performance.stopMeasure)(_performance.MEASURE_NAME.DOCUMENT_INIT, _this.analyticsHelper);
|
|
204
204
|
(_this$initExperience = _this.initExperience) === null || _this$initExperience === void 0 || _this$initExperience.success();
|
|
205
205
|
(_this$analyticsHelper6 = _this.analyticsHelper) === null || _this$analyticsHelper6 === void 0 || _this$analyticsHelper6.sendActionEvent(_const.EVENT_ACTION.DOCUMENT_INIT,
|
|
206
|
-
// TODO: detect when document init fails and fire corresponding event for it
|
|
206
|
+
// TODO: ED-26957 - detect when document init fails and fire corresponding event for it
|
|
207
207
|
_const.EVENT_STATUS.SUCCESS, {
|
|
208
208
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
209
209
|
resetReason: data.resetReason,
|
|
@@ -12,6 +12,7 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
|
|
|
12
12
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
13
13
|
var _throttle = _interopRequireDefault(require("lodash/throttle"));
|
|
14
14
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
15
|
+
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
15
16
|
var _prosemirrorCollab = require("@atlaskit/prosemirror-collab");
|
|
16
17
|
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
17
18
|
var _editorJsonTransformer = require("@atlaskit/editor-json-transformer");
|
|
@@ -24,6 +25,7 @@ var _commitStep = require("../provider/commit-step");
|
|
|
24
25
|
var _customErrors = require("../errors/custom-errors");
|
|
25
26
|
var _catchupv = require("./catchupv2");
|
|
26
27
|
var _stepQueueState = require("./step-queue-state");
|
|
28
|
+
var _getConflictChanges = require("./getConflictChanges");
|
|
27
29
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
28
30
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
29
31
|
var CATCHUP_THROTTLE = 1 * 1000; // 1 second
|
|
@@ -72,7 +74,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
|
|
|
72
74
|
return _this.catchupv2(reason, reconnectionMetadata);
|
|
73
75
|
}, CATCHUP_THROTTLE, {
|
|
74
76
|
leading: false,
|
|
75
|
-
// TODO: why shouldn't this be leading?
|
|
77
|
+
// TODO: ED-26957 - why shouldn't this be leading?
|
|
76
78
|
trailing: true
|
|
77
79
|
}));
|
|
78
80
|
/**
|
|
@@ -772,10 +774,21 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
|
|
|
772
774
|
var state = (_this$getState7 = this.getState) === null || _this$getState7 === void 0 ? void 0 : _this$getState7.call(this);
|
|
773
775
|
var unconfirmedSteps = state ? (_getCollabState = (0, _prosemirrorCollab.getCollabState)(state)) === null || _getCollabState === void 0 ? void 0 : _getCollabState.unconfirmed : undefined;
|
|
774
776
|
if (steps.length > 0 && state && unconfirmedSteps && unconfirmedSteps.length > 0) {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
777
|
+
var schema = state.schema,
|
|
778
|
+
tr = state.tr;
|
|
779
|
+
var remoteSteps = steps.map(function (s) {
|
|
780
|
+
return _transform.Step.fromJSON(schema, s);
|
|
778
781
|
});
|
|
782
|
+
var conflicts = (0, _getConflictChanges.getConflictChanges)({
|
|
783
|
+
localSteps: unconfirmedSteps,
|
|
784
|
+
remoteSteps: remoteSteps,
|
|
785
|
+
tr: tr
|
|
786
|
+
});
|
|
787
|
+
if (conflicts.deleted.length > 0 || conflicts.inserted.length > 0) {
|
|
788
|
+
this.providerEmitCallback('data:conflict', _objectSpread({
|
|
789
|
+
offlineDoc: state.doc
|
|
790
|
+
}, conflicts));
|
|
791
|
+
}
|
|
779
792
|
}
|
|
780
793
|
}
|
|
781
794
|
}, {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.getConflictChanges = getConflictChanges;
|
|
8
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
|
+
var _prosemirrorChangeset = require("prosemirror-changeset");
|
|
10
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
11
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
12
|
+
var simplifySteps = function simplifySteps(steps) {
|
|
13
|
+
return steps.reduce(function (acc, step) {
|
|
14
|
+
var lastStep = acc[acc.length - 1];
|
|
15
|
+
if (lastStep) {
|
|
16
|
+
var mergedStep = lastStep.merge(step);
|
|
17
|
+
if (mergedStep) {
|
|
18
|
+
acc[acc.length - 1] = mergedStep;
|
|
19
|
+
return acc;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return acc.concat(step);
|
|
23
|
+
}, []);
|
|
24
|
+
};
|
|
25
|
+
function findContentChanges(doc, steps) {
|
|
26
|
+
var changes = _prosemirrorChangeset.ChangeSet.create(doc);
|
|
27
|
+
var latestDoc = doc;
|
|
28
|
+
simplifySteps(steps).forEach(function (step, index) {
|
|
29
|
+
var stepResult = step.apply(latestDoc);
|
|
30
|
+
if (stepResult.failed !== null || stepResult.doc === null) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
latestDoc = stepResult.doc;
|
|
34
|
+
changes = changes.addSteps(latestDoc, [step.getMap()], {
|
|
35
|
+
step: index
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
return changes;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Iterate through the changesets to find overlapping regions that indicate conflicting
|
|
43
|
+
* changes
|
|
44
|
+
*/
|
|
45
|
+
var getConflicts = function getConflicts(_ref) {
|
|
46
|
+
var localChanges = _ref.localChanges,
|
|
47
|
+
localDoc = _ref.localDoc,
|
|
48
|
+
remoteChanges = _ref.remoteChanges,
|
|
49
|
+
remoteDoc = _ref.remoteDoc;
|
|
50
|
+
var conflictingChanges = [];
|
|
51
|
+
localChanges.changes.forEach(function (localChange) {
|
|
52
|
+
remoteChanges.changes.forEach(function (remoteChange) {
|
|
53
|
+
if (
|
|
54
|
+
// Local change is inside remote change
|
|
55
|
+
localChange.fromA >= remoteChange.fromA && localChange.toA <= remoteChange.toA ||
|
|
56
|
+
// Remote change is inside local change
|
|
57
|
+
remoteChange.fromA >= localChange.fromA && remoteChange.toA <= localChange.toA ||
|
|
58
|
+
// Partial overlap with the end
|
|
59
|
+
localChange.fromA >= remoteChange.fromA && localChange.fromA < remoteChange.toA && localChange.toA > remoteChange.toA ||
|
|
60
|
+
// Partial overlap with the start
|
|
61
|
+
localChange.fromA < remoteChange.fromA && localChange.toA > remoteChange.fromA && localChange.toA <= remoteChange.toA) {
|
|
62
|
+
conflictingChanges.push({
|
|
63
|
+
from: Math.min(localChange.fromA, remoteChange.fromA),
|
|
64
|
+
to: Math.max(localChange.toA, remoteChange.toA),
|
|
65
|
+
local: localDoc.slice(localChange.fromB, localChange.toB, true),
|
|
66
|
+
remote: remoteDoc.slice(remoteChange.fromB, remoteChange.toB)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
return conflictingChanges;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Almost a copy of the rebaseSteps in the collab algorithm (which gets called
|
|
76
|
+
* synchronously after this).
|
|
77
|
+
*
|
|
78
|
+
* This also tracks the intermediate documents so we can generate the changesets
|
|
79
|
+
* to use for finding any overlapping regions.
|
|
80
|
+
* See: `packages/editor/prosemirror-collab/src/index.ts`
|
|
81
|
+
*/
|
|
82
|
+
var rebaseSteps = function rebaseSteps(_ref2) {
|
|
83
|
+
var _tr$mapping$maps;
|
|
84
|
+
var localSteps = _ref2.localSteps,
|
|
85
|
+
remoteSteps = _ref2.remoteSteps,
|
|
86
|
+
tr = _ref2.tr;
|
|
87
|
+
for (var i = (localSteps === null || localSteps === void 0 ? void 0 : localSteps.length) - 1; i >= 0; i--) {
|
|
88
|
+
tr.step(localSteps[i].inverted);
|
|
89
|
+
}
|
|
90
|
+
var originalDoc = tr.doc;
|
|
91
|
+
var mapStart = (_tr$mapping$maps = tr.mapping.maps) === null || _tr$mapping$maps === void 0 ? void 0 : _tr$mapping$maps.length;
|
|
92
|
+
for (var _i = 0; _i < remoteSteps.length; _i++) {
|
|
93
|
+
tr.step(remoteSteps[_i]);
|
|
94
|
+
}
|
|
95
|
+
var remoteDoc = tr.doc;
|
|
96
|
+
for (var _i2 = 0, mapFrom = localSteps.length; _i2 < localSteps.length; _i2++) {
|
|
97
|
+
var mapped = localSteps[_i2].step.map(tr.mapping.slice(mapFrom));
|
|
98
|
+
mapFrom--;
|
|
99
|
+
if (mapped && !tr.maybeStep(mapped).failed) {
|
|
100
|
+
// Open ticket for setMirror https://github.com/ProseMirror/prosemirror/issues/869
|
|
101
|
+
// @ts-expect-error
|
|
102
|
+
tr.mapping.setMirror(mapFrom, tr.steps.length - 1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
mapStart: mapStart,
|
|
107
|
+
originalDoc: originalDoc,
|
|
108
|
+
remoteDoc: remoteDoc
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Gets the conflicts between the local document and the remote document based on steps.
|
|
114
|
+
* It assumes the steps will be rebased using the `prosemirror-collab` algorithm synchronously after this
|
|
115
|
+
* Therefore the `tr` property is based on the document before rebasing.
|
|
116
|
+
*
|
|
117
|
+
* In the future we could possibly use `prosemirror-recreate-steps` (or similar approach)
|
|
118
|
+
* and tweak this to work for arbitrary diffs between offline and remote documents.
|
|
119
|
+
*
|
|
120
|
+
* @param localSteps Local steps applied between now and the server steps
|
|
121
|
+
* @param remoteSteps Steps retrieved from the server
|
|
122
|
+
* @param tr Transaction of the current document (expected to happen with local steps applied, before remote are applied)
|
|
123
|
+
* @returns All the conflicts (inserted + deleted) which can be applied to the current document
|
|
124
|
+
*/
|
|
125
|
+
function getConflictChanges(_ref3) {
|
|
126
|
+
var localSteps = _ref3.localSteps,
|
|
127
|
+
remoteSteps = _ref3.remoteSteps,
|
|
128
|
+
tr = _ref3.tr;
|
|
129
|
+
var localDoc = tr.doc;
|
|
130
|
+
var _rebaseSteps = rebaseSteps({
|
|
131
|
+
localSteps: localSteps,
|
|
132
|
+
remoteSteps: remoteSteps,
|
|
133
|
+
tr: tr
|
|
134
|
+
}),
|
|
135
|
+
originalDoc = _rebaseSteps.originalDoc,
|
|
136
|
+
remoteDoc = _rebaseSteps.remoteDoc,
|
|
137
|
+
mapStart = _rebaseSteps.mapStart;
|
|
138
|
+
var localChanges = findContentChanges(originalDoc, localSteps.map(function (s) {
|
|
139
|
+
return s.step;
|
|
140
|
+
}));
|
|
141
|
+
var remoteChanges = findContentChanges(originalDoc, remoteSteps);
|
|
142
|
+
|
|
143
|
+
// This is the mapping between the original document and our final one which can be used to
|
|
144
|
+
// map conflict positions (which are based on the original doc)
|
|
145
|
+
var mapping = tr.mapping.slice(mapStart);
|
|
146
|
+
|
|
147
|
+
// Find the overlapping conflicts - these are based on the positions of the original document so are
|
|
148
|
+
// common to both local and remote documents.
|
|
149
|
+
// The above mapping allows us to bring these positions to where they are in the current document
|
|
150
|
+
var conflictingChanges = getConflicts({
|
|
151
|
+
localChanges: localChanges,
|
|
152
|
+
localDoc: localDoc,
|
|
153
|
+
remoteDoc: remoteDoc,
|
|
154
|
+
remoteChanges: remoteChanges
|
|
155
|
+
});
|
|
156
|
+
var isConflictChange = function isConflictChange(value) {
|
|
157
|
+
return Boolean(value);
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
inserted: conflictingChanges.filter(function (i) {
|
|
161
|
+
return i.remote.size !== 0;
|
|
162
|
+
}).map(function (i) {
|
|
163
|
+
return _objectSpread(_objectSpread({}, i), {}, {
|
|
164
|
+
from: mapping.map(i.from, -1),
|
|
165
|
+
to: mapping.map(i.to)
|
|
166
|
+
});
|
|
167
|
+
}).filter(isConflictChange),
|
|
168
|
+
deleted: conflictingChanges.filter(function (d) {
|
|
169
|
+
return d.remote.size === 0;
|
|
170
|
+
}).map(function (d) {
|
|
171
|
+
return _objectSpread(_objectSpread({}, d), {}, {
|
|
172
|
+
from: mapping.map(d.from),
|
|
173
|
+
to: mapping.map(d.to)
|
|
174
|
+
});
|
|
175
|
+
}).filter(isConflictChange)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -25,7 +25,7 @@ var NCS_ERROR_CODE = exports.NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_C
|
|
|
25
25
|
NCS_ERROR_CODE["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
|
|
26
26
|
NCS_ERROR_CODE["PROSEMIRROR_SCHEMA_VALIDATION_ERROR"] = "PROSEMIRROR_SCHEMA_VALIDATION_ERROR";
|
|
27
27
|
return NCS_ERROR_CODE;
|
|
28
|
-
}({}); // TODO: Import emitted error codes from NCS
|
|
28
|
+
}({}); // TODO: ED-26957 - Import emitted error codes from NCS
|
|
29
29
|
// NCS Errors
|
|
30
30
|
// - Step rejection errors
|
|
31
31
|
// - Permission errors
|
|
@@ -16,7 +16,7 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
|
|
|
16
16
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
17
17
|
var logger = (0, _utils.createLogger)('commit-step', 'black');
|
|
18
18
|
var readyToCommit = exports.readyToCommit = true;
|
|
19
|
-
var RESET_READYTOCOMMIT_INTERVAL_MS = exports.RESET_READYTOCOMMIT_INTERVAL_MS =
|
|
19
|
+
var RESET_READYTOCOMMIT_INTERVAL_MS = exports.RESET_READYTOCOMMIT_INTERVAL_MS = 5000;
|
|
20
20
|
var commitStepQueue = exports.commitStepQueue = function commitStepQueue(_ref) {
|
|
21
21
|
var broadcast = _ref.broadcast,
|
|
22
22
|
steps = _ref.steps,
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.version = exports.nextMajorVersion = exports.name = void 0;
|
|
7
7
|
var name = exports.name = "@atlaskit/collab-provider";
|
|
8
|
-
var version = exports.version = "10.9.
|
|
8
|
+
var version = exports.version = "10.9.3";
|
|
9
9
|
var nextMajorVersion = exports.nextMajorVersion = function nextMajorVersion() {
|
|
10
10
|
return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
|
|
11
11
|
};
|
package/dist/es2019/channel.js
CHANGED
|
@@ -139,7 +139,7 @@ export class Channel extends Emitter {
|
|
|
139
139
|
const measure = stopMeasure(MEASURE_NAME.DOCUMENT_INIT, this.analyticsHelper);
|
|
140
140
|
(_this$initExperience = this.initExperience) === null || _this$initExperience === void 0 ? void 0 : _this$initExperience.success();
|
|
141
141
|
(_this$analyticsHelper6 = this.analyticsHelper) === null || _this$analyticsHelper6 === void 0 ? void 0 : _this$analyticsHelper6.sendActionEvent(EVENT_ACTION.DOCUMENT_INIT,
|
|
142
|
-
// TODO: detect when document init fails and fire corresponding event for it
|
|
142
|
+
// TODO: ED-26957 - detect when document init fails and fire corresponding event for it
|
|
143
143
|
EVENT_STATUS.SUCCESS, {
|
|
144
144
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
145
145
|
resetReason: data.resetReason,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import throttle from 'lodash/throttle';
|
|
3
3
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
|
+
import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
|
|
4
5
|
import { getCollabState, sendableSteps } from '@atlaskit/prosemirror-collab';
|
|
5
6
|
import { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
6
7
|
import { JSONTransformer } from '@atlaskit/editor-json-transformer';
|
|
@@ -13,6 +14,7 @@ import { commitStepQueue } from '../provider/commit-step';
|
|
|
13
14
|
import { CantSyncUpError, UpdateDocumentError } from '../errors/custom-errors';
|
|
14
15
|
import { catchupv2 } from './catchupv2';
|
|
15
16
|
import { StepQueueState } from './step-queue-state';
|
|
17
|
+
import { getConflictChanges } from './getConflictChanges';
|
|
16
18
|
const CATCHUP_THROTTLE = 1 * 1000; // 1 second
|
|
17
19
|
|
|
18
20
|
const noop = () => {};
|
|
@@ -52,7 +54,7 @@ export class DocumentService {
|
|
|
52
54
|
*/
|
|
53
55
|
_defineProperty(this, "throttledCatchupv2", throttle((reason, reconnectionMetadata) => this.catchupv2(reason, reconnectionMetadata), CATCHUP_THROTTLE, {
|
|
54
56
|
leading: false,
|
|
55
|
-
// TODO: why shouldn't this be leading?
|
|
57
|
+
// TODO: ED-26957 - why shouldn't this be leading?
|
|
56
58
|
trailing: true
|
|
57
59
|
}));
|
|
58
60
|
/**
|
|
@@ -657,10 +659,22 @@ export class DocumentService {
|
|
|
657
659
|
const state = (_this$getState7 = this.getState) === null || _this$getState7 === void 0 ? void 0 : _this$getState7.call(this);
|
|
658
660
|
const unconfirmedSteps = state ? (_getCollabState = getCollabState(state)) === null || _getCollabState === void 0 ? void 0 : _getCollabState.unconfirmed : undefined;
|
|
659
661
|
if (steps.length > 0 && state && unconfirmedSteps && unconfirmedSteps.length > 0) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
662
|
+
const {
|
|
663
|
+
schema,
|
|
664
|
+
tr
|
|
665
|
+
} = state;
|
|
666
|
+
const remoteSteps = steps.map(s => ProseMirrorStep.fromJSON(schema, s));
|
|
667
|
+
const conflicts = getConflictChanges({
|
|
668
|
+
localSteps: unconfirmedSteps,
|
|
669
|
+
remoteSteps,
|
|
670
|
+
tr
|
|
663
671
|
});
|
|
672
|
+
if (conflicts.deleted.length > 0 || conflicts.inserted.length > 0) {
|
|
673
|
+
this.providerEmitCallback('data:conflict', {
|
|
674
|
+
offlineDoc: state.doc,
|
|
675
|
+
...conflicts
|
|
676
|
+
});
|
|
677
|
+
}
|
|
664
678
|
}
|
|
665
679
|
}
|
|
666
680
|
processQueue() {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { ChangeSet } from 'prosemirror-changeset';
|
|
2
|
+
const simplifySteps = steps => {
|
|
3
|
+
return steps.reduce((acc, step) => {
|
|
4
|
+
const lastStep = acc[acc.length - 1];
|
|
5
|
+
if (lastStep) {
|
|
6
|
+
const mergedStep = lastStep.merge(step);
|
|
7
|
+
if (mergedStep) {
|
|
8
|
+
acc[acc.length - 1] = mergedStep;
|
|
9
|
+
return acc;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return acc.concat(step);
|
|
13
|
+
}, []);
|
|
14
|
+
};
|
|
15
|
+
function findContentChanges(doc, steps) {
|
|
16
|
+
let changes = ChangeSet.create(doc);
|
|
17
|
+
let latestDoc = doc;
|
|
18
|
+
simplifySteps(steps).forEach((step, index) => {
|
|
19
|
+
const stepResult = step.apply(latestDoc);
|
|
20
|
+
if (stepResult.failed !== null || stepResult.doc === null) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
latestDoc = stepResult.doc;
|
|
24
|
+
changes = changes.addSteps(latestDoc, [step.getMap()], {
|
|
25
|
+
step: index
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
return changes;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Iterate through the changesets to find overlapping regions that indicate conflicting
|
|
33
|
+
* changes
|
|
34
|
+
*/
|
|
35
|
+
const getConflicts = ({
|
|
36
|
+
localChanges,
|
|
37
|
+
localDoc,
|
|
38
|
+
remoteChanges,
|
|
39
|
+
remoteDoc
|
|
40
|
+
}) => {
|
|
41
|
+
const conflictingChanges = [];
|
|
42
|
+
localChanges.changes.forEach(localChange => {
|
|
43
|
+
remoteChanges.changes.forEach(remoteChange => {
|
|
44
|
+
if (
|
|
45
|
+
// Local change is inside remote change
|
|
46
|
+
localChange.fromA >= remoteChange.fromA && localChange.toA <= remoteChange.toA ||
|
|
47
|
+
// Remote change is inside local change
|
|
48
|
+
remoteChange.fromA >= localChange.fromA && remoteChange.toA <= localChange.toA ||
|
|
49
|
+
// Partial overlap with the end
|
|
50
|
+
localChange.fromA >= remoteChange.fromA && localChange.fromA < remoteChange.toA && localChange.toA > remoteChange.toA ||
|
|
51
|
+
// Partial overlap with the start
|
|
52
|
+
localChange.fromA < remoteChange.fromA && localChange.toA > remoteChange.fromA && localChange.toA <= remoteChange.toA) {
|
|
53
|
+
conflictingChanges.push({
|
|
54
|
+
from: Math.min(localChange.fromA, remoteChange.fromA),
|
|
55
|
+
to: Math.max(localChange.toA, remoteChange.toA),
|
|
56
|
+
local: localDoc.slice(localChange.fromB, localChange.toB, true),
|
|
57
|
+
remote: remoteDoc.slice(remoteChange.fromB, remoteChange.toB)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
return conflictingChanges;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Almost a copy of the rebaseSteps in the collab algorithm (which gets called
|
|
67
|
+
* synchronously after this).
|
|
68
|
+
*
|
|
69
|
+
* This also tracks the intermediate documents so we can generate the changesets
|
|
70
|
+
* to use for finding any overlapping regions.
|
|
71
|
+
* See: `packages/editor/prosemirror-collab/src/index.ts`
|
|
72
|
+
*/
|
|
73
|
+
const rebaseSteps = ({
|
|
74
|
+
localSteps,
|
|
75
|
+
remoteSteps,
|
|
76
|
+
tr
|
|
77
|
+
}) => {
|
|
78
|
+
var _tr$mapping$maps;
|
|
79
|
+
for (let i = (localSteps === null || localSteps === void 0 ? void 0 : localSteps.length) - 1; i >= 0; i--) {
|
|
80
|
+
tr.step(localSteps[i].inverted);
|
|
81
|
+
}
|
|
82
|
+
const originalDoc = tr.doc;
|
|
83
|
+
const mapStart = (_tr$mapping$maps = tr.mapping.maps) === null || _tr$mapping$maps === void 0 ? void 0 : _tr$mapping$maps.length;
|
|
84
|
+
for (let i = 0; i < remoteSteps.length; i++) {
|
|
85
|
+
tr.step(remoteSteps[i]);
|
|
86
|
+
}
|
|
87
|
+
const remoteDoc = tr.doc;
|
|
88
|
+
for (let i = 0, mapFrom = localSteps.length; i < localSteps.length; i++) {
|
|
89
|
+
const mapped = localSteps[i].step.map(tr.mapping.slice(mapFrom));
|
|
90
|
+
mapFrom--;
|
|
91
|
+
if (mapped && !tr.maybeStep(mapped).failed) {
|
|
92
|
+
// Open ticket for setMirror https://github.com/ProseMirror/prosemirror/issues/869
|
|
93
|
+
// @ts-expect-error
|
|
94
|
+
tr.mapping.setMirror(mapFrom, tr.steps.length - 1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
mapStart,
|
|
99
|
+
originalDoc,
|
|
100
|
+
remoteDoc
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gets the conflicts between the local document and the remote document based on steps.
|
|
106
|
+
* It assumes the steps will be rebased using the `prosemirror-collab` algorithm synchronously after this
|
|
107
|
+
* Therefore the `tr` property is based on the document before rebasing.
|
|
108
|
+
*
|
|
109
|
+
* In the future we could possibly use `prosemirror-recreate-steps` (or similar approach)
|
|
110
|
+
* and tweak this to work for arbitrary diffs between offline and remote documents.
|
|
111
|
+
*
|
|
112
|
+
* @param localSteps Local steps applied between now and the server steps
|
|
113
|
+
* @param remoteSteps Steps retrieved from the server
|
|
114
|
+
* @param tr Transaction of the current document (expected to happen with local steps applied, before remote are applied)
|
|
115
|
+
* @returns All the conflicts (inserted + deleted) which can be applied to the current document
|
|
116
|
+
*/
|
|
117
|
+
export function getConflictChanges({
|
|
118
|
+
localSteps,
|
|
119
|
+
remoteSteps,
|
|
120
|
+
tr
|
|
121
|
+
}) {
|
|
122
|
+
const localDoc = tr.doc;
|
|
123
|
+
const {
|
|
124
|
+
originalDoc,
|
|
125
|
+
remoteDoc,
|
|
126
|
+
mapStart
|
|
127
|
+
} = rebaseSteps({
|
|
128
|
+
localSteps,
|
|
129
|
+
remoteSteps,
|
|
130
|
+
tr
|
|
131
|
+
});
|
|
132
|
+
const localChanges = findContentChanges(originalDoc, localSteps.map(s => s.step));
|
|
133
|
+
const remoteChanges = findContentChanges(originalDoc, remoteSteps);
|
|
134
|
+
|
|
135
|
+
// This is the mapping between the original document and our final one which can be used to
|
|
136
|
+
// map conflict positions (which are based on the original doc)
|
|
137
|
+
const mapping = tr.mapping.slice(mapStart);
|
|
138
|
+
|
|
139
|
+
// Find the overlapping conflicts - these are based on the positions of the original document so are
|
|
140
|
+
// common to both local and remote documents.
|
|
141
|
+
// The above mapping allows us to bring these positions to where they are in the current document
|
|
142
|
+
const conflictingChanges = getConflicts({
|
|
143
|
+
localChanges,
|
|
144
|
+
localDoc,
|
|
145
|
+
remoteDoc,
|
|
146
|
+
remoteChanges
|
|
147
|
+
});
|
|
148
|
+
const isConflictChange = value => Boolean(value);
|
|
149
|
+
return {
|
|
150
|
+
inserted: conflictingChanges.filter(i => i.remote.size !== 0).map(i => ({
|
|
151
|
+
...i,
|
|
152
|
+
from: mapping.map(i.from, -1),
|
|
153
|
+
to: mapping.map(i.to)
|
|
154
|
+
})).filter(isConflictChange),
|
|
155
|
+
deleted: conflictingChanges.filter(d => d.remote.size === 0).map(d => ({
|
|
156
|
+
...d,
|
|
157
|
+
from: mapping.map(d.from),
|
|
158
|
+
to: mapping.map(d.to)
|
|
159
|
+
})).filter(isConflictChange)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -21,7 +21,7 @@ export let NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_CODE) {
|
|
|
21
21
|
return NCS_ERROR_CODE;
|
|
22
22
|
}({});
|
|
23
23
|
|
|
24
|
-
// TODO: Import emitted error codes from NCS
|
|
24
|
+
// TODO: ED-26957 - Import emitted error codes from NCS
|
|
25
25
|
|
|
26
26
|
// NCS Errors
|
|
27
27
|
// - Step rejection errors
|
|
@@ -6,7 +6,7 @@ import { NCS_ERROR_CODE } from '../errors/ncs-errors';
|
|
|
6
6
|
import { createLogger } from '../helpers/utils';
|
|
7
7
|
const logger = createLogger('commit-step', 'black');
|
|
8
8
|
export let readyToCommit = true;
|
|
9
|
-
export const RESET_READYTOCOMMIT_INTERVAL_MS =
|
|
9
|
+
export const RESET_READYTOCOMMIT_INTERVAL_MS = 5000;
|
|
10
10
|
export const commitStepQueue = ({
|
|
11
11
|
broadcast,
|
|
12
12
|
steps,
|
package/dist/esm/channel.js
CHANGED
|
@@ -196,7 +196,7 @@ export var Channel = /*#__PURE__*/function (_Emitter) {
|
|
|
196
196
|
var measure = stopMeasure(MEASURE_NAME.DOCUMENT_INIT, _this.analyticsHelper);
|
|
197
197
|
(_this$initExperience = _this.initExperience) === null || _this$initExperience === void 0 || _this$initExperience.success();
|
|
198
198
|
(_this$analyticsHelper6 = _this.analyticsHelper) === null || _this$analyticsHelper6 === void 0 || _this$analyticsHelper6.sendActionEvent(EVENT_ACTION.DOCUMENT_INIT,
|
|
199
|
-
// TODO: detect when document init fails and fire corresponding event for it
|
|
199
|
+
// TODO: ED-26957 - detect when document init fails and fire corresponding event for it
|
|
200
200
|
EVENT_STATUS.SUCCESS, {
|
|
201
201
|
latency: measure === null || measure === void 0 ? void 0 : measure.duration,
|
|
202
202
|
resetReason: data.resetReason,
|
|
@@ -7,6 +7,7 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
|
|
|
7
7
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
8
8
|
import throttle from 'lodash/throttle';
|
|
9
9
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
10
|
+
import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
|
|
10
11
|
import { getCollabState, sendableSteps } from '@atlaskit/prosemirror-collab';
|
|
11
12
|
import { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
12
13
|
import { JSONTransformer } from '@atlaskit/editor-json-transformer';
|
|
@@ -19,6 +20,7 @@ import { commitStepQueue } from '../provider/commit-step';
|
|
|
19
20
|
import { CantSyncUpError, UpdateDocumentError } from '../errors/custom-errors';
|
|
20
21
|
import { catchupv2 } from './catchupv2';
|
|
21
22
|
import { StepQueueState } from './step-queue-state';
|
|
23
|
+
import { getConflictChanges } from './getConflictChanges';
|
|
22
24
|
var CATCHUP_THROTTLE = 1 * 1000; // 1 second
|
|
23
25
|
|
|
24
26
|
var noop = function noop() {};
|
|
@@ -65,7 +67,7 @@ export var DocumentService = /*#__PURE__*/function () {
|
|
|
65
67
|
return _this.catchupv2(reason, reconnectionMetadata);
|
|
66
68
|
}, CATCHUP_THROTTLE, {
|
|
67
69
|
leading: false,
|
|
68
|
-
// TODO: why shouldn't this be leading?
|
|
70
|
+
// TODO: ED-26957 - why shouldn't this be leading?
|
|
69
71
|
trailing: true
|
|
70
72
|
}));
|
|
71
73
|
/**
|
|
@@ -765,10 +767,21 @@ export var DocumentService = /*#__PURE__*/function () {
|
|
|
765
767
|
var state = (_this$getState7 = this.getState) === null || _this$getState7 === void 0 ? void 0 : _this$getState7.call(this);
|
|
766
768
|
var unconfirmedSteps = state ? (_getCollabState = getCollabState(state)) === null || _getCollabState === void 0 ? void 0 : _getCollabState.unconfirmed : undefined;
|
|
767
769
|
if (steps.length > 0 && state && unconfirmedSteps && unconfirmedSteps.length > 0) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
770
|
+
var schema = state.schema,
|
|
771
|
+
tr = state.tr;
|
|
772
|
+
var remoteSteps = steps.map(function (s) {
|
|
773
|
+
return ProseMirrorStep.fromJSON(schema, s);
|
|
771
774
|
});
|
|
775
|
+
var conflicts = getConflictChanges({
|
|
776
|
+
localSteps: unconfirmedSteps,
|
|
777
|
+
remoteSteps: remoteSteps,
|
|
778
|
+
tr: tr
|
|
779
|
+
});
|
|
780
|
+
if (conflicts.deleted.length > 0 || conflicts.inserted.length > 0) {
|
|
781
|
+
this.providerEmitCallback('data:conflict', _objectSpread({
|
|
782
|
+
offlineDoc: state.doc
|
|
783
|
+
}, conflicts));
|
|
784
|
+
}
|
|
772
785
|
}
|
|
773
786
|
}
|
|
774
787
|
}, {
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
+
import { ChangeSet } from 'prosemirror-changeset';
|
|
5
|
+
var simplifySteps = function simplifySteps(steps) {
|
|
6
|
+
return steps.reduce(function (acc, step) {
|
|
7
|
+
var lastStep = acc[acc.length - 1];
|
|
8
|
+
if (lastStep) {
|
|
9
|
+
var mergedStep = lastStep.merge(step);
|
|
10
|
+
if (mergedStep) {
|
|
11
|
+
acc[acc.length - 1] = mergedStep;
|
|
12
|
+
return acc;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return acc.concat(step);
|
|
16
|
+
}, []);
|
|
17
|
+
};
|
|
18
|
+
function findContentChanges(doc, steps) {
|
|
19
|
+
var changes = ChangeSet.create(doc);
|
|
20
|
+
var latestDoc = doc;
|
|
21
|
+
simplifySteps(steps).forEach(function (step, index) {
|
|
22
|
+
var stepResult = step.apply(latestDoc);
|
|
23
|
+
if (stepResult.failed !== null || stepResult.doc === null) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
latestDoc = stepResult.doc;
|
|
27
|
+
changes = changes.addSteps(latestDoc, [step.getMap()], {
|
|
28
|
+
step: index
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
return changes;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Iterate through the changesets to find overlapping regions that indicate conflicting
|
|
36
|
+
* changes
|
|
37
|
+
*/
|
|
38
|
+
var getConflicts = function getConflicts(_ref) {
|
|
39
|
+
var localChanges = _ref.localChanges,
|
|
40
|
+
localDoc = _ref.localDoc,
|
|
41
|
+
remoteChanges = _ref.remoteChanges,
|
|
42
|
+
remoteDoc = _ref.remoteDoc;
|
|
43
|
+
var conflictingChanges = [];
|
|
44
|
+
localChanges.changes.forEach(function (localChange) {
|
|
45
|
+
remoteChanges.changes.forEach(function (remoteChange) {
|
|
46
|
+
if (
|
|
47
|
+
// Local change is inside remote change
|
|
48
|
+
localChange.fromA >= remoteChange.fromA && localChange.toA <= remoteChange.toA ||
|
|
49
|
+
// Remote change is inside local change
|
|
50
|
+
remoteChange.fromA >= localChange.fromA && remoteChange.toA <= localChange.toA ||
|
|
51
|
+
// Partial overlap with the end
|
|
52
|
+
localChange.fromA >= remoteChange.fromA && localChange.fromA < remoteChange.toA && localChange.toA > remoteChange.toA ||
|
|
53
|
+
// Partial overlap with the start
|
|
54
|
+
localChange.fromA < remoteChange.fromA && localChange.toA > remoteChange.fromA && localChange.toA <= remoteChange.toA) {
|
|
55
|
+
conflictingChanges.push({
|
|
56
|
+
from: Math.min(localChange.fromA, remoteChange.fromA),
|
|
57
|
+
to: Math.max(localChange.toA, remoteChange.toA),
|
|
58
|
+
local: localDoc.slice(localChange.fromB, localChange.toB, true),
|
|
59
|
+
remote: remoteDoc.slice(remoteChange.fromB, remoteChange.toB)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
return conflictingChanges;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Almost a copy of the rebaseSteps in the collab algorithm (which gets called
|
|
69
|
+
* synchronously after this).
|
|
70
|
+
*
|
|
71
|
+
* This also tracks the intermediate documents so we can generate the changesets
|
|
72
|
+
* to use for finding any overlapping regions.
|
|
73
|
+
* See: `packages/editor/prosemirror-collab/src/index.ts`
|
|
74
|
+
*/
|
|
75
|
+
var rebaseSteps = function rebaseSteps(_ref2) {
|
|
76
|
+
var _tr$mapping$maps;
|
|
77
|
+
var localSteps = _ref2.localSteps,
|
|
78
|
+
remoteSteps = _ref2.remoteSteps,
|
|
79
|
+
tr = _ref2.tr;
|
|
80
|
+
for (var i = (localSteps === null || localSteps === void 0 ? void 0 : localSteps.length) - 1; i >= 0; i--) {
|
|
81
|
+
tr.step(localSteps[i].inverted);
|
|
82
|
+
}
|
|
83
|
+
var originalDoc = tr.doc;
|
|
84
|
+
var mapStart = (_tr$mapping$maps = tr.mapping.maps) === null || _tr$mapping$maps === void 0 ? void 0 : _tr$mapping$maps.length;
|
|
85
|
+
for (var _i = 0; _i < remoteSteps.length; _i++) {
|
|
86
|
+
tr.step(remoteSteps[_i]);
|
|
87
|
+
}
|
|
88
|
+
var remoteDoc = tr.doc;
|
|
89
|
+
for (var _i2 = 0, mapFrom = localSteps.length; _i2 < localSteps.length; _i2++) {
|
|
90
|
+
var mapped = localSteps[_i2].step.map(tr.mapping.slice(mapFrom));
|
|
91
|
+
mapFrom--;
|
|
92
|
+
if (mapped && !tr.maybeStep(mapped).failed) {
|
|
93
|
+
// Open ticket for setMirror https://github.com/ProseMirror/prosemirror/issues/869
|
|
94
|
+
// @ts-expect-error
|
|
95
|
+
tr.mapping.setMirror(mapFrom, tr.steps.length - 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
mapStart: mapStart,
|
|
100
|
+
originalDoc: originalDoc,
|
|
101
|
+
remoteDoc: remoteDoc
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Gets the conflicts between the local document and the remote document based on steps.
|
|
107
|
+
* It assumes the steps will be rebased using the `prosemirror-collab` algorithm synchronously after this
|
|
108
|
+
* Therefore the `tr` property is based on the document before rebasing.
|
|
109
|
+
*
|
|
110
|
+
* In the future we could possibly use `prosemirror-recreate-steps` (or similar approach)
|
|
111
|
+
* and tweak this to work for arbitrary diffs between offline and remote documents.
|
|
112
|
+
*
|
|
113
|
+
* @param localSteps Local steps applied between now and the server steps
|
|
114
|
+
* @param remoteSteps Steps retrieved from the server
|
|
115
|
+
* @param tr Transaction of the current document (expected to happen with local steps applied, before remote are applied)
|
|
116
|
+
* @returns All the conflicts (inserted + deleted) which can be applied to the current document
|
|
117
|
+
*/
|
|
118
|
+
export function getConflictChanges(_ref3) {
|
|
119
|
+
var localSteps = _ref3.localSteps,
|
|
120
|
+
remoteSteps = _ref3.remoteSteps,
|
|
121
|
+
tr = _ref3.tr;
|
|
122
|
+
var localDoc = tr.doc;
|
|
123
|
+
var _rebaseSteps = rebaseSteps({
|
|
124
|
+
localSteps: localSteps,
|
|
125
|
+
remoteSteps: remoteSteps,
|
|
126
|
+
tr: tr
|
|
127
|
+
}),
|
|
128
|
+
originalDoc = _rebaseSteps.originalDoc,
|
|
129
|
+
remoteDoc = _rebaseSteps.remoteDoc,
|
|
130
|
+
mapStart = _rebaseSteps.mapStart;
|
|
131
|
+
var localChanges = findContentChanges(originalDoc, localSteps.map(function (s) {
|
|
132
|
+
return s.step;
|
|
133
|
+
}));
|
|
134
|
+
var remoteChanges = findContentChanges(originalDoc, remoteSteps);
|
|
135
|
+
|
|
136
|
+
// This is the mapping between the original document and our final one which can be used to
|
|
137
|
+
// map conflict positions (which are based on the original doc)
|
|
138
|
+
var mapping = tr.mapping.slice(mapStart);
|
|
139
|
+
|
|
140
|
+
// Find the overlapping conflicts - these are based on the positions of the original document so are
|
|
141
|
+
// common to both local and remote documents.
|
|
142
|
+
// The above mapping allows us to bring these positions to where they are in the current document
|
|
143
|
+
var conflictingChanges = getConflicts({
|
|
144
|
+
localChanges: localChanges,
|
|
145
|
+
localDoc: localDoc,
|
|
146
|
+
remoteDoc: remoteDoc,
|
|
147
|
+
remoteChanges: remoteChanges
|
|
148
|
+
});
|
|
149
|
+
var isConflictChange = function isConflictChange(value) {
|
|
150
|
+
return Boolean(value);
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
inserted: conflictingChanges.filter(function (i) {
|
|
154
|
+
return i.remote.size !== 0;
|
|
155
|
+
}).map(function (i) {
|
|
156
|
+
return _objectSpread(_objectSpread({}, i), {}, {
|
|
157
|
+
from: mapping.map(i.from, -1),
|
|
158
|
+
to: mapping.map(i.to)
|
|
159
|
+
});
|
|
160
|
+
}).filter(isConflictChange),
|
|
161
|
+
deleted: conflictingChanges.filter(function (d) {
|
|
162
|
+
return d.remote.size === 0;
|
|
163
|
+
}).map(function (d) {
|
|
164
|
+
return _objectSpread(_objectSpread({}, d), {}, {
|
|
165
|
+
from: mapping.map(d.from),
|
|
166
|
+
to: mapping.map(d.to)
|
|
167
|
+
});
|
|
168
|
+
}).filter(isConflictChange)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -21,7 +21,7 @@ export var NCS_ERROR_CODE = /*#__PURE__*/function (NCS_ERROR_CODE) {
|
|
|
21
21
|
return NCS_ERROR_CODE;
|
|
22
22
|
}({});
|
|
23
23
|
|
|
24
|
-
// TODO: Import emitted error codes from NCS
|
|
24
|
+
// TODO: ED-26957 - Import emitted error codes from NCS
|
|
25
25
|
|
|
26
26
|
// NCS Errors
|
|
27
27
|
// - Step rejection errors
|
|
@@ -9,7 +9,7 @@ import { NCS_ERROR_CODE } from '../errors/ncs-errors';
|
|
|
9
9
|
import { createLogger } from '../helpers/utils';
|
|
10
10
|
var logger = createLogger('commit-step', 'black');
|
|
11
11
|
export var readyToCommit = true;
|
|
12
|
-
export var RESET_READYTOCOMMIT_INTERVAL_MS =
|
|
12
|
+
export var RESET_READYTOCOMMIT_INTERVAL_MS = 5000;
|
|
13
13
|
export var commitStepQueue = function commitStepQueue(_ref) {
|
|
14
14
|
var broadcast = _ref.broadcast,
|
|
15
15
|
steps = _ref.steps,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
|
|
2
|
+
import { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
3
|
+
import type { ConflictChanges } from '@atlaskit/editor-common/collab';
|
|
4
|
+
interface Options {
|
|
5
|
+
localSteps: readonly {
|
|
6
|
+
inverted: ProseMirrorStep;
|
|
7
|
+
step: ProseMirrorStep;
|
|
8
|
+
}[];
|
|
9
|
+
remoteSteps: ProseMirrorStep[];
|
|
10
|
+
tr: Transaction;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gets the conflicts between the local document and the remote document based on steps.
|
|
14
|
+
* It assumes the steps will be rebased using the `prosemirror-collab` algorithm synchronously after this
|
|
15
|
+
* Therefore the `tr` property is based on the document before rebasing.
|
|
16
|
+
*
|
|
17
|
+
* In the future we could possibly use `prosemirror-recreate-steps` (or similar approach)
|
|
18
|
+
* and tweak this to work for arbitrary diffs between offline and remote documents.
|
|
19
|
+
*
|
|
20
|
+
* @param localSteps Local steps applied between now and the server steps
|
|
21
|
+
* @param remoteSteps Steps retrieved from the server
|
|
22
|
+
* @param tr Transaction of the current document (expected to happen with local steps applied, before remote are applied)
|
|
23
|
+
* @returns All the conflicts (inserted + deleted) which can be applied to the current document
|
|
24
|
+
*/
|
|
25
|
+
export declare function getConflictChanges({ localSteps, remoteSteps, tr }: Options): ConflictChanges;
|
|
26
|
+
export {};
|
|
@@ -4,7 +4,7 @@ import type { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/trans
|
|
|
4
4
|
import type AnalyticsHelper from '../analytics/analytics-helper';
|
|
5
5
|
import type { InternalError } from '../errors/internal-errors';
|
|
6
6
|
export declare let readyToCommit: boolean;
|
|
7
|
-
export declare const RESET_READYTOCOMMIT_INTERVAL_MS =
|
|
7
|
+
export declare const RESET_READYTOCOMMIT_INTERVAL_MS = 5000;
|
|
8
8
|
export declare const commitStepQueue: ({ broadcast, steps, version, userId, clientId, onStepsAdded, onErrorHandled, analyticsHelper, emit, __livePage, hasRecovered, collabMode, forcePublish, }: {
|
|
9
9
|
broadcast: <K extends keyof ChannelEvent>(type: K, data: Omit<ChannelEvent[K], 'timestamp'>, callback?: Function) => void;
|
|
10
10
|
steps: readonly ProseMirrorStep[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform';
|
|
2
|
+
import { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
3
|
+
import type { ConflictChanges } from '@atlaskit/editor-common/collab';
|
|
4
|
+
interface Options {
|
|
5
|
+
localSteps: readonly {
|
|
6
|
+
inverted: ProseMirrorStep;
|
|
7
|
+
step: ProseMirrorStep;
|
|
8
|
+
}[];
|
|
9
|
+
remoteSteps: ProseMirrorStep[];
|
|
10
|
+
tr: Transaction;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gets the conflicts between the local document and the remote document based on steps.
|
|
14
|
+
* It assumes the steps will be rebased using the `prosemirror-collab` algorithm synchronously after this
|
|
15
|
+
* Therefore the `tr` property is based on the document before rebasing.
|
|
16
|
+
*
|
|
17
|
+
* In the future we could possibly use `prosemirror-recreate-steps` (or similar approach)
|
|
18
|
+
* and tweak this to work for arbitrary diffs between offline and remote documents.
|
|
19
|
+
*
|
|
20
|
+
* @param localSteps Local steps applied between now and the server steps
|
|
21
|
+
* @param remoteSteps Steps retrieved from the server
|
|
22
|
+
* @param tr Transaction of the current document (expected to happen with local steps applied, before remote are applied)
|
|
23
|
+
* @returns All the conflicts (inserted + deleted) which can be applied to the current document
|
|
24
|
+
*/
|
|
25
|
+
export declare function getConflictChanges({ localSteps, remoteSteps, tr }: Options): ConflictChanges;
|
|
26
|
+
export {};
|
|
@@ -4,7 +4,7 @@ import type { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/trans
|
|
|
4
4
|
import type AnalyticsHelper from '../analytics/analytics-helper';
|
|
5
5
|
import type { InternalError } from '../errors/internal-errors';
|
|
6
6
|
export declare let readyToCommit: boolean;
|
|
7
|
-
export declare const RESET_READYTOCOMMIT_INTERVAL_MS =
|
|
7
|
+
export declare const RESET_READYTOCOMMIT_INTERVAL_MS = 5000;
|
|
8
8
|
export declare const commitStepQueue: ({ broadcast, steps, version, userId, clientId, onStepsAdded, onErrorHandled, analyticsHelper, emit, __livePage, hasRecovered, collabMode, forcePublish, }: {
|
|
9
9
|
broadcast: <K extends keyof ChannelEvent>(type: K, data: Omit<ChannelEvent[K], 'timestamp'>, callback?: Function) => void;
|
|
10
10
|
steps: readonly ProseMirrorStep[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/collab-provider",
|
|
3
|
-
"version": "10.9.
|
|
3
|
+
"version": "10.9.3",
|
|
4
4
|
"description": "A provider for collaborative editing.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@atlaskit/adf-utils": "^19.19.0",
|
|
36
36
|
"@atlaskit/analytics-gas-types": "^5.1.0",
|
|
37
37
|
"@atlaskit/analytics-listeners": "^9.0.0",
|
|
38
|
-
"@atlaskit/editor-common": "^102.
|
|
38
|
+
"@atlaskit/editor-common": "^102.2.0",
|
|
39
39
|
"@atlaskit/editor-json-transformer": "^8.24.0",
|
|
40
40
|
"@atlaskit/editor-prosemirror": "7.0.0",
|
|
41
41
|
"@atlaskit/feature-gate-js-client": "^4.26.0",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@babel/runtime": "^7.0.0",
|
|
48
48
|
"eventemitter2": "^4.1.0",
|
|
49
49
|
"lodash": "^4.17.21",
|
|
50
|
+
"prosemirror-changeset": "^2.2.1",
|
|
50
51
|
"socket.io-client": "^4.7.5",
|
|
51
52
|
"uuid": "^3.1.0"
|
|
52
53
|
},
|