@manuscripts/track-changes-plugin 0.1.1 → 0.4.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/README.md +20 -45
- package/dist/ChangeSet.d.ts +3 -1
- package/dist/actions.d.ts +8 -1
- package/dist/change-steps/diffChangeSteps.d.ts +21 -0
- package/dist/change-steps/processChangeSteps.d.ts +21 -0
- package/dist/{track → changes}/applyChanges.d.ts +0 -0
- package/dist/{track → changes}/findChanges.d.ts +0 -0
- package/dist/{track → changes}/fixInconsistentChanges.d.ts +0 -0
- package/dist/{track → changes}/updateChangeAttrs.d.ts +0 -0
- package/dist/commands.d.ts +1 -1
- package/dist/{track/node-utils.d.ts → compute/nodeHelpers.d.ts} +4 -11
- package/dist/{track/steps → compute}/setFragmentAsInserted.d.ts +1 -1
- package/dist/compute/splitSliceIntoMergedParts.d.ts +41 -0
- package/dist/{index.es.js → index.cjs} +658 -380
- package/dist/index.d.ts +1 -1
- package/dist/index.js +663 -412
- package/dist/{track/steps → mutate}/deleteAndMergeSplitNodes.d.ts +4 -3
- package/dist/{track → mutate}/deleteNode.d.ts +9 -0
- package/dist/mutate/deleteText.d.ts +32 -0
- package/dist/{track → mutate}/mergeNode.d.ts +1 -1
- package/dist/{track/steps → mutate}/mergeTrackedMarks.d.ts +0 -0
- package/dist/{track/steps → steps}/trackReplaceAroundStep.d.ts +3 -2
- package/dist/{track/steps → steps}/trackReplaceStep.d.ts +3 -2
- package/dist/{track → steps}/trackTransaction.d.ts +2 -2
- package/dist/types/change.d.ts +22 -11
- package/dist/types/step.d.ts +52 -0
- package/dist/{track/steps → utils}/track-utils.d.ts +1 -2
- package/package.json +6 -6
- package/dist/types/editor.d.ts +0 -23
package/dist/index.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { PluginKey, Plugin } from 'prosemirror-state';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
import { liftTarget, canJoin, Mapping, ReplaceStep, ReplaceAroundStep } from 'prosemirror-transform';
|
|
4
|
+
import { Fragment, Slice } from 'prosemirror-model';
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var prosemirrorState = require('prosemirror-state');
|
|
6
|
-
var debug = require('debug');
|
|
7
|
-
var prosemirrorTransform = require('prosemirror-transform');
|
|
8
|
-
var prosemirrorModel = require('prosemirror-model');
|
|
9
|
-
|
|
10
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
11
|
-
|
|
12
|
-
var debug__default = /*#__PURE__*/_interopDefaultLegacy(debug);
|
|
13
|
-
|
|
14
|
-
exports.TrackChangesAction = void 0;
|
|
6
|
+
var TrackChangesAction;
|
|
15
7
|
(function (TrackChangesAction) {
|
|
16
8
|
TrackChangesAction["skipTrack"] = "track-changes-skip-tracking";
|
|
17
9
|
TrackChangesAction["setUserID"] = "track-changes-set-user-id";
|
|
@@ -20,7 +12,7 @@ exports.TrackChangesAction = void 0;
|
|
|
20
12
|
TrackChangesAction["updateChanges"] = "track-changes-update-changes";
|
|
21
13
|
TrackChangesAction["refreshChanges"] = "track-changes-refresh-changes";
|
|
22
14
|
TrackChangesAction["applyAndRemoveChanges"] = "track-changes-apply-remove-changes";
|
|
23
|
-
})(
|
|
15
|
+
})(TrackChangesAction || (TrackChangesAction = {}));
|
|
24
16
|
/**
|
|
25
17
|
* Gets the value of a meta field, action payload, of a defined track-changes action.
|
|
26
18
|
* @param tr
|
|
@@ -38,7 +30,14 @@ function getAction(tr, action) {
|
|
|
38
30
|
*/
|
|
39
31
|
function setAction(tr, action, payload) {
|
|
40
32
|
return tr.setMeta(action, payload);
|
|
41
|
-
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Skip tracking for a transaction, use this with caution to avoid race-conditions or just to otherwise
|
|
36
|
+
* omitting applying of track attributes or marks.
|
|
37
|
+
* @param tr
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
const skipTracking = (tr) => setAction(tr, TrackChangesAction.skipTrack, true);
|
|
42
41
|
|
|
43
42
|
/******************************************************************************
|
|
44
43
|
Copyright (c) Microsoft Corporation.
|
|
@@ -83,22 +82,22 @@ function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
|
83
82
|
* See the License for the specific language governing permissions and
|
|
84
83
|
* limitations under the License.
|
|
85
84
|
*/
|
|
86
|
-
|
|
85
|
+
var CHANGE_OPERATION;
|
|
87
86
|
(function (CHANGE_OPERATION) {
|
|
88
87
|
CHANGE_OPERATION["insert"] = "insert";
|
|
89
88
|
CHANGE_OPERATION["delete"] = "delete";
|
|
90
|
-
CHANGE_OPERATION["set_node_attributes"] = "
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
})(
|
|
96
|
-
|
|
89
|
+
CHANGE_OPERATION["set_node_attributes"] = "set_attrs";
|
|
90
|
+
// wrap_with_node = 'wrap_with_node',
|
|
91
|
+
// unwrap_from_node = 'unwrap_from_node',
|
|
92
|
+
// add_mark = 'add_mark',
|
|
93
|
+
// remove_mark = 'remove_mark',
|
|
94
|
+
})(CHANGE_OPERATION || (CHANGE_OPERATION = {}));
|
|
95
|
+
var CHANGE_STATUS;
|
|
97
96
|
(function (CHANGE_STATUS) {
|
|
98
97
|
CHANGE_STATUS["accepted"] = "accepted";
|
|
99
98
|
CHANGE_STATUS["rejected"] = "rejected";
|
|
100
99
|
CHANGE_STATUS["pending"] = "pending";
|
|
101
|
-
})(
|
|
100
|
+
})(CHANGE_STATUS || (CHANGE_STATUS = {}));
|
|
102
101
|
|
|
103
102
|
/*!
|
|
104
103
|
* © 2021 Atypon Systems LLC
|
|
@@ -115,7 +114,7 @@ exports.CHANGE_STATUS = void 0;
|
|
|
115
114
|
* See the License for the specific language governing permissions and
|
|
116
115
|
* limitations under the License.
|
|
117
116
|
*/
|
|
118
|
-
const logger =
|
|
117
|
+
const logger = debug('track');
|
|
119
118
|
const log = {
|
|
120
119
|
info(str, obj) {
|
|
121
120
|
if (obj) {
|
|
@@ -148,10 +147,10 @@ const log = {
|
|
|
148
147
|
*/
|
|
149
148
|
const enableDebug = (enabled) => {
|
|
150
149
|
if (enabled) {
|
|
151
|
-
|
|
150
|
+
debug.enable('track');
|
|
152
151
|
}
|
|
153
152
|
else {
|
|
154
|
-
|
|
153
|
+
debug.disable();
|
|
155
154
|
}
|
|
156
155
|
};
|
|
157
156
|
|
|
@@ -192,16 +191,13 @@ class ChangeSet {
|
|
|
192
191
|
rootNodes.push(currentNodeChange);
|
|
193
192
|
currentNodeChange = undefined;
|
|
194
193
|
}
|
|
195
|
-
if (
|
|
194
|
+
if (currentNodeChange && c.from < currentNodeChange.to) {
|
|
196
195
|
currentNodeChange.children.push(c);
|
|
197
196
|
}
|
|
198
197
|
else if (c.type === 'node-change') {
|
|
199
198
|
currentNodeChange = { ...c, children: [] };
|
|
200
199
|
}
|
|
201
|
-
else
|
|
202
|
-
currentNodeChange.children.push(c);
|
|
203
|
-
}
|
|
204
|
-
else if (c.type === 'text-change') {
|
|
200
|
+
else {
|
|
205
201
|
rootNodes.push(c);
|
|
206
202
|
}
|
|
207
203
|
});
|
|
@@ -211,13 +207,13 @@ class ChangeSet {
|
|
|
211
207
|
return rootNodes;
|
|
212
208
|
}
|
|
213
209
|
get pending() {
|
|
214
|
-
return this.changeTree.filter((c) => c.attrs.status ===
|
|
210
|
+
return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.pending);
|
|
215
211
|
}
|
|
216
212
|
get accepted() {
|
|
217
|
-
return this.changeTree.filter((c) => c.attrs.status ===
|
|
213
|
+
return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.accepted);
|
|
218
214
|
}
|
|
219
215
|
get rejected() {
|
|
220
|
-
return this.changeTree.filter((c) => c.attrs.status ===
|
|
216
|
+
return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.rejected);
|
|
221
217
|
}
|
|
222
218
|
get textChanges() {
|
|
223
219
|
return this.changes.filter((c) => c.type === 'text-change');
|
|
@@ -225,6 +221,9 @@ class ChangeSet {
|
|
|
225
221
|
get nodeChanges() {
|
|
226
222
|
return this.changes.filter((c) => c.type === 'node-change');
|
|
227
223
|
}
|
|
224
|
+
get nodeAttrChanges() {
|
|
225
|
+
return this.changes.filter((c) => c.type === 'node-attr-change');
|
|
226
|
+
}
|
|
228
227
|
get isEmpty() {
|
|
229
228
|
return __classPrivateFieldGet(this, _ChangeSet_changes, "f").length === 0;
|
|
230
229
|
}
|
|
@@ -270,9 +269,7 @@ class ChangeSet {
|
|
|
270
269
|
* @param change
|
|
271
270
|
*/
|
|
272
271
|
static shouldNotDelete(change) {
|
|
273
|
-
|
|
274
|
-
return ((operation === exports.CHANGE_OPERATION.insert && status === exports.CHANGE_STATUS.accepted) ||
|
|
275
|
-
(operation === exports.CHANGE_OPERATION.delete && status === exports.CHANGE_STATUS.rejected));
|
|
272
|
+
return !ChangeSet.shouldDeleteChange(change);
|
|
276
273
|
}
|
|
277
274
|
/**
|
|
278
275
|
* Determines whether a change should be deleted when applying it to the document.
|
|
@@ -280,8 +277,8 @@ class ChangeSet {
|
|
|
280
277
|
*/
|
|
281
278
|
static shouldDeleteChange(change) {
|
|
282
279
|
const { status, operation } = change.attrs;
|
|
283
|
-
return ((operation ===
|
|
284
|
-
(operation ===
|
|
280
|
+
return ((operation === CHANGE_OPERATION.insert && status === CHANGE_STATUS.rejected) ||
|
|
281
|
+
(operation === CHANGE_OPERATION.delete && status === CHANGE_STATUS.accepted));
|
|
285
282
|
}
|
|
286
283
|
/**
|
|
287
284
|
* Checks whether change attributes contain all TrackedAttrs keys with non-undefined values
|
|
@@ -291,10 +288,22 @@ class ChangeSet {
|
|
|
291
288
|
if ('attrs' in attrs) {
|
|
292
289
|
log.warn('passed "attrs" as property to isValidTrackedAttrs(attrs)', attrs);
|
|
293
290
|
}
|
|
294
|
-
const trackedKeys = [
|
|
295
|
-
|
|
291
|
+
const trackedKeys = [
|
|
292
|
+
'id',
|
|
293
|
+
'authorID',
|
|
294
|
+
'operation',
|
|
295
|
+
'status',
|
|
296
|
+
'createdAt',
|
|
297
|
+
'updatedAt',
|
|
298
|
+
];
|
|
299
|
+
// reviewedByID is set optional since either ProseMirror or Yjs doesn't like persisting null values inside attributes objects
|
|
300
|
+
// So it can be either omitted completely or at least be null or string
|
|
301
|
+
const optionalKeys = ['reviewedByID'];
|
|
302
|
+
const entries = Object.entries(attrs).filter(([key, val]) => trackedKeys.includes(key));
|
|
303
|
+
const optionalEntries = Object.entries(attrs).filter(([key, val]) => optionalKeys.includes(key));
|
|
296
304
|
return (entries.length === trackedKeys.length &&
|
|
297
305
|
entries.every(([key, val]) => trackedKeys.includes(key) && val !== undefined) &&
|
|
306
|
+
optionalEntries.every(([key, val]) => optionalKeys.includes(key) && val !== undefined) &&
|
|
298
307
|
(attrs.id || '').length > 0 // Changes created with undefined id have '' as placeholder
|
|
299
308
|
);
|
|
300
309
|
}
|
|
@@ -304,9 +313,89 @@ class ChangeSet {
|
|
|
304
313
|
static isNodeChange(change) {
|
|
305
314
|
return change.type === 'node-change';
|
|
306
315
|
}
|
|
316
|
+
static isNodeAttrChange(change) {
|
|
317
|
+
return change.type === 'node-attr-change';
|
|
318
|
+
}
|
|
307
319
|
}
|
|
308
320
|
_ChangeSet_changes = new WeakMap();
|
|
309
321
|
|
|
322
|
+
/*!
|
|
323
|
+
* © 2021 Atypon Systems LLC
|
|
324
|
+
*
|
|
325
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
326
|
+
* you may not use this file except in compliance with the License.
|
|
327
|
+
* You may obtain a copy of the License at
|
|
328
|
+
*
|
|
329
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
330
|
+
*
|
|
331
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
332
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
333
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
334
|
+
* See the License for the specific language governing permissions and
|
|
335
|
+
* limitations under the License.
|
|
336
|
+
*/
|
|
337
|
+
function uuidv4() {
|
|
338
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
339
|
+
const r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
|
|
340
|
+
return v.toString(16);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function addTrackIdIfDoesntExist(attrs) {
|
|
345
|
+
if (!attrs.id) {
|
|
346
|
+
return {
|
|
347
|
+
id: uuidv4(),
|
|
348
|
+
...attrs,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return attrs;
|
|
352
|
+
}
|
|
353
|
+
function getInlineNodeTrackedMarkData(node, schema) {
|
|
354
|
+
if (!node || !node.isInline) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
const marksTrackedData = [];
|
|
358
|
+
node.marks.forEach((mark) => {
|
|
359
|
+
if (mark.type === schema.marks.tracked_insert || mark.type === schema.marks.tracked_delete) {
|
|
360
|
+
const operation = mark.type === schema.marks.tracked_insert
|
|
361
|
+
? CHANGE_OPERATION.insert
|
|
362
|
+
: CHANGE_OPERATION.delete;
|
|
363
|
+
marksTrackedData.push({ ...mark.attrs.dataTracked, operation });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
if (marksTrackedData.length > 1) {
|
|
367
|
+
log.warn('inline node with more than 1 of tracked marks', marksTrackedData);
|
|
368
|
+
}
|
|
369
|
+
return marksTrackedData[0] || undefined;
|
|
370
|
+
}
|
|
371
|
+
function getNodeTrackedData(node, schema) {
|
|
372
|
+
return !node
|
|
373
|
+
? undefined
|
|
374
|
+
: node.isText
|
|
375
|
+
? getInlineNodeTrackedMarkData(node, schema)
|
|
376
|
+
: node.attrs.dataTracked;
|
|
377
|
+
}
|
|
378
|
+
function equalMarks(n1, n2) {
|
|
379
|
+
return (n1.marks.length === n2.marks.length &&
|
|
380
|
+
n1.marks.every((mark) => n1.marks.find((m) => m.type === mark.type)));
|
|
381
|
+
}
|
|
382
|
+
function shouldMergeTrackedAttributes(left, right) {
|
|
383
|
+
if (!left || !right) {
|
|
384
|
+
log.warn('passed undefined dataTracked attributes to shouldMergeTrackedAttributes', {
|
|
385
|
+
left,
|
|
386
|
+
right,
|
|
387
|
+
});
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
return (left.status === right.status &&
|
|
391
|
+
left.operation === right.operation &&
|
|
392
|
+
left.authorID === right.authorID);
|
|
393
|
+
}
|
|
394
|
+
function getMergeableMarkTrackedAttrs(node, attrs, schema) {
|
|
395
|
+
const nodeAttrs = getInlineNodeTrackedMarkData(node, schema);
|
|
396
|
+
return nodeAttrs && shouldMergeTrackedAttributes(nodeAttrs, attrs) ? nodeAttrs : null;
|
|
397
|
+
}
|
|
398
|
+
|
|
310
399
|
/*!
|
|
311
400
|
* © 2021 Atypon Systems LLC
|
|
312
401
|
*
|
|
@@ -335,15 +424,17 @@ function deleteNode(node, pos, tr) {
|
|
|
335
424
|
var _a;
|
|
336
425
|
const startPos = tr.doc.resolve(pos + 1);
|
|
337
426
|
const range = startPos.blockRange(tr.doc.resolve(startPos.pos - 2 + node.nodeSize));
|
|
338
|
-
const targetDepth = range &&
|
|
339
|
-
// Check with typeof since with
|
|
427
|
+
const targetDepth = range && liftTarget(range);
|
|
428
|
+
// Check with typeof since with prosemirror-transform pre 1.6.0 targetDepth is undefined
|
|
340
429
|
if (range && typeof targetDepth === 'number') {
|
|
341
430
|
return tr.lift(range, targetDepth);
|
|
342
431
|
}
|
|
343
432
|
const resPos = tr.doc.resolve(pos);
|
|
344
|
-
|
|
433
|
+
// Block nodes can be deleted by just removing their start token which should then merge the text
|
|
434
|
+
// content to above node's content (if there is one)
|
|
435
|
+
const canMergeToNodeAbove = (resPos.parent !== tr.doc || resPos.nodeBefore) && node.isBlock && ((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.isText);
|
|
345
436
|
if (canMergeToNodeAbove) {
|
|
346
|
-
return tr.replaceWith(pos - 1, pos + 1,
|
|
437
|
+
return tr.replaceWith(pos - 1, pos + 1, Fragment.empty);
|
|
347
438
|
}
|
|
348
439
|
else {
|
|
349
440
|
// NOTE: there's an edge case where moving content is not possible but because the immediate
|
|
@@ -376,98 +467,22 @@ function deleteNode(node, pos, tr) {
|
|
|
376
467
|
*/
|
|
377
468
|
function mergeNode(node, pos, tr) {
|
|
378
469
|
var _a;
|
|
379
|
-
if (
|
|
470
|
+
if (canJoin(tr.doc, pos)) {
|
|
380
471
|
return tr.join(pos);
|
|
381
472
|
}
|
|
382
|
-
else if (
|
|
473
|
+
else if (canJoin(tr.doc, pos + node.nodeSize)) {
|
|
474
|
+
// TODO should copy the attributes from the merged node below
|
|
383
475
|
return tr.join(pos + node.nodeSize);
|
|
384
476
|
}
|
|
385
|
-
// TODO is this the same thing as join?
|
|
477
|
+
// TODO is this the same thing as join to above?
|
|
386
478
|
const resPos = tr.doc.resolve(pos);
|
|
387
479
|
const canMergeToNodeAbove = (resPos.parent !== tr.doc || resPos.nodeBefore) && ((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.isText);
|
|
388
480
|
if (canMergeToNodeAbove) {
|
|
389
|
-
return tr.replaceWith(pos - 1, pos + 1,
|
|
481
|
+
return tr.replaceWith(pos - 1, pos + 1, Fragment.empty);
|
|
390
482
|
}
|
|
391
483
|
return undefined;
|
|
392
484
|
}
|
|
393
485
|
|
|
394
|
-
/*!
|
|
395
|
-
* © 2021 Atypon Systems LLC
|
|
396
|
-
*
|
|
397
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
398
|
-
* you may not use this file except in compliance with the License.
|
|
399
|
-
* You may obtain a copy of the License at
|
|
400
|
-
*
|
|
401
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
402
|
-
*
|
|
403
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
404
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
405
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
406
|
-
* See the License for the specific language governing permissions and
|
|
407
|
-
* limitations under the License.
|
|
408
|
-
*/
|
|
409
|
-
function uuidv4() {
|
|
410
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
411
|
-
const r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
|
|
412
|
-
return v.toString(16);
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function addTrackIdIfDoesntExist(attrs) {
|
|
417
|
-
if (!attrs.id) {
|
|
418
|
-
return {
|
|
419
|
-
id: uuidv4(),
|
|
420
|
-
...attrs,
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
return attrs;
|
|
424
|
-
}
|
|
425
|
-
function getInlineNodeTrackedMarkData(node, schema) {
|
|
426
|
-
if (!node || !node.isInline) {
|
|
427
|
-
return undefined;
|
|
428
|
-
}
|
|
429
|
-
const marksTrackedData = [];
|
|
430
|
-
node.marks.forEach((mark) => {
|
|
431
|
-
if (mark.type === schema.marks.tracked_insert || mark.type === schema.marks.tracked_delete) {
|
|
432
|
-
const operation = mark.type === schema.marks.tracked_insert
|
|
433
|
-
? exports.CHANGE_OPERATION.insert
|
|
434
|
-
: exports.CHANGE_OPERATION.delete;
|
|
435
|
-
marksTrackedData.push({ ...mark.attrs.dataTracked, operation });
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
if (marksTrackedData.length > 1) {
|
|
439
|
-
log.warn('inline node with more than 1 of tracked marks', marksTrackedData);
|
|
440
|
-
}
|
|
441
|
-
return marksTrackedData[0] || undefined;
|
|
442
|
-
}
|
|
443
|
-
function getNodeTrackedData(node, schema) {
|
|
444
|
-
return !node
|
|
445
|
-
? undefined
|
|
446
|
-
: node.isText
|
|
447
|
-
? getInlineNodeTrackedMarkData(node, schema)
|
|
448
|
-
: node.attrs.dataTracked;
|
|
449
|
-
}
|
|
450
|
-
function equalMarks(n1, n2) {
|
|
451
|
-
return (n1.marks.length === n2.marks.length &&
|
|
452
|
-
n1.marks.every((mark) => n1.marks.find((m) => m.type === mark.type)));
|
|
453
|
-
}
|
|
454
|
-
function shouldMergeTrackedAttributes(left, right) {
|
|
455
|
-
if (!left || !right) {
|
|
456
|
-
log.warn('passed undefined dataTracked attributes to shouldMergeTrackedAttributes', {
|
|
457
|
-
left,
|
|
458
|
-
right,
|
|
459
|
-
});
|
|
460
|
-
return false;
|
|
461
|
-
}
|
|
462
|
-
return (left.status === right.status &&
|
|
463
|
-
left.operation === right.operation &&
|
|
464
|
-
left.userID === right.userID);
|
|
465
|
-
}
|
|
466
|
-
function getMergeableMarkTrackedAttrs(node, attrs, schema) {
|
|
467
|
-
const nodeAttrs = getInlineNodeTrackedMarkData(node, schema);
|
|
468
|
-
return nodeAttrs && shouldMergeTrackedAttributes(nodeAttrs, attrs) ? nodeAttrs : null;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
486
|
function updateChangeAttrs(tr, change, trackedAttrs, schema) {
|
|
472
487
|
const node = tr.doc.nodeAt(change.from);
|
|
473
488
|
if (!node) {
|
|
@@ -505,9 +520,9 @@ function updateChangeChildrenAttributes(changes, tr, mapping) {
|
|
|
505
520
|
* @param changes
|
|
506
521
|
* @param deleteMap
|
|
507
522
|
*/
|
|
508
|
-
function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new
|
|
523
|
+
function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mapping()) {
|
|
509
524
|
changes.forEach((change) => {
|
|
510
|
-
if (change.attrs.status ===
|
|
525
|
+
if (change.attrs.status === CHANGE_STATUS.pending) {
|
|
511
526
|
return;
|
|
512
527
|
}
|
|
513
528
|
// Map change.from and skip those which dont need to be applied
|
|
@@ -540,6 +555,16 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new prose
|
|
|
540
555
|
}
|
|
541
556
|
deleteMap.appendMap(tr.steps[tr.steps.length - 1].getMap());
|
|
542
557
|
}
|
|
558
|
+
else if (ChangeSet.isNodeAttrChange(change) &&
|
|
559
|
+
change.attrs.status === CHANGE_STATUS.accepted) {
|
|
560
|
+
const attrs = { ...node.attrs, dataTracked: null };
|
|
561
|
+
tr.setNodeMarkup(from, undefined, attrs, node.marks);
|
|
562
|
+
}
|
|
563
|
+
else if (ChangeSet.isNodeAttrChange(change) &&
|
|
564
|
+
change.attrs.status === CHANGE_STATUS.rejected) {
|
|
565
|
+
const attrs = { ...change.oldAttrs, dataTracked: null };
|
|
566
|
+
tr.setNodeMarkup(from, undefined, attrs, node.marks);
|
|
567
|
+
}
|
|
543
568
|
});
|
|
544
569
|
return deleteMap;
|
|
545
570
|
}
|
|
@@ -581,6 +606,7 @@ function findChanges(state) {
|
|
|
581
606
|
type: 'text-change',
|
|
582
607
|
from: pos,
|
|
583
608
|
to: pos + node.nodeSize,
|
|
609
|
+
text: node.text,
|
|
584
610
|
attrs,
|
|
585
611
|
},
|
|
586
612
|
node,
|
|
@@ -625,13 +651,15 @@ function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
|
|
|
625
651
|
const iteratedIds = new Set();
|
|
626
652
|
let changed = false;
|
|
627
653
|
changeSet.invalidChanges.forEach((c) => {
|
|
628
|
-
const { id,
|
|
654
|
+
const { id, authorID, operation, reviewedByID, status, createdAt, updatedAt } = c.attrs;
|
|
629
655
|
const newAttrs = {
|
|
630
656
|
...((!id || iteratedIds.has(id) || id.length === 0) && { id: uuidv4() }),
|
|
631
|
-
...(!
|
|
632
|
-
...(!operation && { operation:
|
|
633
|
-
...(!
|
|
657
|
+
...(!authorID && { authorID: trackUserID }),
|
|
658
|
+
...(!operation && { operation: CHANGE_OPERATION.insert }),
|
|
659
|
+
...(!reviewedByID && { reviewedByID: null }),
|
|
660
|
+
...(!status && { status: CHANGE_STATUS.pending }),
|
|
634
661
|
...(!createdAt && { createdAt: Date.now() }),
|
|
662
|
+
...(!updatedAt && { updatedAt: Date.now() }),
|
|
635
663
|
};
|
|
636
664
|
if (Object.keys(newAttrs).length > 0) {
|
|
637
665
|
updateChangeAttrs(newTr, c, { ...c.attrs, ...newAttrs }, schema);
|
|
@@ -642,87 +670,6 @@ function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
|
|
|
642
670
|
return changed;
|
|
643
671
|
}
|
|
644
672
|
|
|
645
|
-
/*!
|
|
646
|
-
* © 2021 Atypon Systems LLC
|
|
647
|
-
*
|
|
648
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
649
|
-
* you may not use this file except in compliance with the License.
|
|
650
|
-
* You may obtain a copy of the License at
|
|
651
|
-
*
|
|
652
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
653
|
-
*
|
|
654
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
655
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
656
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
657
|
-
* See the License for the specific language governing permissions and
|
|
658
|
-
* limitations under the License.
|
|
659
|
-
*/
|
|
660
|
-
function markInlineNodeChange(node, newTrackAttrs, schema) {
|
|
661
|
-
const filtered = node.marks.filter((m) => m.type !== schema.marks.tracked_insert && m.type !== schema.marks.tracked_delete);
|
|
662
|
-
const mark = newTrackAttrs.operation === exports.CHANGE_OPERATION.insert
|
|
663
|
-
? schema.marks.tracked_insert
|
|
664
|
-
: schema.marks.tracked_delete;
|
|
665
|
-
const createdMark = mark.create({
|
|
666
|
-
dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
|
|
667
|
-
});
|
|
668
|
-
return node.mark(filtered.concat(createdMark));
|
|
669
|
-
}
|
|
670
|
-
function recurseNodeContent(node, newTrackAttrs, schema) {
|
|
671
|
-
if (node.isText) {
|
|
672
|
-
return markInlineNodeChange(node, newTrackAttrs, schema);
|
|
673
|
-
}
|
|
674
|
-
else if (node.isBlock || node.isInline) {
|
|
675
|
-
const updatedChildren = [];
|
|
676
|
-
node.content.forEach((child) => {
|
|
677
|
-
updatedChildren.push(recurseNodeContent(child, newTrackAttrs, schema));
|
|
678
|
-
});
|
|
679
|
-
return node.type.create({
|
|
680
|
-
...node.attrs,
|
|
681
|
-
dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
|
|
682
|
-
}, prosemirrorModel.Fragment.fromArray(updatedChildren), node.marks);
|
|
683
|
-
}
|
|
684
|
-
else {
|
|
685
|
-
log.error(`unhandled node type: "${node.type.name}"`, node);
|
|
686
|
-
return node;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
function setFragmentAsInserted(inserted, insertAttrs, schema) {
|
|
690
|
-
// Recurse the content in the inserted slice and either mark it tracked_insert or set node attrs
|
|
691
|
-
const updatedInserted = [];
|
|
692
|
-
inserted.forEach((n) => {
|
|
693
|
-
updatedInserted.push(recurseNodeContent(n, insertAttrs, schema));
|
|
694
|
-
});
|
|
695
|
-
return updatedInserted.length === 0 ? inserted : prosemirrorModel.Fragment.fromArray(updatedInserted);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/*!
|
|
699
|
-
* © 2021 Atypon Systems LLC
|
|
700
|
-
*
|
|
701
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
702
|
-
* you may not use this file except in compliance with the License.
|
|
703
|
-
* You may obtain a copy of the License at
|
|
704
|
-
*
|
|
705
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
706
|
-
*
|
|
707
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
708
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
709
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
710
|
-
* See the License for the specific language governing permissions and
|
|
711
|
-
* limitations under the License.
|
|
712
|
-
*/
|
|
713
|
-
function createNewInsertAttrs(attrs) {
|
|
714
|
-
return {
|
|
715
|
-
...attrs,
|
|
716
|
-
operation: exports.CHANGE_OPERATION.insert,
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
|
-
function createNewDeleteAttrs(attrs) {
|
|
720
|
-
return {
|
|
721
|
-
...attrs,
|
|
722
|
-
operation: exports.CHANGE_OPERATION.delete,
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
|
|
726
673
|
/*!
|
|
727
674
|
* © 2021 Atypon Systems LLC
|
|
728
675
|
*
|
|
@@ -762,7 +709,7 @@ function getMergedNode(node, currentDepth, depth, first) {
|
|
|
762
709
|
};
|
|
763
710
|
}
|
|
764
711
|
const result = [];
|
|
765
|
-
let merged =
|
|
712
|
+
let merged = Fragment.empty;
|
|
766
713
|
node.content.forEach((n, _, i) => {
|
|
767
714
|
if ((first && i === 0) || (!first && i === node.childCount - 1)) {
|
|
768
715
|
const { mergedNodeContent, unmergedContent } = getMergedNode(n, currentDepth + 1, depth, first);
|
|
@@ -777,7 +724,7 @@ function getMergedNode(node, currentDepth, depth, first) {
|
|
|
777
724
|
});
|
|
778
725
|
return {
|
|
779
726
|
mergedNodeContent: merged,
|
|
780
|
-
unmergedContent: result.length > 0 ?
|
|
727
|
+
unmergedContent: result.length > 0 ? Fragment.fromArray(result) : undefined,
|
|
781
728
|
};
|
|
782
729
|
}
|
|
783
730
|
/**
|
|
@@ -818,70 +765,104 @@ function splitSliceIntoMergedParts(insertSlice, mergeEqualSides = false) {
|
|
|
818
765
|
firstMergedNode,
|
|
819
766
|
lastMergedNode,
|
|
820
767
|
};
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/*!
|
|
771
|
+
* © 2021 Atypon Systems LLC
|
|
824
772
|
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
*
|
|
773
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
774
|
+
* you may not use this file except in compliance with the License.
|
|
775
|
+
* You may obtain a copy of the License at
|
|
776
|
+
*
|
|
777
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
778
|
+
*
|
|
779
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
780
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
781
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
782
|
+
* See the License for the specific language governing permissions and
|
|
783
|
+
* limitations under the License.
|
|
834
784
|
*/
|
|
835
|
-
function
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
785
|
+
function markInlineNodeChange(node, newTrackAttrs, schema) {
|
|
786
|
+
const filtered = node.marks.filter((m) => m.type !== schema.marks.tracked_insert && m.type !== schema.marks.tracked_delete);
|
|
787
|
+
const mark = newTrackAttrs.operation === CHANGE_OPERATION.insert
|
|
788
|
+
? schema.marks.tracked_insert
|
|
789
|
+
: schema.marks.tracked_delete;
|
|
790
|
+
const createdMark = mark.create({
|
|
791
|
+
dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
|
|
792
|
+
});
|
|
793
|
+
return node.mark(filtered.concat(createdMark));
|
|
794
|
+
}
|
|
795
|
+
function recurseNodeContent(node, newTrackAttrs, schema) {
|
|
796
|
+
if (node.isText) {
|
|
797
|
+
return markInlineNodeChange(node, newTrackAttrs, schema);
|
|
845
798
|
}
|
|
846
|
-
else {
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
|
|
851
|
-
const fromStartOfMark = start - (leftNode && leftMarks ? leftNode.nodeSize : 0);
|
|
852
|
-
const toEndOfMark = end + (rightNode && rightMarks ? rightNode.nodeSize : 0);
|
|
853
|
-
const dataTracked = addTrackIdIfDoesntExist({
|
|
854
|
-
...leftMarks,
|
|
855
|
-
...rightMarks,
|
|
856
|
-
...deleteAttrs,
|
|
799
|
+
else if (node.isBlock || node.isInline) {
|
|
800
|
+
const updatedChildren = [];
|
|
801
|
+
node.content.forEach((child) => {
|
|
802
|
+
updatedChildren.push(recurseNodeContent(child, newTrackAttrs, schema));
|
|
857
803
|
});
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
/**
|
|
865
|
-
* Deletes inserted block or inline node, otherwise adds `dataTracked` object with CHANGE_STATUS 'deleted'
|
|
866
|
-
* @param node
|
|
867
|
-
* @param pos
|
|
868
|
-
* @param newTr
|
|
869
|
-
* @param deleteAttrs
|
|
870
|
-
*/
|
|
871
|
-
function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
|
|
872
|
-
const dataTracked = node.attrs.dataTracked;
|
|
873
|
-
const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === exports.CHANGE_OPERATION.insert && dataTracked.userID === deleteAttrs.userID;
|
|
874
|
-
if (wasInsertedBySameUser) {
|
|
875
|
-
deleteNode(node, pos, newTr);
|
|
804
|
+
return node.type.create({
|
|
805
|
+
...node.attrs,
|
|
806
|
+
dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
|
|
807
|
+
}, Fragment.fromArray(updatedChildren), node.marks);
|
|
876
808
|
}
|
|
877
809
|
else {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
dataTracked: addTrackIdIfDoesntExist(deleteAttrs),
|
|
881
|
-
};
|
|
882
|
-
newTr.setNodeMarkup(pos, undefined, attrs, node.marks);
|
|
810
|
+
log.error(`unhandled node type: "${node.type.name}"`, node);
|
|
811
|
+
return node;
|
|
883
812
|
}
|
|
884
813
|
}
|
|
814
|
+
function setFragmentAsInserted(inserted, insertAttrs, schema) {
|
|
815
|
+
// Recurse the content in the inserted slice and either mark it tracked_insert or set node attrs
|
|
816
|
+
const updatedInserted = [];
|
|
817
|
+
inserted.forEach((n) => {
|
|
818
|
+
updatedInserted.push(recurseNodeContent(n, insertAttrs, schema));
|
|
819
|
+
});
|
|
820
|
+
return updatedInserted.length === 0 ? inserted : Fragment.fromArray(updatedInserted);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/*!
|
|
824
|
+
* © 2021 Atypon Systems LLC
|
|
825
|
+
*
|
|
826
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
827
|
+
* you may not use this file except in compliance with the License.
|
|
828
|
+
* You may obtain a copy of the License at
|
|
829
|
+
*
|
|
830
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
831
|
+
*
|
|
832
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
833
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
834
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
835
|
+
* See the License for the specific language governing permissions and
|
|
836
|
+
* limitations under the License.
|
|
837
|
+
*/
|
|
838
|
+
function createNewInsertAttrs(attrs) {
|
|
839
|
+
return {
|
|
840
|
+
...attrs,
|
|
841
|
+
operation: CHANGE_OPERATION.insert,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function createNewDeleteAttrs(attrs) {
|
|
845
|
+
return {
|
|
846
|
+
...attrs,
|
|
847
|
+
operation: CHANGE_OPERATION.delete,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/*!
|
|
852
|
+
* © 2021 Atypon Systems LLC
|
|
853
|
+
*
|
|
854
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
855
|
+
* you may not use this file except in compliance with the License.
|
|
856
|
+
* You may obtain a copy of the License at
|
|
857
|
+
*
|
|
858
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
859
|
+
*
|
|
860
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
861
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
862
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
863
|
+
* See the License for the specific language governing permissions and
|
|
864
|
+
* limitations under the License.
|
|
865
|
+
*/
|
|
885
866
|
/**
|
|
886
867
|
* Applies deletion to the doc without actually deleting nodes that have not been inserted
|
|
887
868
|
*
|
|
@@ -908,28 +889,39 @@ function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
|
|
|
908
889
|
* @returns mapping adjusted by the applied operations & modified insert slice
|
|
909
890
|
*/
|
|
910
891
|
function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackAttrs, insertSlice) {
|
|
911
|
-
const deleteMap = new
|
|
912
|
-
const
|
|
892
|
+
const deleteMap = new Mapping();
|
|
893
|
+
const steps = [];
|
|
913
894
|
// No deletion applied, return default values
|
|
914
895
|
if (from === to) {
|
|
915
896
|
return {
|
|
916
897
|
deleteMap,
|
|
917
|
-
mergedInsertPos,
|
|
918
898
|
newSliceContent: insertSlice.content,
|
|
899
|
+
steps,
|
|
919
900
|
};
|
|
920
901
|
}
|
|
921
902
|
const { openStart, openEnd } = insertSlice;
|
|
922
903
|
const { updatedSliceNodes, firstMergedNode, lastMergedNode } = splitSliceIntoMergedParts(insertSlice, gap !== undefined);
|
|
923
|
-
const deleteAttrs = createNewDeleteAttrs(trackAttrs);
|
|
924
904
|
let mergingStartSide = true;
|
|
925
905
|
startDoc.nodesBetween(from, to, (node, pos) => {
|
|
926
906
|
const { pos: offsetPos, deleted: nodeWasDeleted } = deleteMap.mapResult(pos, 1);
|
|
927
907
|
const offsetFrom = deleteMap.map(from, -1);
|
|
928
908
|
const offsetTo = deleteMap.map(to, 1);
|
|
929
|
-
const wasWithinGap = gap && offsetPos >= deleteMap.map(gap.start, -1);
|
|
930
909
|
const nodeEnd = offsetPos + node.nodeSize;
|
|
910
|
+
// So this insane boolean checks for ReplaceAroundStep gaps and whether the node should be skipped
|
|
911
|
+
// since the content inside gap should stay unchanged.
|
|
912
|
+
// All other nodes except text nodes consist of one start and end token (or just a single token for atoms).
|
|
913
|
+
// For them we can just check whether the start token is within the gap eg pos is 10 when gap (8, 18) to
|
|
914
|
+
// determine whether it should be skipped.
|
|
915
|
+
// For text nodes though, since they are continous, they might only partially be enclosed in the gap
|
|
916
|
+
// eg. pos 10 when gap is (8, 18) BUT if their nodeEnd goes past the gap's end eg nodeEnd 20 they actually
|
|
917
|
+
// are altered and should not be skipped.
|
|
918
|
+
// @TODO ATM 20.7.2022 there doesn't seem to be tests that capture this.
|
|
919
|
+
const wasWithinGap = gap &&
|
|
920
|
+
((!node.isText && offsetPos >= deleteMap.map(gap.start, -1)) ||
|
|
921
|
+
(node.isText &&
|
|
922
|
+
offsetPos <= deleteMap.map(gap.start, -1) &&
|
|
923
|
+
nodeEnd >= deleteMap.map(gap.end, -1)));
|
|
931
924
|
let step = newTr.steps[newTr.steps.length - 1];
|
|
932
|
-
// debugger
|
|
933
925
|
// nodeEnd > offsetFrom -> delete touches this node
|
|
934
926
|
// eg (del 6 10) <p 5>|<t 6>cdf</t 9></p 10>| -> <p> nodeEnd 10 > from 6
|
|
935
927
|
//
|
|
@@ -984,24 +976,18 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
|
|
|
984
976
|
// ProseMirror node semantics as start tokens are considered to contain the actual node itself.
|
|
985
977
|
const mergeEndNode = startTokenDeleted && openEnd > 0 && depth === openEnd && mergeContent !== undefined;
|
|
986
978
|
if (mergeStartNode || mergeEndNode) {
|
|
987
|
-
// The default insert position for block nodes is either the start of the merged content or the end.
|
|
988
|
-
// Incase text was merged, this must be updated as the start or end of the node doesn't map to the
|
|
989
|
-
// actual position of the merge. Currently the inserted content is inserted at the start or end
|
|
990
|
-
// of the merged content, TODO reverse the start/end when end/start token?
|
|
991
|
-
let insertPos = mergeStartNode ? nodeEnd - openStart : offsetPos + openEnd;
|
|
992
|
-
if (node.isText) {
|
|
993
|
-
// When merging text we must delete text in the same go as well, as the from/to boundary goes through
|
|
994
|
-
// the text node.
|
|
995
|
-
insertPos = deleteTextIfInserted(node, offsetPos, newTr, schema, deleteAttrs, offsetFrom, offsetTo);
|
|
996
|
-
deleteMap.appendMap(newTr.steps[newTr.steps.length - 1].getMap());
|
|
997
|
-
step = newTr.steps[newTr.steps.length - 1];
|
|
998
|
-
}
|
|
999
979
|
// Just as a fun fact that I found out while debugging this. Inserting text at paragraph position wraps
|
|
1000
980
|
// it into a new paragraph(!). So that's why you always offset your positions to insert it _inside_
|
|
1001
981
|
// the paragraph.
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
982
|
+
steps.push({
|
|
983
|
+
type: 'merge-fragment',
|
|
984
|
+
pos: offsetPos,
|
|
985
|
+
mergePos: mergeStartNode ? nodeEnd - openStart : offsetPos + openEnd,
|
|
986
|
+
from: offsetFrom,
|
|
987
|
+
to: offsetTo,
|
|
988
|
+
node,
|
|
989
|
+
fragment: setFragmentAsInserted(mergeContent, createNewInsertAttrs(trackAttrs), schema),
|
|
990
|
+
});
|
|
1005
991
|
// Okay this is a bit ridiculous but it's used to adjust the insert pos when track changes prevents deletions
|
|
1006
992
|
// of merged nodes & content, as just using mapped toA in that case isn't the same.
|
|
1007
993
|
// The calculation is a bit mysterious, I admit.
|
|
@@ -1014,12 +1000,23 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
|
|
|
1014
1000
|
else if (node.isText) {
|
|
1015
1001
|
// Text deletion is handled even when the deletion doesn't completely wrap the text node
|
|
1016
1002
|
// (which is basically the case most of the time)
|
|
1017
|
-
|
|
1003
|
+
steps.push({
|
|
1004
|
+
type: 'delete-text',
|
|
1005
|
+
pos: offsetPos,
|
|
1006
|
+
from: Math.max(offsetPos, offsetFrom),
|
|
1007
|
+
to: Math.min(nodeEnd, offsetTo),
|
|
1008
|
+
node,
|
|
1009
|
+
});
|
|
1018
1010
|
}
|
|
1019
1011
|
else ;
|
|
1020
1012
|
}
|
|
1021
1013
|
else if (nodeCompletelyDeleted) {
|
|
1022
|
-
|
|
1014
|
+
steps.push({
|
|
1015
|
+
type: 'delete-node',
|
|
1016
|
+
pos: offsetPos,
|
|
1017
|
+
nodeEnd: nodeEnd,
|
|
1018
|
+
node,
|
|
1019
|
+
});
|
|
1023
1020
|
}
|
|
1024
1021
|
}
|
|
1025
1022
|
const newestStep = newTr.steps[newTr.steps.length - 1];
|
|
@@ -1029,10 +1026,10 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
|
|
|
1029
1026
|
});
|
|
1030
1027
|
return {
|
|
1031
1028
|
deleteMap,
|
|
1032
|
-
mergedInsertPos,
|
|
1033
1029
|
newSliceContent: updatedSliceNodes
|
|
1034
|
-
?
|
|
1030
|
+
? Fragment.fromArray(updatedSliceNodes)
|
|
1035
1031
|
: insertSlice.content,
|
|
1032
|
+
steps,
|
|
1036
1033
|
};
|
|
1037
1034
|
}
|
|
1038
1035
|
|
|
@@ -1055,18 +1052,20 @@ function mergeTrackedMarks(pos, doc, newTr, schema) {
|
|
|
1055
1052
|
if (!nodeAfter || !nodeBefore || !leftMark || !rightMark || leftMark.type !== rightMark.type) {
|
|
1056
1053
|
return;
|
|
1057
1054
|
}
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
if (!shouldMergeTrackedAttributes(
|
|
1055
|
+
const leftDataTracked = leftMark.attrs.dataTracked;
|
|
1056
|
+
const rightDataTracked = rightMark.attrs.dataTracked;
|
|
1057
|
+
if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
|
|
1061
1058
|
return;
|
|
1062
1059
|
}
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1060
|
+
const isLeftOlder = (leftDataTracked.createdAt || 0) < (rightDataTracked.createdAt || 0);
|
|
1061
|
+
const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
|
|
1062
|
+
const dataTracked = {
|
|
1063
|
+
...ancestorAttrs,
|
|
1064
|
+
updatedAt: Date.now(),
|
|
1066
1065
|
};
|
|
1067
1066
|
const fromStartOfMark = pos - nodeBefore.nodeSize;
|
|
1068
1067
|
const toEndOfMark = pos + nodeAfter.nodeSize;
|
|
1069
|
-
newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create(
|
|
1068
|
+
newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create({ ...leftMark.attrs, dataTracked }));
|
|
1070
1069
|
}
|
|
1071
1070
|
|
|
1072
1071
|
/*!
|
|
@@ -1114,14 +1113,16 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
|
|
|
1114
1113
|
const stepResult = newTr.maybeStep(newStep);
|
|
1115
1114
|
if (stepResult.failed) {
|
|
1116
1115
|
log.error(`inverting ReplaceAroundStep failed: "${stepResult.failed}"`, newStep);
|
|
1117
|
-
return;
|
|
1116
|
+
return [];
|
|
1118
1117
|
}
|
|
1119
1118
|
const gap = oldState.doc.slice(gapFrom, gapTo);
|
|
1120
1119
|
log.info('RETAINED GAP CONTENT', gap);
|
|
1121
1120
|
// First apply the deleted range and update the insert slice to not include content that was deleted,
|
|
1122
1121
|
// eg partial nodes in an open-ended slice
|
|
1123
|
-
const { deleteMap, newSliceContent } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
|
|
1122
|
+
const { deleteMap, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
|
|
1123
|
+
let steps = deleteSteps;
|
|
1124
1124
|
log.info('TR: new steps after applying delete', [...newTr.steps]);
|
|
1125
|
+
log.info('DELETE STEPS: ', deleteSteps);
|
|
1125
1126
|
// We only want to insert when there something inside the gap (actually would this be always true?)
|
|
1126
1127
|
// or insert slice wasn't just start/end tokens (which we already merged inside deleteAndMergeSplitBlockNodes)
|
|
1127
1128
|
if (gap.size > 0 || (!structure && newSliceContent.size > 0)) {
|
|
@@ -1130,27 +1131,25 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
|
|
|
1130
1131
|
// the sides should be equal. TODO can they be other than 0?
|
|
1131
1132
|
const openStart = slice.openStart !== slice.openEnd || newSliceContent.size === 0 ? 0 : slice.openStart;
|
|
1132
1133
|
const openEnd = slice.openStart !== slice.openEnd || newSliceContent.size === 0 ? 0 : slice.openEnd;
|
|
1133
|
-
let insertedSlice = new
|
|
1134
|
+
let insertedSlice = new Slice(setFragmentAsInserted(newSliceContent, createNewInsertAttrs(attrs), oldState.schema), openStart, openEnd);
|
|
1134
1135
|
if (gap.size > 0) {
|
|
1135
1136
|
log.info('insertedSlice before inserted gap', insertedSlice);
|
|
1136
1137
|
insertedSlice = insertedSlice.insertAt(insertedSlice.size === 0 ? 0 : insert, gap.content);
|
|
1137
1138
|
log.info('insertedSlice after inserted gap', insertedSlice);
|
|
1138
1139
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
log.info('new steps after applying insert', [...newTr.steps]);
|
|
1146
|
-
mergeTrackedMarks(deleteMap.map(gapFrom), newTr.doc, newTr, oldState.schema);
|
|
1147
|
-
mergeTrackedMarks(deleteMap.map(gapTo), newTr.doc, newTr, oldState.schema);
|
|
1140
|
+
deleteSteps.push({
|
|
1141
|
+
type: 'insert-slice',
|
|
1142
|
+
from: deleteMap.map(gapFrom),
|
|
1143
|
+
to: deleteMap.map(gapTo),
|
|
1144
|
+
slice: insertedSlice,
|
|
1145
|
+
});
|
|
1148
1146
|
}
|
|
1149
1147
|
else {
|
|
1150
1148
|
// Incase only deletion was applied, check whether tracked marks around deleted content can be merged
|
|
1151
1149
|
mergeTrackedMarks(deleteMap.map(gapFrom), newTr.doc, newTr, oldState.schema);
|
|
1152
1150
|
mergeTrackedMarks(deleteMap.map(gapTo), newTr.doc, newTr, oldState.schema);
|
|
1153
1151
|
}
|
|
1152
|
+
return steps;
|
|
1154
1153
|
}
|
|
1155
1154
|
|
|
1156
1155
|
/*!
|
|
@@ -1170,7 +1169,7 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
|
|
|
1170
1169
|
*/
|
|
1171
1170
|
function trackReplaceStep(step, oldState, newTr, attrs) {
|
|
1172
1171
|
log.info('###### ReplaceStep ######');
|
|
1173
|
-
let selectionPos = 0;
|
|
1172
|
+
let selectionPos = 0, changeSteps = [];
|
|
1174
1173
|
step.getMap().forEach((fromA, toA, fromB, toB) => {
|
|
1175
1174
|
log.info(`changed ranges: ${fromA} ${toA} ${fromB} ${toB}`);
|
|
1176
1175
|
const { slice } = step;
|
|
@@ -1181,36 +1180,309 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
|
|
|
1181
1180
|
log.error(`invert ReplaceStep failed: "${stepResult.failed}"`, newStep);
|
|
1182
1181
|
return;
|
|
1183
1182
|
}
|
|
1183
|
+
log.info('TR: steps before applying delete', [...newTr.steps]);
|
|
1184
1184
|
// First apply the deleted range and update the insert slice to not include content that was deleted,
|
|
1185
1185
|
// eg partial nodes in an open-ended slice
|
|
1186
|
-
const { deleteMap,
|
|
1187
|
-
|
|
1188
|
-
|
|
1186
|
+
const { deleteMap, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(fromA, toA, undefined, oldState.doc, newTr, oldState.schema, attrs, slice);
|
|
1187
|
+
changeSteps.push(...deleteSteps);
|
|
1188
|
+
log.info('TR: steps after applying delete', [...newTr.steps]);
|
|
1189
|
+
log.info('DELETE STEPS: ', changeSteps);
|
|
1190
|
+
const adjustedInsertPos = deleteMap.map(toA);
|
|
1189
1191
|
if (newSliceContent.size > 0) {
|
|
1190
1192
|
log.info('newSliceContent', newSliceContent);
|
|
1191
1193
|
// Since deleteAndMergeSplitBlockNodes modified the slice to not to contain any merged nodes,
|
|
1192
1194
|
// the sides should be equal. TODO can they be other than 0?
|
|
1193
1195
|
const openStart = slice.openStart !== slice.openEnd ? 0 : slice.openStart;
|
|
1194
1196
|
const openEnd = slice.openStart !== slice.openEnd ? 0 : slice.openEnd;
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
+
changeSteps.push({
|
|
1198
|
+
type: 'insert-slice',
|
|
1199
|
+
from: adjustedInsertPos,
|
|
1200
|
+
to: adjustedInsertPos,
|
|
1201
|
+
slice: new Slice(setFragmentAsInserted(newSliceContent, createNewInsertAttrs(attrs), oldState.schema), openStart, openEnd),
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
// Incase only deletion was applied, check whether tracked marks around deleted content can be merged
|
|
1206
|
+
mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema);
|
|
1207
|
+
selectionPos = fromA;
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
return [changeSteps, selectionPos];
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/*!
|
|
1214
|
+
* © 2021 Atypon Systems LLC
|
|
1215
|
+
*
|
|
1216
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1217
|
+
* you may not use this file except in compliance with the License.
|
|
1218
|
+
* You may obtain a copy of the License at
|
|
1219
|
+
*
|
|
1220
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1221
|
+
*
|
|
1222
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1223
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1224
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1225
|
+
* See the License for the specific language governing permissions and
|
|
1226
|
+
* limitations under the License.
|
|
1227
|
+
*/
|
|
1228
|
+
/**
|
|
1229
|
+
* Deletes inserted text directly, otherwise wraps it with tracked_delete mark
|
|
1230
|
+
*
|
|
1231
|
+
* This would work for general inline nodes too, but since node marks don't work properly
|
|
1232
|
+
* with Yjs, attributes are used instead.
|
|
1233
|
+
* @param node
|
|
1234
|
+
* @param pos
|
|
1235
|
+
* @param newTr
|
|
1236
|
+
* @param schema
|
|
1237
|
+
* @param deleteAttrs
|
|
1238
|
+
* @param from
|
|
1239
|
+
* @param to
|
|
1240
|
+
*/
|
|
1241
|
+
function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
|
|
1242
|
+
const start = from ? Math.max(pos, from) : pos;
|
|
1243
|
+
const nodeEnd = pos + node.nodeSize;
|
|
1244
|
+
const end = to ? Math.min(nodeEnd, to) : nodeEnd;
|
|
1245
|
+
if (node.marks.find((m) => m.type === schema.marks.tracked_insert)) {
|
|
1246
|
+
// Math.max(pos, from) is for picking always the start of the node,
|
|
1247
|
+
// not the start of the change (which might span multiple nodes).
|
|
1248
|
+
// Pos can be less than from as nodesBetween iterates through all nodes starting from the top block node
|
|
1249
|
+
newTr.replaceWith(start, end, Fragment.empty);
|
|
1250
|
+
return start;
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
const leftNode = newTr.doc.resolve(start).nodeBefore;
|
|
1254
|
+
const leftMarks = getMergeableMarkTrackedAttrs(leftNode, deleteAttrs, schema);
|
|
1255
|
+
const rightNode = newTr.doc.resolve(end).nodeAfter;
|
|
1256
|
+
const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
|
|
1257
|
+
const fromStartOfMark = start - (leftNode && leftMarks ? leftNode.nodeSize : 0);
|
|
1258
|
+
const toEndOfMark = end + (rightNode && rightMarks ? rightNode.nodeSize : 0);
|
|
1259
|
+
const createdAt = Math.min((leftMarks === null || leftMarks === void 0 ? void 0 : leftMarks.createdAt) || Number.MAX_VALUE, (rightMarks === null || rightMarks === void 0 ? void 0 : rightMarks.createdAt) || Number.MAX_VALUE, deleteAttrs.createdAt);
|
|
1260
|
+
const dataTracked = addTrackIdIfDoesntExist({
|
|
1261
|
+
...leftMarks,
|
|
1262
|
+
...rightMarks,
|
|
1263
|
+
...deleteAttrs,
|
|
1264
|
+
createdAt,
|
|
1265
|
+
});
|
|
1266
|
+
newTr.addMark(fromStartOfMark, toEndOfMark, schema.marks.tracked_delete.create({
|
|
1267
|
+
dataTracked,
|
|
1268
|
+
}));
|
|
1269
|
+
return toEndOfMark;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/*!
|
|
1274
|
+
* © 2021 Atypon Systems LLC
|
|
1275
|
+
*
|
|
1276
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1277
|
+
* you may not use this file except in compliance with the License.
|
|
1278
|
+
* You may obtain a copy of the License at
|
|
1279
|
+
*
|
|
1280
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1281
|
+
*
|
|
1282
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1283
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1284
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1285
|
+
* See the License for the specific language governing permissions and
|
|
1286
|
+
* limitations under the License.
|
|
1287
|
+
*/
|
|
1288
|
+
function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
|
|
1289
|
+
const mapping = new Mapping();
|
|
1290
|
+
const deleteAttrs = createNewDeleteAttrs(emptyAttrs);
|
|
1291
|
+
let selectionPos = startPos;
|
|
1292
|
+
// @TODO add custom handler / condition?
|
|
1293
|
+
changes.forEach((c) => {
|
|
1294
|
+
let step = newTr.steps[newTr.steps.length - 1];
|
|
1295
|
+
log.info('process change: ', c);
|
|
1296
|
+
// const handled = customStepHandler(changes, newTr, emptyAttrs) // ChangeStep[] | undefined
|
|
1297
|
+
if (c.type === 'delete-node') {
|
|
1298
|
+
const dataTracked = c.node.attrs.dataTracked;
|
|
1299
|
+
const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === CHANGE_OPERATION.insert &&
|
|
1300
|
+
dataTracked.authorID === emptyAttrs.authorID;
|
|
1301
|
+
if (wasInsertedBySameUser) {
|
|
1302
|
+
deleteNode(c.node, mapping.map(c.pos), newTr);
|
|
1303
|
+
const newestStep = newTr.steps[newTr.steps.length - 1];
|
|
1304
|
+
if (step !== newestStep) {
|
|
1305
|
+
mapping.appendMap(newestStep.getMap());
|
|
1306
|
+
step = newestStep;
|
|
1307
|
+
}
|
|
1308
|
+
mergeTrackedMarks(mapping.map(c.pos), newTr.doc, newTr, schema);
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
const attrs = {
|
|
1312
|
+
...c.node.attrs,
|
|
1313
|
+
dataTracked: addTrackIdIfDoesntExist(deleteAttrs),
|
|
1314
|
+
};
|
|
1315
|
+
newTr.setNodeMarkup(mapping.map(c.pos), undefined, attrs, c.node.marks);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
else if (c.type === 'delete-text') {
|
|
1319
|
+
const from = mapping.map(c.from, -1);
|
|
1320
|
+
const to = mapping.map(c.to, 1);
|
|
1321
|
+
const node = newTr.doc.nodeAt(mapping.map(c.pos));
|
|
1322
|
+
if (node === null || node === void 0 ? void 0 : node.marks.find((m) => m.type === schema.marks.tracked_insert)) {
|
|
1323
|
+
newTr.replaceWith(from, to, Fragment.empty);
|
|
1324
|
+
mergeTrackedMarks(from, newTr.doc, newTr, schema);
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
const leftNode = newTr.doc.resolve(from).nodeBefore;
|
|
1328
|
+
const leftMarks = getMergeableMarkTrackedAttrs(leftNode, deleteAttrs, schema);
|
|
1329
|
+
const rightNode = newTr.doc.resolve(to).nodeAfter;
|
|
1330
|
+
const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
|
|
1331
|
+
const fromStartOfMark = from - (leftNode && leftMarks ? leftNode.nodeSize : 0);
|
|
1332
|
+
const toEndOfMark = to + (rightNode && rightMarks ? rightNode.nodeSize : 0);
|
|
1333
|
+
const dataTracked = addTrackIdIfDoesntExist({
|
|
1334
|
+
...leftMarks,
|
|
1335
|
+
...rightMarks,
|
|
1336
|
+
...deleteAttrs,
|
|
1337
|
+
});
|
|
1338
|
+
newTr.addMark(fromStartOfMark, toEndOfMark, schema.marks.tracked_delete.create({
|
|
1339
|
+
dataTracked,
|
|
1340
|
+
}));
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
else if (c.type === 'merge-fragment') {
|
|
1344
|
+
let insertPos = mapping.map(c.mergePos);
|
|
1345
|
+
// The default insert position for block nodes is either the start of the merged content or the end.
|
|
1346
|
+
// Incase text was merged, this must be updated as the start or end of the node doesn't map to the
|
|
1347
|
+
// actual position of the merge. Currently the inserted content is inserted at the start or end
|
|
1348
|
+
// of the merged content, TODO reverse the start/end when end/start token?
|
|
1349
|
+
if (c.node.isText) {
|
|
1350
|
+
// When merging text we must delete text in the same go as well, as the from/to boundary goes through
|
|
1351
|
+
// the text node.
|
|
1352
|
+
insertPos = deleteTextIfInserted(c.node, mapping.map(c.pos), newTr, schema, deleteAttrs, mapping.map(c.from), mapping.map(c.to));
|
|
1353
|
+
const newestStep = newTr.steps[newTr.steps.length - 1];
|
|
1354
|
+
if (step !== newestStep) {
|
|
1355
|
+
mapping.appendMap(newestStep.getMap());
|
|
1356
|
+
step = newestStep;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
if (c.fragment.size > 0) {
|
|
1360
|
+
newTr.insert(insertPos, c.fragment);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
else if (c.type === 'insert-slice') {
|
|
1364
|
+
const newStep = new ReplaceStep(mapping.map(c.from), mapping.map(c.to), c.slice, false);
|
|
1197
1365
|
const stepResult = newTr.maybeStep(newStep);
|
|
1198
1366
|
if (stepResult.failed) {
|
|
1199
1367
|
log.error(`insert ReplaceStep failed: "${stepResult.failed}"`, newStep);
|
|
1200
1368
|
return;
|
|
1201
1369
|
}
|
|
1202
|
-
|
|
1203
|
-
mergeTrackedMarks(
|
|
1204
|
-
|
|
1205
|
-
selectionPos = adjustedInsertPos + insertedSlice.size;
|
|
1370
|
+
mergeTrackedMarks(mapping.map(c.from), newTr.doc, newTr, schema);
|
|
1371
|
+
mergeTrackedMarks(mapping.map(c.to), newTr.doc, newTr, schema);
|
|
1372
|
+
selectionPos = mapping.map(c.to) + c.slice.size;
|
|
1206
1373
|
}
|
|
1207
|
-
else {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1374
|
+
else if (c.type === 'update-node-attrs') {
|
|
1375
|
+
const oldDataTracked = c.oldAttrs.dataTracked;
|
|
1376
|
+
const oldAttrs = (oldDataTracked === null || oldDataTracked === void 0 ? void 0 : oldDataTracked.operation) === CHANGE_OPERATION.set_node_attributes
|
|
1377
|
+
? oldDataTracked.oldAttrs
|
|
1378
|
+
: c.oldAttrs;
|
|
1379
|
+
const dataTracked = addTrackIdIfDoesntExist({
|
|
1380
|
+
...oldDataTracked,
|
|
1381
|
+
oldAttrs,
|
|
1382
|
+
...emptyAttrs,
|
|
1383
|
+
operation: CHANGE_OPERATION.set_node_attributes,
|
|
1384
|
+
});
|
|
1385
|
+
newTr.setNodeMarkup(mapping.map(c.pos), undefined, { ...c.newAttrs, dataTracked });
|
|
1386
|
+
}
|
|
1387
|
+
const newestStep = newTr.steps[newTr.steps.length - 1];
|
|
1388
|
+
if (step !== newestStep) {
|
|
1389
|
+
mapping.appendMap(newestStep.getMap());
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
return [mapping, selectionPos];
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/*!
|
|
1396
|
+
* © 2021 Atypon Systems LLC
|
|
1397
|
+
*
|
|
1398
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1399
|
+
* you may not use this file except in compliance with the License.
|
|
1400
|
+
* You may obtain a copy of the License at
|
|
1401
|
+
*
|
|
1402
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1403
|
+
*
|
|
1404
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1405
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1406
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1407
|
+
* See the License for the specific language governing permissions and
|
|
1408
|
+
* limitations under the License.
|
|
1409
|
+
*/
|
|
1410
|
+
function matchInserted(inDeleted, deleted, inserted, newTr, schema) {
|
|
1411
|
+
var _a;
|
|
1412
|
+
for (let i = 0;; i += 1) {
|
|
1413
|
+
if (inserted.childCount === i)
|
|
1414
|
+
return [inDeleted, deleted];
|
|
1415
|
+
const child = inserted.child(i);
|
|
1416
|
+
// @ts-ignore
|
|
1417
|
+
let adjDeleted = deleted.find((d) => (d.type === 'delete-text' && d.to === inDeleted) ||
|
|
1418
|
+
(d.type === 'delete-node' && d.nodeEnd === inDeleted));
|
|
1419
|
+
if (child.type !== ((_a = adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node) === null || _a === void 0 ? void 0 : _a.type)) {
|
|
1420
|
+
return [inDeleted, deleted];
|
|
1421
|
+
}
|
|
1422
|
+
else if (child.isText && (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node)) {
|
|
1423
|
+
adjDeleted = adjDeleted;
|
|
1424
|
+
const { pos, from, to, node } = adjDeleted;
|
|
1425
|
+
let j = 0, d = from - pos, maxSteps = Math.max(pos, from) - to;
|
|
1426
|
+
// Match text inside the inserted text node to the deleted text node
|
|
1427
|
+
for (; maxSteps !== j && child.text[j] === node.text[d]; j += 1, d += 1) {
|
|
1428
|
+
inDeleted -= 1;
|
|
1429
|
+
}
|
|
1430
|
+
// this is needed incase diffing tr.doc
|
|
1431
|
+
// deleted.push({
|
|
1432
|
+
// pos: pos,
|
|
1433
|
+
// type: 'update-node-attrs',
|
|
1434
|
+
// // Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
|
|
1435
|
+
// oldAttrs: adjDeleted.node.attrs || {},
|
|
1436
|
+
// newAttrs: child.attrs || {},
|
|
1437
|
+
// })
|
|
1438
|
+
if (maxSteps !== j) {
|
|
1439
|
+
deleted.push({
|
|
1440
|
+
pos,
|
|
1441
|
+
from: Math.max(pos, from) + j,
|
|
1442
|
+
to,
|
|
1443
|
+
type: 'delete-text',
|
|
1444
|
+
node,
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
return [inDeleted, deleted.filter((d) => d !== adjDeleted)];
|
|
1448
|
+
}
|
|
1449
|
+
else if (child.content.size > 0 || (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node.content.size) > 0) {
|
|
1450
|
+
// Move the inDeleted inside the block node's boundary
|
|
1451
|
+
return matchInserted(inDeleted - 1, deleted.filter((d) => d !== adjDeleted), child.content);
|
|
1452
|
+
}
|
|
1453
|
+
deleted.push({
|
|
1454
|
+
pos: adjDeleted.pos,
|
|
1455
|
+
type: 'update-node-attrs',
|
|
1456
|
+
// Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
|
|
1457
|
+
oldAttrs: adjDeleted.node.attrs || {},
|
|
1458
|
+
newAttrs: child.attrs || {},
|
|
1459
|
+
});
|
|
1460
|
+
deleted = deleted.filter((d) => d !== adjDeleted);
|
|
1461
|
+
inDeleted -= child.nodeSize;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
function diffChangeSteps(deleted, inserted, newTr, schema) {
|
|
1465
|
+
const updated = [];
|
|
1466
|
+
let updatedDeleted = [...deleted];
|
|
1467
|
+
inserted.forEach((ins) => {
|
|
1468
|
+
const [inDeleted, updatedDel] = matchInserted(ins.from, updatedDeleted, ins.slice.content);
|
|
1469
|
+
if (inDeleted === ins.from) {
|
|
1470
|
+
updated.push(ins);
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
updatedDeleted = updatedDel;
|
|
1474
|
+
const newInsertedA = ins.slice.content.cut(ins.from - inDeleted);
|
|
1475
|
+
const newInsertedB = ins.slice.content.cut(ins.from - inDeleted + 1);
|
|
1476
|
+
// Super hax to cut over block node boundaries in the inserted fragment
|
|
1477
|
+
const newInserted = newInsertedA.size === newInsertedB.size + 2 ? newInsertedB : newInsertedA;
|
|
1478
|
+
if (newInserted.size > 0) {
|
|
1479
|
+
updated.push({
|
|
1480
|
+
...ins,
|
|
1481
|
+
slice: new Slice(newInserted, ins.slice.openStart, ins.slice.openEnd),
|
|
1482
|
+
});
|
|
1211
1483
|
}
|
|
1212
1484
|
});
|
|
1213
|
-
return
|
|
1485
|
+
return [...updatedDeleted, ...updated];
|
|
1214
1486
|
}
|
|
1215
1487
|
|
|
1216
1488
|
/**
|
|
@@ -1234,15 +1506,16 @@ const getSelectionStaticConstructor = (sel) => Object.getPrototypeOf(sel).constr
|
|
|
1234
1506
|
* @param tr Original transaction
|
|
1235
1507
|
* @param oldState State before transaction
|
|
1236
1508
|
* @param newTr Transaction created from the new editor state
|
|
1237
|
-
* @param
|
|
1509
|
+
* @param authorID User id
|
|
1238
1510
|
* @returns newTr that inverts the initial tr and applies track attributes/marks
|
|
1239
1511
|
*/
|
|
1240
|
-
function trackTransaction(tr, oldState, newTr,
|
|
1241
|
-
var _a;
|
|
1512
|
+
function trackTransaction(tr, oldState, newTr, authorID) {
|
|
1242
1513
|
const emptyAttrs = {
|
|
1243
|
-
|
|
1514
|
+
authorID,
|
|
1515
|
+
reviewedByID: null,
|
|
1244
1516
|
createdAt: tr.time,
|
|
1245
|
-
|
|
1517
|
+
updatedAt: tr.time,
|
|
1518
|
+
status: CHANGE_STATUS.pending,
|
|
1246
1519
|
};
|
|
1247
1520
|
// Must use constructor.name instead of instanceof as aliasing prosemirror-state is a lot more
|
|
1248
1521
|
// difficult than prosemirror-transform
|
|
@@ -1257,13 +1530,20 @@ function trackTransaction(tr, oldState, newTr, userID) {
|
|
|
1257
1530
|
'This is probably an error with the library, please report back to maintainers with a reproduction if possible', newTr);
|
|
1258
1531
|
return;
|
|
1259
1532
|
}
|
|
1260
|
-
else if (!(step instanceof
|
|
1533
|
+
else if (!(step instanceof ReplaceStep) && step.constructor.name === 'ReplaceStep') {
|
|
1261
1534
|
console.error('@manuscripts/track-changes-plugin: Multiple prosemirror-transform packages imported, alias/dedupe them ' +
|
|
1262
1535
|
'or instanceof checks fail as well as creating new steps');
|
|
1263
1536
|
return;
|
|
1264
1537
|
}
|
|
1265
|
-
else if (step instanceof
|
|
1266
|
-
|
|
1538
|
+
else if (step instanceof ReplaceStep) {
|
|
1539
|
+
let [steps, startPos] = trackReplaceStep(step, oldState, newTr, emptyAttrs);
|
|
1540
|
+
log.info('CHANGES: ', steps);
|
|
1541
|
+
// deleted and merged really...
|
|
1542
|
+
const deleted = steps.filter((s) => s.type !== 'insert-slice');
|
|
1543
|
+
const inserted = steps.filter((s) => s.type === 'insert-slice');
|
|
1544
|
+
steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
|
|
1545
|
+
log.info('DIFFED STEPS: ', steps);
|
|
1546
|
+
const [mapping, selectionPos] = processChangeSteps(steps, startPos, newTr, emptyAttrs, oldState.schema);
|
|
1267
1547
|
if (!wasNodeSelection) {
|
|
1268
1548
|
const sel = getSelectionStaticConstructor(tr.selection);
|
|
1269
1549
|
// Use Selection.near to fix selections that point to a block node instead of inline content
|
|
@@ -1273,11 +1553,17 @@ function trackTransaction(tr, oldState, newTr, userID) {
|
|
|
1273
1553
|
newTr.setSelection(near);
|
|
1274
1554
|
}
|
|
1275
1555
|
}
|
|
1276
|
-
else if (step instanceof
|
|
1277
|
-
trackReplaceAroundStep(step, oldState, newTr, emptyAttrs);
|
|
1278
|
-
|
|
1279
|
-
|
|
1556
|
+
else if (step instanceof ReplaceAroundStep) {
|
|
1557
|
+
let steps = trackReplaceAroundStep(step, oldState, newTr, emptyAttrs);
|
|
1558
|
+
const deleted = steps.filter((s) => s.type !== 'insert-slice');
|
|
1559
|
+
const inserted = steps.filter((s) => s.type === 'insert-slice');
|
|
1560
|
+
log.info('INSERT STEPS: ', inserted);
|
|
1561
|
+
steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
|
|
1562
|
+
log.info('DIFFED STEPS: ', steps);
|
|
1563
|
+
processChangeSteps(steps, tr.selection.from, newTr, emptyAttrs, oldState.schema);
|
|
1280
1564
|
}
|
|
1565
|
+
// } else if (step instanceof AddMarkStep) {
|
|
1566
|
+
// } else if (step instanceof RemoveMarkStep) {
|
|
1281
1567
|
// TODO: here we could check whether adjacent inserts & deletes cancel each other out.
|
|
1282
1568
|
// However, this should not be done by diffing and only matching node or char by char instead since
|
|
1283
1569
|
// it's A easier and B more intuitive to user.
|
|
@@ -1289,24 +1575,24 @@ function trackTransaction(tr, oldState, newTr, userID) {
|
|
|
1289
1575
|
tr.getMeta('uiEvent') && newTr.setMeta('uiEvent', tr.getMeta('uiEvent'));
|
|
1290
1576
|
});
|
|
1291
1577
|
// This is kinda hacky solution at the moment to maintain NodeSelections over transactions
|
|
1292
|
-
// These are required by at least cross-references
|
|
1578
|
+
// These are required by at least cross-references and links to activate their selector pop-ups
|
|
1293
1579
|
if (wasNodeSelection) {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
const
|
|
1580
|
+
// And -1 here is necessary to keep the selection pointing at the start of the node
|
|
1581
|
+
// (or something, breaks with cross-references otherwise)
|
|
1582
|
+
const mappedPos = newTr.mapping.map(tr.selection.from, -1);
|
|
1297
1583
|
const sel = getSelectionStaticConstructor(tr.selection);
|
|
1298
|
-
newTr.setSelection(sel.create(newTr.doc,
|
|
1584
|
+
newTr.setSelection(sel.create(newTr.doc, mappedPos));
|
|
1299
1585
|
}
|
|
1300
1586
|
log.info('NEW transaction', newTr);
|
|
1301
1587
|
return newTr;
|
|
1302
1588
|
}
|
|
1303
1589
|
|
|
1304
|
-
|
|
1590
|
+
var TrackChangesStatus;
|
|
1305
1591
|
(function (TrackChangesStatus) {
|
|
1306
1592
|
TrackChangesStatus["enabled"] = "enabled";
|
|
1307
1593
|
TrackChangesStatus["viewSnapshots"] = "view-snapshots";
|
|
1308
1594
|
TrackChangesStatus["disabled"] = "disabled";
|
|
1309
|
-
})(
|
|
1595
|
+
})(TrackChangesStatus || (TrackChangesStatus = {}));
|
|
1310
1596
|
|
|
1311
1597
|
/*!
|
|
1312
1598
|
* © 2021 Atypon Systems LLC
|
|
@@ -1323,12 +1609,7 @@ exports.TrackChangesStatus = void 0;
|
|
|
1323
1609
|
* See the License for the specific language governing permissions and
|
|
1324
1610
|
* limitations under the License.
|
|
1325
1611
|
*/
|
|
1326
|
-
const trackChangesPluginKey = new
|
|
1327
|
-
// TODO remove
|
|
1328
|
-
const infiniteLoopCounter = {
|
|
1329
|
-
start: 0,
|
|
1330
|
-
iters: 0,
|
|
1331
|
-
};
|
|
1612
|
+
const trackChangesPluginKey = new PluginKey('track-changes');
|
|
1332
1613
|
/**
|
|
1333
1614
|
* The ProseMirror plugin needed to enable track-changes.
|
|
1334
1615
|
*
|
|
@@ -1341,25 +1622,25 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1341
1622
|
if (debug) {
|
|
1342
1623
|
enableDebug(true);
|
|
1343
1624
|
}
|
|
1344
|
-
return new
|
|
1625
|
+
return new Plugin({
|
|
1345
1626
|
key: trackChangesPluginKey,
|
|
1346
1627
|
props: {
|
|
1347
1628
|
editable(state) {
|
|
1348
1629
|
var _a;
|
|
1349
|
-
return ((_a = trackChangesPluginKey.getState(state)) === null || _a === void 0 ? void 0 : _a.status) !==
|
|
1630
|
+
return ((_a = trackChangesPluginKey.getState(state)) === null || _a === void 0 ? void 0 : _a.status) !== TrackChangesStatus.viewSnapshots;
|
|
1350
1631
|
},
|
|
1351
1632
|
},
|
|
1352
1633
|
state: {
|
|
1353
1634
|
init(_config, state) {
|
|
1354
1635
|
return {
|
|
1355
|
-
status:
|
|
1636
|
+
status: TrackChangesStatus.enabled,
|
|
1356
1637
|
userID,
|
|
1357
1638
|
changeSet: findChanges(state),
|
|
1358
1639
|
};
|
|
1359
1640
|
},
|
|
1360
1641
|
apply(tr, pluginState, _oldState, newState) {
|
|
1361
|
-
const setUserID = getAction(tr,
|
|
1362
|
-
const setStatus = getAction(tr,
|
|
1642
|
+
const setUserID = getAction(tr, TrackChangesAction.setUserID);
|
|
1643
|
+
const setStatus = getAction(tr, TrackChangesAction.setPluginStatus);
|
|
1363
1644
|
if (setUserID) {
|
|
1364
1645
|
return { ...pluginState, userID: setUserID };
|
|
1365
1646
|
}
|
|
@@ -1370,12 +1651,12 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1370
1651
|
changeSet: findChanges(newState),
|
|
1371
1652
|
};
|
|
1372
1653
|
}
|
|
1373
|
-
else if (pluginState.status ===
|
|
1654
|
+
else if (pluginState.status === TrackChangesStatus.disabled) {
|
|
1374
1655
|
return { ...pluginState, changeSet: new ChangeSet() };
|
|
1375
1656
|
}
|
|
1376
1657
|
let { changeSet, ...rest } = pluginState;
|
|
1377
|
-
const updatedChangeIds = getAction(tr,
|
|
1378
|
-
if (updatedChangeIds || getAction(tr,
|
|
1658
|
+
const updatedChangeIds = getAction(tr, TrackChangesAction.updateChanges);
|
|
1659
|
+
if (updatedChangeIds || getAction(tr, TrackChangesAction.refreshChanges)) {
|
|
1379
1660
|
changeSet = findChanges(newState);
|
|
1380
1661
|
}
|
|
1381
1662
|
return {
|
|
@@ -1394,46 +1675,37 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1394
1675
|
appendTransaction(trs, oldState, newState) {
|
|
1395
1676
|
const pluginState = trackChangesPluginKey.getState(newState);
|
|
1396
1677
|
if (!pluginState ||
|
|
1397
|
-
pluginState.status ===
|
|
1678
|
+
pluginState.status === TrackChangesStatus.disabled ||
|
|
1398
1679
|
!(editorView === null || editorView === void 0 ? void 0 : editorView.editable)) {
|
|
1399
1680
|
return null;
|
|
1400
1681
|
}
|
|
1401
|
-
if (infiniteLoopCounter.start < Date.now() - 10000) {
|
|
1402
|
-
infiniteLoopCounter.start = Date.now();
|
|
1403
|
-
infiniteLoopCounter.iters = 0;
|
|
1404
|
-
}
|
|
1405
|
-
if (infiniteLoopCounter.iters >= 100) {
|
|
1406
|
-
console.error('Detected probable infinite loop in track changes!');
|
|
1407
|
-
return null;
|
|
1408
|
-
}
|
|
1409
1682
|
const { userID, changeSet } = pluginState;
|
|
1410
1683
|
let createdTr = newState.tr, docChanged = false;
|
|
1411
1684
|
log.info('TRS', trs);
|
|
1412
1685
|
trs.forEach((tr) => {
|
|
1413
1686
|
const wasAppended = tr.getMeta('appendedTransaction');
|
|
1414
1687
|
const skipMetaUsed = skipTrsWithMetas.some((m) => tr.getMeta(m) || (wasAppended === null || wasAppended === void 0 ? void 0 : wasAppended.getMeta(m)));
|
|
1415
|
-
const skipTrackUsed = getAction(tr,
|
|
1416
|
-
(wasAppended && getAction(wasAppended,
|
|
1688
|
+
const skipTrackUsed = getAction(tr, TrackChangesAction.skipTrack) ||
|
|
1689
|
+
(wasAppended && getAction(wasAppended, TrackChangesAction.skipTrack));
|
|
1417
1690
|
if (tr.docChanged && !skipMetaUsed && !skipTrackUsed && !tr.getMeta('history$')) {
|
|
1418
1691
|
createdTr = trackTransaction(tr, oldState, createdTr, userID);
|
|
1419
|
-
infiniteLoopCounter.iters += 1;
|
|
1420
1692
|
}
|
|
1421
1693
|
docChanged = docChanged || tr.docChanged;
|
|
1422
|
-
const setChangeStatuses = getAction(tr,
|
|
1694
|
+
const setChangeStatuses = getAction(tr, TrackChangesAction.setChangeStatuses);
|
|
1423
1695
|
if (setChangeStatuses) {
|
|
1424
1696
|
const { status, ids } = setChangeStatuses;
|
|
1425
1697
|
ids.forEach((changeId) => {
|
|
1426
1698
|
const change = changeSet === null || changeSet === void 0 ? void 0 : changeSet.get(changeId);
|
|
1427
1699
|
if (change) {
|
|
1428
|
-
createdTr = updateChangeAttrs(createdTr, change, { status }, oldState.schema);
|
|
1429
|
-
setAction(createdTr,
|
|
1700
|
+
createdTr = updateChangeAttrs(createdTr, change, { status, reviewedByID: userID }, oldState.schema);
|
|
1701
|
+
setAction(createdTr, TrackChangesAction.updateChanges, [change.id]);
|
|
1430
1702
|
}
|
|
1431
1703
|
});
|
|
1432
1704
|
}
|
|
1433
|
-
else if (getAction(tr,
|
|
1705
|
+
else if (getAction(tr, TrackChangesAction.applyAndRemoveChanges)) {
|
|
1434
1706
|
const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.nodeChanges);
|
|
1435
1707
|
applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.textChanges, mapping);
|
|
1436
|
-
setAction(createdTr,
|
|
1708
|
+
setAction(createdTr, TrackChangesAction.refreshChanges, true);
|
|
1437
1709
|
}
|
|
1438
1710
|
});
|
|
1439
1711
|
const changed = pluginState.changeSet.hasInconsistentData &&
|
|
@@ -1443,28 +1715,13 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1443
1715
|
}
|
|
1444
1716
|
if (docChanged || createdTr.docChanged || changed) {
|
|
1445
1717
|
createdTr.setMeta('origin', trackChangesPluginKey);
|
|
1446
|
-
return setAction(createdTr,
|
|
1718
|
+
return setAction(createdTr, TrackChangesAction.refreshChanges, true);
|
|
1447
1719
|
}
|
|
1448
1720
|
return null;
|
|
1449
1721
|
},
|
|
1450
1722
|
});
|
|
1451
1723
|
};
|
|
1452
1724
|
|
|
1453
|
-
/*!
|
|
1454
|
-
* © 2021 Atypon Systems LLC
|
|
1455
|
-
*
|
|
1456
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1457
|
-
* you may not use this file except in compliance with the License.
|
|
1458
|
-
* You may obtain a copy of the License at
|
|
1459
|
-
*
|
|
1460
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1461
|
-
*
|
|
1462
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1463
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1464
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1465
|
-
* See the License for the specific language governing permissions and
|
|
1466
|
-
* limitations under the License.
|
|
1467
|
-
*/
|
|
1468
1725
|
/**
|
|
1469
1726
|
* Sets track-changes plugin's status to any of: 'enabled' 'disabled' 'viewSnapshots'. Passing undefined will
|
|
1470
1727
|
* set 'enabled' status to 'disabled' and 'disabled' | 'viewSnapshots' status to 'enabled'.
|
|
@@ -1481,11 +1738,11 @@ const setTrackingStatus = (status) => (state, dispatch) => {
|
|
|
1481
1738
|
let newStatus = status;
|
|
1482
1739
|
if (newStatus === undefined) {
|
|
1483
1740
|
newStatus =
|
|
1484
|
-
currentStatus ===
|
|
1485
|
-
?
|
|
1486
|
-
:
|
|
1741
|
+
currentStatus === TrackChangesStatus.enabled
|
|
1742
|
+
? TrackChangesStatus.disabled
|
|
1743
|
+
: TrackChangesStatus.enabled;
|
|
1487
1744
|
}
|
|
1488
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1745
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.setPluginStatus, newStatus));
|
|
1489
1746
|
return true;
|
|
1490
1747
|
}
|
|
1491
1748
|
return false;
|
|
@@ -1497,7 +1754,7 @@ const setTrackingStatus = (status) => (state, dispatch) => {
|
|
|
1497
1754
|
*/
|
|
1498
1755
|
const setChangeStatuses = (status, ids) => (state, dispatch) => {
|
|
1499
1756
|
dispatch &&
|
|
1500
|
-
dispatch(setAction(state.tr,
|
|
1757
|
+
dispatch(setAction(state.tr, TrackChangesAction.setChangeStatuses, {
|
|
1501
1758
|
status,
|
|
1502
1759
|
ids,
|
|
1503
1760
|
}));
|
|
@@ -1508,21 +1765,21 @@ const setChangeStatuses = (status, ids) => (state, dispatch) => {
|
|
|
1508
1765
|
* @param userID
|
|
1509
1766
|
*/
|
|
1510
1767
|
const setUserID = (userID) => (state, dispatch) => {
|
|
1511
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1768
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.setUserID, userID));
|
|
1512
1769
|
return true;
|
|
1513
1770
|
};
|
|
1514
1771
|
/**
|
|
1515
1772
|
* Appends a transaction that applies all 'accepted' and 'rejected' changes to the document.
|
|
1516
1773
|
*/
|
|
1517
1774
|
const applyAndRemoveChanges = () => (state, dispatch) => {
|
|
1518
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1775
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.applyAndRemoveChanges, true));
|
|
1519
1776
|
return true;
|
|
1520
1777
|
};
|
|
1521
1778
|
/**
|
|
1522
1779
|
* Runs `findChanges` to iterate over the document to collect changes into a new ChangeSet.
|
|
1523
1780
|
*/
|
|
1524
1781
|
const refreshChanges = () => (state, dispatch) => {
|
|
1525
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1782
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.updateChanges, []));
|
|
1526
1783
|
return true;
|
|
1527
1784
|
};
|
|
1528
1785
|
/**
|
|
@@ -1552,10 +1809,4 @@ var commands = /*#__PURE__*/Object.freeze({
|
|
|
1552
1809
|
setParagraphTestAttribute: setParagraphTestAttribute
|
|
1553
1810
|
});
|
|
1554
1811
|
|
|
1555
|
-
|
|
1556
|
-
exports.enableDebug = enableDebug;
|
|
1557
|
-
exports.getAction = getAction;
|
|
1558
|
-
exports.setAction = setAction;
|
|
1559
|
-
exports.trackChangesPlugin = trackChangesPlugin;
|
|
1560
|
-
exports.trackChangesPluginKey = trackChangesPluginKey;
|
|
1561
|
-
exports.trackCommands = commands;
|
|
1812
|
+
export { CHANGE_OPERATION, CHANGE_STATUS, ChangeSet, TrackChangesStatus, enableDebug, skipTracking, trackChangesPlugin, trackChangesPluginKey, commands as trackCommands };
|