@manuscripts/track-changes-plugin 0.0.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/actions.d.ts +1 -1
- package/dist/index.cjs +1562 -0
- package/dist/index.es.js +23 -17
- package/dist/index.js +124 -131
- package/dist/plugin.d.ts +2 -2
- package/dist/track/deleteNode.d.ts +1 -1
- package/dist/track/mergeNode.d.ts +1 -1
- package/dist/track/node-utils.d.ts +1 -1
- package/dist/track/steps/deleteAndMergeSplitNodes.d.ts +1 -1
- package/dist/track/steps/setFragmentAsInserted.d.ts +1 -1
- package/dist/track/trackTransaction.d.ts +1 -1
- package/package.json +17 -21
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
|
|
@@ -83,7 +75,7 @@ function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
|
83
75
|
* See the License for the specific language governing permissions and
|
|
84
76
|
* limitations under the License.
|
|
85
77
|
*/
|
|
86
|
-
|
|
78
|
+
var CHANGE_OPERATION;
|
|
87
79
|
(function (CHANGE_OPERATION) {
|
|
88
80
|
CHANGE_OPERATION["insert"] = "insert";
|
|
89
81
|
CHANGE_OPERATION["delete"] = "delete";
|
|
@@ -92,13 +84,13 @@ exports.CHANGE_OPERATION = void 0;
|
|
|
92
84
|
CHANGE_OPERATION["unwrap_from_node"] = "unwrap_from_node";
|
|
93
85
|
CHANGE_OPERATION["add_mark"] = "add_mark";
|
|
94
86
|
CHANGE_OPERATION["remove_mark"] = "remove_mark";
|
|
95
|
-
})(
|
|
96
|
-
|
|
87
|
+
})(CHANGE_OPERATION || (CHANGE_OPERATION = {}));
|
|
88
|
+
var CHANGE_STATUS;
|
|
97
89
|
(function (CHANGE_STATUS) {
|
|
98
90
|
CHANGE_STATUS["accepted"] = "accepted";
|
|
99
91
|
CHANGE_STATUS["rejected"] = "rejected";
|
|
100
92
|
CHANGE_STATUS["pending"] = "pending";
|
|
101
|
-
})(
|
|
93
|
+
})(CHANGE_STATUS || (CHANGE_STATUS = {}));
|
|
102
94
|
|
|
103
95
|
/*!
|
|
104
96
|
* © 2021 Atypon Systems LLC
|
|
@@ -115,7 +107,7 @@ exports.CHANGE_STATUS = void 0;
|
|
|
115
107
|
* See the License for the specific language governing permissions and
|
|
116
108
|
* limitations under the License.
|
|
117
109
|
*/
|
|
118
|
-
const logger =
|
|
110
|
+
const logger = debug('track');
|
|
119
111
|
const log = {
|
|
120
112
|
info(str, obj) {
|
|
121
113
|
if (obj) {
|
|
@@ -148,10 +140,10 @@ const log = {
|
|
|
148
140
|
*/
|
|
149
141
|
const enableDebug = (enabled) => {
|
|
150
142
|
if (enabled) {
|
|
151
|
-
|
|
143
|
+
debug.enable('track');
|
|
152
144
|
}
|
|
153
145
|
else {
|
|
154
|
-
|
|
146
|
+
debug.disable();
|
|
155
147
|
}
|
|
156
148
|
};
|
|
157
149
|
|
|
@@ -211,13 +203,13 @@ class ChangeSet {
|
|
|
211
203
|
return rootNodes;
|
|
212
204
|
}
|
|
213
205
|
get pending() {
|
|
214
|
-
return this.changeTree.filter((c) => c.attrs.status ===
|
|
206
|
+
return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.pending);
|
|
215
207
|
}
|
|
216
208
|
get accepted() {
|
|
217
|
-
return this.changeTree.filter((c) => c.attrs.status ===
|
|
209
|
+
return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.accepted);
|
|
218
210
|
}
|
|
219
211
|
get rejected() {
|
|
220
|
-
return this.changeTree.filter((c) => c.attrs.status ===
|
|
212
|
+
return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.rejected);
|
|
221
213
|
}
|
|
222
214
|
get textChanges() {
|
|
223
215
|
return this.changes.filter((c) => c.type === 'text-change');
|
|
@@ -271,8 +263,8 @@ class ChangeSet {
|
|
|
271
263
|
*/
|
|
272
264
|
static shouldNotDelete(change) {
|
|
273
265
|
const { status, operation } = change.attrs;
|
|
274
|
-
return ((operation ===
|
|
275
|
-
(operation ===
|
|
266
|
+
return ((operation === CHANGE_OPERATION.insert && status === CHANGE_STATUS.accepted) ||
|
|
267
|
+
(operation === CHANGE_OPERATION.delete && status === CHANGE_STATUS.rejected));
|
|
276
268
|
}
|
|
277
269
|
/**
|
|
278
270
|
* Determines whether a change should be deleted when applying it to the document.
|
|
@@ -280,8 +272,8 @@ class ChangeSet {
|
|
|
280
272
|
*/
|
|
281
273
|
static shouldDeleteChange(change) {
|
|
282
274
|
const { status, operation } = change.attrs;
|
|
283
|
-
return ((operation ===
|
|
284
|
-
(operation ===
|
|
275
|
+
return ((operation === CHANGE_OPERATION.insert && status === CHANGE_STATUS.rejected) ||
|
|
276
|
+
(operation === CHANGE_OPERATION.delete && status === CHANGE_STATUS.accepted));
|
|
285
277
|
}
|
|
286
278
|
/**
|
|
287
279
|
* Checks whether change attributes contain all TrackedAttrs keys with non-undefined values
|
|
@@ -335,14 +327,17 @@ function deleteNode(node, pos, tr) {
|
|
|
335
327
|
var _a;
|
|
336
328
|
const startPos = tr.doc.resolve(pos + 1);
|
|
337
329
|
const range = startPos.blockRange(tr.doc.resolve(startPos.pos - 2 + node.nodeSize));
|
|
338
|
-
const targetDepth = range
|
|
339
|
-
|
|
330
|
+
const targetDepth = range && liftTarget(range);
|
|
331
|
+
// Check with typeof since with old prosemirror-transform targetDepth is undefined
|
|
332
|
+
if (range && typeof targetDepth === 'number') {
|
|
340
333
|
return tr.lift(range, targetDepth);
|
|
341
334
|
}
|
|
342
335
|
const resPos = tr.doc.resolve(pos);
|
|
343
|
-
|
|
336
|
+
// Block nodes can be deleted by just removing their start token which should then merge the text
|
|
337
|
+
// content to above node's content (if there is one)
|
|
338
|
+
const canMergeToNodeAbove = (resPos.parent !== tr.doc || resPos.nodeBefore) && node.isBlock && ((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.isText);
|
|
344
339
|
if (canMergeToNodeAbove) {
|
|
345
|
-
return tr.replaceWith(pos - 1, pos + 1,
|
|
340
|
+
return tr.replaceWith(pos - 1, pos + 1, Fragment.empty);
|
|
346
341
|
}
|
|
347
342
|
else {
|
|
348
343
|
// NOTE: there's an edge case where moving content is not possible but because the immediate
|
|
@@ -375,17 +370,18 @@ function deleteNode(node, pos, tr) {
|
|
|
375
370
|
*/
|
|
376
371
|
function mergeNode(node, pos, tr) {
|
|
377
372
|
var _a;
|
|
378
|
-
if (
|
|
373
|
+
if (canJoin(tr.doc, pos)) {
|
|
379
374
|
return tr.join(pos);
|
|
380
375
|
}
|
|
381
|
-
else if (
|
|
376
|
+
else if (canJoin(tr.doc, pos + node.nodeSize)) {
|
|
377
|
+
// TODO should copy the attributes from the merged node below
|
|
382
378
|
return tr.join(pos + node.nodeSize);
|
|
383
379
|
}
|
|
384
|
-
// TODO is this the same thing as join?
|
|
380
|
+
// TODO is this the same thing as join to above?
|
|
385
381
|
const resPos = tr.doc.resolve(pos);
|
|
386
382
|
const canMergeToNodeAbove = (resPos.parent !== tr.doc || resPos.nodeBefore) && ((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.isText);
|
|
387
383
|
if (canMergeToNodeAbove) {
|
|
388
|
-
return tr.replaceWith(pos - 1, pos + 1,
|
|
384
|
+
return tr.replaceWith(pos - 1, pos + 1, Fragment.empty);
|
|
389
385
|
}
|
|
390
386
|
return undefined;
|
|
391
387
|
}
|
|
@@ -429,8 +425,8 @@ function getInlineNodeTrackedMarkData(node, schema) {
|
|
|
429
425
|
node.marks.forEach((mark) => {
|
|
430
426
|
if (mark.type === schema.marks.tracked_insert || mark.type === schema.marks.tracked_delete) {
|
|
431
427
|
const operation = mark.type === schema.marks.tracked_insert
|
|
432
|
-
?
|
|
433
|
-
:
|
|
428
|
+
? CHANGE_OPERATION.insert
|
|
429
|
+
: CHANGE_OPERATION.delete;
|
|
434
430
|
marksTrackedData.push({ ...mark.attrs.dataTracked, operation });
|
|
435
431
|
}
|
|
436
432
|
});
|
|
@@ -504,16 +500,16 @@ function updateChangeChildrenAttributes(changes, tr, mapping) {
|
|
|
504
500
|
* @param changes
|
|
505
501
|
* @param deleteMap
|
|
506
502
|
*/
|
|
507
|
-
function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new
|
|
503
|
+
function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mapping()) {
|
|
508
504
|
changes.forEach((change) => {
|
|
509
|
-
if (change.attrs.status ===
|
|
505
|
+
if (change.attrs.status === CHANGE_STATUS.pending) {
|
|
510
506
|
return;
|
|
511
507
|
}
|
|
512
508
|
// Map change.from and skip those which dont need to be applied
|
|
513
509
|
// or were already deleted by an applied block delete
|
|
514
510
|
const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || ChangeSet.shouldNotDelete(change);
|
|
515
511
|
if (!node) {
|
|
516
|
-
log.warn('no node found to update for change', change);
|
|
512
|
+
!deleted && log.warn('no node found to update for change', change);
|
|
517
513
|
return;
|
|
518
514
|
}
|
|
519
515
|
if (ChangeSet.isTextChange(change) && noChangeNeeded) {
|
|
@@ -628,8 +624,8 @@ function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
|
|
|
628
624
|
const newAttrs = {
|
|
629
625
|
...((!id || iteratedIds.has(id) || id.length === 0) && { id: uuidv4() }),
|
|
630
626
|
...(!userID && { userID: trackUserID }),
|
|
631
|
-
...(!operation && { operation:
|
|
632
|
-
...(!status && { status:
|
|
627
|
+
...(!operation && { operation: CHANGE_OPERATION.insert }),
|
|
628
|
+
...(!status && { status: CHANGE_STATUS.pending }),
|
|
633
629
|
...(!createdAt && { createdAt: Date.now() }),
|
|
634
630
|
};
|
|
635
631
|
if (Object.keys(newAttrs).length > 0) {
|
|
@@ -658,7 +654,7 @@ function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
|
|
|
658
654
|
*/
|
|
659
655
|
function markInlineNodeChange(node, newTrackAttrs, schema) {
|
|
660
656
|
const filtered = node.marks.filter((m) => m.type !== schema.marks.tracked_insert && m.type !== schema.marks.tracked_delete);
|
|
661
|
-
const mark = newTrackAttrs.operation ===
|
|
657
|
+
const mark = newTrackAttrs.operation === CHANGE_OPERATION.insert
|
|
662
658
|
? schema.marks.tracked_insert
|
|
663
659
|
: schema.marks.tracked_delete;
|
|
664
660
|
const createdMark = mark.create({
|
|
@@ -678,7 +674,7 @@ function recurseNodeContent(node, newTrackAttrs, schema) {
|
|
|
678
674
|
return node.type.create({
|
|
679
675
|
...node.attrs,
|
|
680
676
|
dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
|
|
681
|
-
},
|
|
677
|
+
}, Fragment.fromArray(updatedChildren), node.marks);
|
|
682
678
|
}
|
|
683
679
|
else {
|
|
684
680
|
log.error(`unhandled node type: "${node.type.name}"`, node);
|
|
@@ -691,7 +687,7 @@ function setFragmentAsInserted(inserted, insertAttrs, schema) {
|
|
|
691
687
|
inserted.forEach((n) => {
|
|
692
688
|
updatedInserted.push(recurseNodeContent(n, insertAttrs, schema));
|
|
693
689
|
});
|
|
694
|
-
return updatedInserted.length === 0 ? inserted :
|
|
690
|
+
return updatedInserted.length === 0 ? inserted : Fragment.fromArray(updatedInserted);
|
|
695
691
|
}
|
|
696
692
|
|
|
697
693
|
/*!
|
|
@@ -712,13 +708,13 @@ function setFragmentAsInserted(inserted, insertAttrs, schema) {
|
|
|
712
708
|
function createNewInsertAttrs(attrs) {
|
|
713
709
|
return {
|
|
714
710
|
...attrs,
|
|
715
|
-
operation:
|
|
711
|
+
operation: CHANGE_OPERATION.insert,
|
|
716
712
|
};
|
|
717
713
|
}
|
|
718
714
|
function createNewDeleteAttrs(attrs) {
|
|
719
715
|
return {
|
|
720
716
|
...attrs,
|
|
721
|
-
operation:
|
|
717
|
+
operation: CHANGE_OPERATION.delete,
|
|
722
718
|
};
|
|
723
719
|
}
|
|
724
720
|
|
|
@@ -761,7 +757,7 @@ function getMergedNode(node, currentDepth, depth, first) {
|
|
|
761
757
|
};
|
|
762
758
|
}
|
|
763
759
|
const result = [];
|
|
764
|
-
let merged =
|
|
760
|
+
let merged = Fragment.empty;
|
|
765
761
|
node.content.forEach((n, _, i) => {
|
|
766
762
|
if ((first && i === 0) || (!first && i === node.childCount - 1)) {
|
|
767
763
|
const { mergedNodeContent, unmergedContent } = getMergedNode(n, currentDepth + 1, depth, first);
|
|
@@ -776,7 +772,7 @@ function getMergedNode(node, currentDepth, depth, first) {
|
|
|
776
772
|
});
|
|
777
773
|
return {
|
|
778
774
|
mergedNodeContent: merged,
|
|
779
|
-
unmergedContent: result.length > 0 ?
|
|
775
|
+
unmergedContent: result.length > 0 ? Fragment.fromArray(result) : undefined,
|
|
780
776
|
};
|
|
781
777
|
}
|
|
782
778
|
/**
|
|
@@ -839,7 +835,7 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
|
|
|
839
835
|
// Math.max(pos, from) is for picking always the start of the node,
|
|
840
836
|
// not the start of the change (which might span multiple nodes).
|
|
841
837
|
// Pos can be less than from as nodesBetween iterates through all nodes starting from the top block node
|
|
842
|
-
newTr.replaceWith(start, end,
|
|
838
|
+
newTr.replaceWith(start, end, Fragment.empty);
|
|
843
839
|
return start;
|
|
844
840
|
}
|
|
845
841
|
else {
|
|
@@ -869,7 +865,7 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
|
|
|
869
865
|
*/
|
|
870
866
|
function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
|
|
871
867
|
const dataTracked = node.attrs.dataTracked;
|
|
872
|
-
const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) ===
|
|
868
|
+
const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === CHANGE_OPERATION.insert && dataTracked.userID === deleteAttrs.userID;
|
|
873
869
|
if (wasInsertedBySameUser) {
|
|
874
870
|
deleteNode(node, pos, newTr);
|
|
875
871
|
}
|
|
@@ -907,7 +903,7 @@ function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
|
|
|
907
903
|
* @returns mapping adjusted by the applied operations & modified insert slice
|
|
908
904
|
*/
|
|
909
905
|
function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackAttrs, insertSlice) {
|
|
910
|
-
const deleteMap = new
|
|
906
|
+
const deleteMap = new Mapping();
|
|
911
907
|
const mergedInsertPos = undefined;
|
|
912
908
|
// No deletion applied, return default values
|
|
913
909
|
if (from === to) {
|
|
@@ -925,10 +921,22 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
|
|
|
925
921
|
const { pos: offsetPos, deleted: nodeWasDeleted } = deleteMap.mapResult(pos, 1);
|
|
926
922
|
const offsetFrom = deleteMap.map(from, -1);
|
|
927
923
|
const offsetTo = deleteMap.map(to, 1);
|
|
928
|
-
const wasWithinGap = gap && offsetPos >= deleteMap.map(gap.start, -1);
|
|
929
924
|
const nodeEnd = offsetPos + node.nodeSize;
|
|
925
|
+
// So this insane boolean checks for ReplaceAroundStep gaps and whether the node should be skipped
|
|
926
|
+
// since the content inside gap should stay unchanged.
|
|
927
|
+
// All other nodes except text nodes consist of one start and end token (or just a single token for atoms).
|
|
928
|
+
// For them we can just check whether the start token is within the gap eg pos is 10 when gap (8, 18) to
|
|
929
|
+
// determine whether it should be skipped.
|
|
930
|
+
// For text nodes though, since they are continous, they might only partially be enclosed in the gap
|
|
931
|
+
// eg. pos 10 when gap is (8, 18) BUT if their nodeEnd goes past the gap's end eg nodeEnd 20 they actually
|
|
932
|
+
// are altered and should not be skipped.
|
|
933
|
+
// @TODO ATM 20.7.2022 there doesn't seem to be tests that capture this.
|
|
934
|
+
const wasWithinGap = gap &&
|
|
935
|
+
((!node.isText && offsetPos >= deleteMap.map(gap.start, -1)) ||
|
|
936
|
+
(node.isText &&
|
|
937
|
+
offsetPos <= deleteMap.map(gap.start, -1) &&
|
|
938
|
+
nodeEnd >= deleteMap.map(gap.end, -1)));
|
|
930
939
|
let step = newTr.steps[newTr.steps.length - 1];
|
|
931
|
-
// debugger
|
|
932
940
|
// nodeEnd > offsetFrom -> delete touches this node
|
|
933
941
|
// eg (del 6 10) <p 5>|<t 6>cdf</t 9></p 10>| -> <p> nodeEnd 10 > from 6
|
|
934
942
|
//
|
|
@@ -937,6 +945,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
|
|
|
937
945
|
// But from what I remember what it safeguards against is, when you've already deleted a node
|
|
938
946
|
// say an inserted blockquote that had all its children deleted, nodesBetween still iterates over those
|
|
939
947
|
// nodes and therefore we have to make this check to ensure they still exist in the doc.
|
|
948
|
+
//
|
|
940
949
|
if (nodeEnd > offsetFrom && !nodeWasDeleted && !wasWithinGap) {
|
|
941
950
|
// |<p>asdf</p>| -> node deleted completely
|
|
942
951
|
const nodeCompletelyDeleted = offsetPos >= offsetFrom && nodeEnd <= offsetTo;
|
|
@@ -1029,7 +1038,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
|
|
|
1029
1038
|
deleteMap,
|
|
1030
1039
|
mergedInsertPos,
|
|
1031
1040
|
newSliceContent: updatedSliceNodes
|
|
1032
|
-
?
|
|
1041
|
+
? Fragment.fromArray(updatedSliceNodes)
|
|
1033
1042
|
: insertSlice.content,
|
|
1034
1043
|
};
|
|
1035
1044
|
}
|
|
@@ -1128,13 +1137,13 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
|
|
|
1128
1137
|
// the sides should be equal. TODO can they be other than 0?
|
|
1129
1138
|
const openStart = slice.openStart !== slice.openEnd || newSliceContent.size === 0 ? 0 : slice.openStart;
|
|
1130
1139
|
const openEnd = slice.openStart !== slice.openEnd || newSliceContent.size === 0 ? 0 : slice.openEnd;
|
|
1131
|
-
let insertedSlice = new
|
|
1140
|
+
let insertedSlice = new Slice(setFragmentAsInserted(newSliceContent, createNewInsertAttrs(attrs), oldState.schema), openStart, openEnd);
|
|
1132
1141
|
if (gap.size > 0) {
|
|
1133
1142
|
log.info('insertedSlice before inserted gap', insertedSlice);
|
|
1134
1143
|
insertedSlice = insertedSlice.insertAt(insertedSlice.size === 0 ? 0 : insert, gap.content);
|
|
1135
1144
|
log.info('insertedSlice after inserted gap', insertedSlice);
|
|
1136
1145
|
}
|
|
1137
|
-
const newStep = new
|
|
1146
|
+
const newStep = new ReplaceStep(deleteMap.map(gapFrom), deleteMap.map(gapTo), insertedSlice, false);
|
|
1138
1147
|
const stepResult = newTr.maybeStep(newStep);
|
|
1139
1148
|
if (stepResult.failed) {
|
|
1140
1149
|
log.error(`insert ReplaceStep failed: "${stepResult.failed}"`, newStep);
|
|
@@ -1179,10 +1188,11 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
|
|
|
1179
1188
|
log.error(`invert ReplaceStep failed: "${stepResult.failed}"`, newStep);
|
|
1180
1189
|
return;
|
|
1181
1190
|
}
|
|
1191
|
+
log.info('TR: steps before applying delete', [...newTr.steps]);
|
|
1182
1192
|
// First apply the deleted range and update the insert slice to not include content that was deleted,
|
|
1183
1193
|
// eg partial nodes in an open-ended slice
|
|
1184
1194
|
const { deleteMap, mergedInsertPos, newSliceContent } = deleteAndMergeSplitNodes(fromA, toA, undefined, oldState.doc, newTr, oldState.schema, attrs, slice);
|
|
1185
|
-
log.info('TR:
|
|
1195
|
+
log.info('TR: steps after applying delete', [...newTr.steps]);
|
|
1186
1196
|
const adjustedInsertPos = mergedInsertPos !== null && mergedInsertPos !== void 0 ? mergedInsertPos : deleteMap.map(toA);
|
|
1187
1197
|
if (newSliceContent.size > 0) {
|
|
1188
1198
|
log.info('newSliceContent', newSliceContent);
|
|
@@ -1190,8 +1200,8 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
|
|
|
1190
1200
|
// the sides should be equal. TODO can they be other than 0?
|
|
1191
1201
|
const openStart = slice.openStart !== slice.openEnd ? 0 : slice.openStart;
|
|
1192
1202
|
const openEnd = slice.openStart !== slice.openEnd ? 0 : slice.openEnd;
|
|
1193
|
-
const insertedSlice = new
|
|
1194
|
-
const newStep = new
|
|
1203
|
+
const insertedSlice = new Slice(setFragmentAsInserted(newSliceContent, createNewInsertAttrs(attrs), oldState.schema), openStart, openEnd);
|
|
1204
|
+
const newStep = new ReplaceStep(adjustedInsertPos, adjustedInsertPos, insertedSlice);
|
|
1195
1205
|
const stepResult = newTr.maybeStep(newStep);
|
|
1196
1206
|
if (stepResult.failed) {
|
|
1197
1207
|
log.error(`insert ReplaceStep failed: "${stepResult.failed}"`, newStep);
|
|
@@ -1217,11 +1227,9 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
|
|
|
1217
1227
|
* This skips the direct dependency to prosemirror-state where multiple versions might cause conflicts
|
|
1218
1228
|
* as the created instances might belong to different prosemirror-state import than one used in the editor.
|
|
1219
1229
|
* @param sel
|
|
1220
|
-
* @param doc
|
|
1221
|
-
* @param from
|
|
1222
1230
|
* @returns
|
|
1223
1231
|
*/
|
|
1224
|
-
const
|
|
1232
|
+
const getSelectionStaticConstructor = (sel) => Object.getPrototypeOf(sel).constructor;
|
|
1225
1233
|
/**
|
|
1226
1234
|
* Inverts transactions to wrap their contents/operations with track data instead
|
|
1227
1235
|
*
|
|
@@ -1238,11 +1246,10 @@ const getSelectionStaticCreate = (sel, doc, from) => Object.getPrototypeOf(sel).
|
|
|
1238
1246
|
* @returns newTr that inverts the initial tr and applies track attributes/marks
|
|
1239
1247
|
*/
|
|
1240
1248
|
function trackTransaction(tr, oldState, newTr, userID) {
|
|
1241
|
-
var _a;
|
|
1242
1249
|
const emptyAttrs = {
|
|
1243
1250
|
userID,
|
|
1244
1251
|
createdAt: tr.time,
|
|
1245
|
-
status:
|
|
1252
|
+
status: CHANGE_STATUS.pending,
|
|
1246
1253
|
};
|
|
1247
1254
|
// Must use constructor.name instead of instanceof as aliasing prosemirror-state is a lot more
|
|
1248
1255
|
// difficult than prosemirror-transform
|
|
@@ -1257,18 +1264,23 @@ function trackTransaction(tr, oldState, newTr, userID) {
|
|
|
1257
1264
|
'This is probably an error with the library, please report back to maintainers with a reproduction if possible', newTr);
|
|
1258
1265
|
return;
|
|
1259
1266
|
}
|
|
1260
|
-
else if (!(step instanceof
|
|
1267
|
+
else if (!(step instanceof ReplaceStep) && step.constructor.name === 'ReplaceStep') {
|
|
1261
1268
|
console.error('@manuscripts/track-changes-plugin: Multiple prosemirror-transform packages imported, alias/dedupe them ' +
|
|
1262
1269
|
'or instanceof checks fail as well as creating new steps');
|
|
1263
1270
|
return;
|
|
1264
1271
|
}
|
|
1265
|
-
else if (step instanceof
|
|
1272
|
+
else if (step instanceof ReplaceStep) {
|
|
1266
1273
|
const selectionPos = trackReplaceStep(step, oldState, newTr, emptyAttrs);
|
|
1267
1274
|
if (!wasNodeSelection) {
|
|
1268
|
-
|
|
1275
|
+
const sel = getSelectionStaticConstructor(tr.selection);
|
|
1276
|
+
// Use Selection.near to fix selections that point to a block node instead of inline content
|
|
1277
|
+
// eg when inserting a complete new paragraph. -1 finds the first valid position moving backwards
|
|
1278
|
+
// inside the content
|
|
1279
|
+
const near = sel.near(newTr.doc.resolve(selectionPos), -1);
|
|
1280
|
+
newTr.setSelection(near);
|
|
1269
1281
|
}
|
|
1270
1282
|
}
|
|
1271
|
-
else if (step instanceof
|
|
1283
|
+
else if (step instanceof ReplaceAroundStep) {
|
|
1272
1284
|
trackReplaceAroundStep(step, oldState, newTr, emptyAttrs);
|
|
1273
1285
|
// } else if (step instanceof AddMarkStep) {
|
|
1274
1286
|
// } else if (step instanceof RemoveMarkStep) {
|
|
@@ -1276,32 +1288,32 @@ function trackTransaction(tr, oldState, newTr, userID) {
|
|
|
1276
1288
|
// TODO: here we could check whether adjacent inserts & deletes cancel each other out.
|
|
1277
1289
|
// However, this should not be done by diffing and only matching node or char by char instead since
|
|
1278
1290
|
// it's A easier and B more intuitive to user.
|
|
1279
|
-
|
|
1280
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
//
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
Object.keys(meta).forEach((key) => newTr.setMeta(key, tr.getMeta(key)));
|
|
1291
|
+
// The old meta keys are not copied to the new transaction since this will cause race-conditions
|
|
1292
|
+
// when a single meta-field is expected to having been processed / removed. Generic input meta keys,
|
|
1293
|
+
// inputType and uiEvent, are re-added since some plugins might depend on them and process the transaction
|
|
1294
|
+
// after track-changes plugin.
|
|
1295
|
+
tr.getMeta('inputType') && newTr.setMeta('inputType', tr.getMeta('inputType'));
|
|
1296
|
+
tr.getMeta('uiEvent') && newTr.setMeta('uiEvent', tr.getMeta('uiEvent'));
|
|
1286
1297
|
});
|
|
1287
1298
|
// This is kinda hacky solution at the moment to maintain NodeSelections over transactions
|
|
1288
|
-
// These are required by at least cross-references
|
|
1299
|
+
// These are required by at least cross-references and links to activate their selector pop-ups
|
|
1289
1300
|
if (wasNodeSelection) {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1301
|
+
// And -1 here is necessary to keep the selection pointing at the start of the node
|
|
1302
|
+
// (or something, breaks with cross-references otherwise)
|
|
1303
|
+
const mappedPos = newTr.mapping.map(tr.selection.from, -1);
|
|
1304
|
+
const sel = getSelectionStaticConstructor(tr.selection);
|
|
1305
|
+
newTr.setSelection(sel.create(newTr.doc, mappedPos));
|
|
1294
1306
|
}
|
|
1295
1307
|
log.info('NEW transaction', newTr);
|
|
1296
1308
|
return newTr;
|
|
1297
1309
|
}
|
|
1298
1310
|
|
|
1299
|
-
|
|
1311
|
+
var TrackChangesStatus;
|
|
1300
1312
|
(function (TrackChangesStatus) {
|
|
1301
1313
|
TrackChangesStatus["enabled"] = "enabled";
|
|
1302
1314
|
TrackChangesStatus["viewSnapshots"] = "view-snapshots";
|
|
1303
1315
|
TrackChangesStatus["disabled"] = "disabled";
|
|
1304
|
-
})(
|
|
1316
|
+
})(TrackChangesStatus || (TrackChangesStatus = {}));
|
|
1305
1317
|
|
|
1306
1318
|
/*!
|
|
1307
1319
|
* © 2021 Atypon Systems LLC
|
|
@@ -1318,12 +1330,7 @@ exports.TrackChangesStatus = void 0;
|
|
|
1318
1330
|
* See the License for the specific language governing permissions and
|
|
1319
1331
|
* limitations under the License.
|
|
1320
1332
|
*/
|
|
1321
|
-
const trackChangesPluginKey = new
|
|
1322
|
-
// TODO remove
|
|
1323
|
-
const infiniteLoopCounter = {
|
|
1324
|
-
start: 0,
|
|
1325
|
-
iters: 0,
|
|
1326
|
-
};
|
|
1333
|
+
const trackChangesPluginKey = new PluginKey('track-changes');
|
|
1327
1334
|
/**
|
|
1328
1335
|
* The ProseMirror plugin needed to enable track-changes.
|
|
1329
1336
|
*
|
|
@@ -1336,24 +1343,25 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1336
1343
|
if (debug) {
|
|
1337
1344
|
enableDebug(true);
|
|
1338
1345
|
}
|
|
1339
|
-
return new
|
|
1346
|
+
return new Plugin({
|
|
1340
1347
|
key: trackChangesPluginKey,
|
|
1341
1348
|
props: {
|
|
1342
1349
|
editable(state) {
|
|
1343
|
-
|
|
1350
|
+
var _a;
|
|
1351
|
+
return ((_a = trackChangesPluginKey.getState(state)) === null || _a === void 0 ? void 0 : _a.status) !== TrackChangesStatus.viewSnapshots;
|
|
1344
1352
|
},
|
|
1345
1353
|
},
|
|
1346
1354
|
state: {
|
|
1347
1355
|
init(_config, state) {
|
|
1348
1356
|
return {
|
|
1349
|
-
status:
|
|
1357
|
+
status: TrackChangesStatus.enabled,
|
|
1350
1358
|
userID,
|
|
1351
1359
|
changeSet: findChanges(state),
|
|
1352
1360
|
};
|
|
1353
1361
|
},
|
|
1354
1362
|
apply(tr, pluginState, _oldState, newState) {
|
|
1355
|
-
const setUserID = getAction(tr,
|
|
1356
|
-
const setStatus = getAction(tr,
|
|
1363
|
+
const setUserID = getAction(tr, TrackChangesAction.setUserID);
|
|
1364
|
+
const setStatus = getAction(tr, TrackChangesAction.setPluginStatus);
|
|
1357
1365
|
if (setUserID) {
|
|
1358
1366
|
return { ...pluginState, userID: setUserID };
|
|
1359
1367
|
}
|
|
@@ -1364,12 +1372,12 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1364
1372
|
changeSet: findChanges(newState),
|
|
1365
1373
|
};
|
|
1366
1374
|
}
|
|
1367
|
-
else if (pluginState.status ===
|
|
1375
|
+
else if (pluginState.status === TrackChangesStatus.disabled) {
|
|
1368
1376
|
return { ...pluginState, changeSet: new ChangeSet() };
|
|
1369
1377
|
}
|
|
1370
1378
|
let { changeSet, ...rest } = pluginState;
|
|
1371
|
-
const updatedChangeIds = getAction(tr,
|
|
1372
|
-
if (updatedChangeIds || getAction(tr,
|
|
1379
|
+
const updatedChangeIds = getAction(tr, TrackChangesAction.updateChanges);
|
|
1380
|
+
if (updatedChangeIds || getAction(tr, TrackChangesAction.refreshChanges)) {
|
|
1373
1381
|
changeSet = findChanges(newState);
|
|
1374
1382
|
}
|
|
1375
1383
|
return {
|
|
@@ -1388,47 +1396,37 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1388
1396
|
appendTransaction(trs, oldState, newState) {
|
|
1389
1397
|
const pluginState = trackChangesPluginKey.getState(newState);
|
|
1390
1398
|
if (!pluginState ||
|
|
1391
|
-
pluginState.status ===
|
|
1399
|
+
pluginState.status === TrackChangesStatus.disabled ||
|
|
1392
1400
|
!(editorView === null || editorView === void 0 ? void 0 : editorView.editable)) {
|
|
1393
1401
|
return null;
|
|
1394
1402
|
}
|
|
1395
|
-
if (infiniteLoopCounter.start < Date.now() - 10000) {
|
|
1396
|
-
infiniteLoopCounter.start = Date.now();
|
|
1397
|
-
infiniteLoopCounter.iters = 0;
|
|
1398
|
-
}
|
|
1399
|
-
if (infiniteLoopCounter.iters >= 100) {
|
|
1400
|
-
console.error('Detected probable infinite loop in track changes!');
|
|
1401
|
-
return null;
|
|
1402
|
-
}
|
|
1403
1403
|
const { userID, changeSet } = pluginState;
|
|
1404
1404
|
let createdTr = newState.tr, docChanged = false;
|
|
1405
1405
|
log.info('TRS', trs);
|
|
1406
1406
|
trs.forEach((tr) => {
|
|
1407
1407
|
const wasAppended = tr.getMeta('appendedTransaction');
|
|
1408
1408
|
const skipMetaUsed = skipTrsWithMetas.some((m) => tr.getMeta(m) || (wasAppended === null || wasAppended === void 0 ? void 0 : wasAppended.getMeta(m)));
|
|
1409
|
-
const skipTrackUsed = getAction(tr,
|
|
1410
|
-
(wasAppended && getAction(wasAppended,
|
|
1409
|
+
const skipTrackUsed = getAction(tr, TrackChangesAction.skipTrack) ||
|
|
1410
|
+
(wasAppended && getAction(wasAppended, TrackChangesAction.skipTrack));
|
|
1411
1411
|
if (tr.docChanged && !skipMetaUsed && !skipTrackUsed && !tr.getMeta('history$')) {
|
|
1412
1412
|
createdTr = trackTransaction(tr, oldState, createdTr, userID);
|
|
1413
|
-
createdTr.setMeta('origin', trackChangesPluginKey);
|
|
1414
|
-
infiniteLoopCounter.iters += 1;
|
|
1415
1413
|
}
|
|
1416
1414
|
docChanged = docChanged || tr.docChanged;
|
|
1417
|
-
const setChangeStatuses = getAction(tr,
|
|
1415
|
+
const setChangeStatuses = getAction(tr, TrackChangesAction.setChangeStatuses);
|
|
1418
1416
|
if (setChangeStatuses) {
|
|
1419
1417
|
const { status, ids } = setChangeStatuses;
|
|
1420
1418
|
ids.forEach((changeId) => {
|
|
1421
1419
|
const change = changeSet === null || changeSet === void 0 ? void 0 : changeSet.get(changeId);
|
|
1422
1420
|
if (change) {
|
|
1423
1421
|
createdTr = updateChangeAttrs(createdTr, change, { status }, oldState.schema);
|
|
1424
|
-
setAction(createdTr,
|
|
1422
|
+
setAction(createdTr, TrackChangesAction.updateChanges, [change.id]);
|
|
1425
1423
|
}
|
|
1426
1424
|
});
|
|
1427
1425
|
}
|
|
1428
|
-
else if (getAction(tr,
|
|
1426
|
+
else if (getAction(tr, TrackChangesAction.applyAndRemoveChanges)) {
|
|
1429
1427
|
const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.nodeChanges);
|
|
1430
1428
|
applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.textChanges, mapping);
|
|
1431
|
-
setAction(createdTr,
|
|
1429
|
+
setAction(createdTr, TrackChangesAction.refreshChanges, true);
|
|
1432
1430
|
}
|
|
1433
1431
|
});
|
|
1434
1432
|
const changed = pluginState.changeSet.hasInconsistentData &&
|
|
@@ -1437,7 +1435,8 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1437
1435
|
log.warn('had to fix inconsistent changes in', createdTr);
|
|
1438
1436
|
}
|
|
1439
1437
|
if (docChanged || createdTr.docChanged || changed) {
|
|
1440
|
-
|
|
1438
|
+
createdTr.setMeta('origin', trackChangesPluginKey);
|
|
1439
|
+
return setAction(createdTr, TrackChangesAction.refreshChanges, true);
|
|
1441
1440
|
}
|
|
1442
1441
|
return null;
|
|
1443
1442
|
},
|
|
@@ -1475,11 +1474,11 @@ const setTrackingStatus = (status) => (state, dispatch) => {
|
|
|
1475
1474
|
let newStatus = status;
|
|
1476
1475
|
if (newStatus === undefined) {
|
|
1477
1476
|
newStatus =
|
|
1478
|
-
currentStatus ===
|
|
1479
|
-
?
|
|
1480
|
-
:
|
|
1477
|
+
currentStatus === TrackChangesStatus.enabled
|
|
1478
|
+
? TrackChangesStatus.disabled
|
|
1479
|
+
: TrackChangesStatus.enabled;
|
|
1481
1480
|
}
|
|
1482
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1481
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.setPluginStatus, newStatus));
|
|
1483
1482
|
return true;
|
|
1484
1483
|
}
|
|
1485
1484
|
return false;
|
|
@@ -1491,7 +1490,7 @@ const setTrackingStatus = (status) => (state, dispatch) => {
|
|
|
1491
1490
|
*/
|
|
1492
1491
|
const setChangeStatuses = (status, ids) => (state, dispatch) => {
|
|
1493
1492
|
dispatch &&
|
|
1494
|
-
dispatch(setAction(state.tr,
|
|
1493
|
+
dispatch(setAction(state.tr, TrackChangesAction.setChangeStatuses, {
|
|
1495
1494
|
status,
|
|
1496
1495
|
ids,
|
|
1497
1496
|
}));
|
|
@@ -1502,21 +1501,21 @@ const setChangeStatuses = (status, ids) => (state, dispatch) => {
|
|
|
1502
1501
|
* @param userID
|
|
1503
1502
|
*/
|
|
1504
1503
|
const setUserID = (userID) => (state, dispatch) => {
|
|
1505
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1504
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.setUserID, userID));
|
|
1506
1505
|
return true;
|
|
1507
1506
|
};
|
|
1508
1507
|
/**
|
|
1509
1508
|
* Appends a transaction that applies all 'accepted' and 'rejected' changes to the document.
|
|
1510
1509
|
*/
|
|
1511
1510
|
const applyAndRemoveChanges = () => (state, dispatch) => {
|
|
1512
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1511
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.applyAndRemoveChanges, true));
|
|
1513
1512
|
return true;
|
|
1514
1513
|
};
|
|
1515
1514
|
/**
|
|
1516
1515
|
* Runs `findChanges` to iterate over the document to collect changes into a new ChangeSet.
|
|
1517
1516
|
*/
|
|
1518
1517
|
const refreshChanges = () => (state, dispatch) => {
|
|
1519
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1518
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.updateChanges, []));
|
|
1520
1519
|
return true;
|
|
1521
1520
|
};
|
|
1522
1521
|
/**
|
|
@@ -1546,10 +1545,4 @@ var commands = /*#__PURE__*/Object.freeze({
|
|
|
1546
1545
|
setParagraphTestAttribute: setParagraphTestAttribute
|
|
1547
1546
|
});
|
|
1548
1547
|
|
|
1549
|
-
|
|
1550
|
-
exports.enableDebug = enableDebug;
|
|
1551
|
-
exports.getAction = getAction;
|
|
1552
|
-
exports.setAction = setAction;
|
|
1553
|
-
exports.trackChangesPlugin = trackChangesPlugin;
|
|
1554
|
-
exports.trackChangesPluginKey = trackChangesPluginKey;
|
|
1555
|
-
exports.trackCommands = commands;
|
|
1548
|
+
export { CHANGE_OPERATION, CHANGE_STATUS, ChangeSet, TrackChangesAction, TrackChangesStatus, enableDebug, getAction, setAction, trackChangesPlugin, trackChangesPluginKey, commands as trackCommands };
|