@manuscripts/track-changes-plugin 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -42
- package/dist/actions.d.ts +7 -0
- package/dist/index.cjs +68 -39
- package/dist/index.d.ts +1 -1
- package/dist/index.js +50 -20
- package/dist/track/node-utils.d.ts +4 -11
- package/dist/track/steps/track-utils.d.ts +0 -1
- package/dist/track/trackTransaction.d.ts +2 -2
- package/dist/types/change.d.ts +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
ProseMirror plugin to track inserts/deletes to nodes and text.
|
|
4
4
|
|
|
5
|
-
If you have multiple versions of prosemirror packages, ensure that track-changes' dependencies `prosemirror-model` and `prosemirror-transform` are aliased/deduped to same instance. `prosemirror-state` and `prosemirror-view` are only used at type level. [Example](https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/examples-packages/client/vite.config.js).
|
|
6
|
-
|
|
7
|
-
[More detailed overview](https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/quarterback-packages/track-changes-plugin/OVERVIEW.md)
|
|
8
|
-
|
|
9
5
|
## How to use
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
Requires normal ProseMirror editor dependencies.
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
1. Install the plugin: `npm i @manuscripts/track-changes-plugin`
|
|
10
|
+
2. Add it to ProseMirror plugins:
|
|
14
11
|
|
|
15
12
|
```ts
|
|
16
13
|
import { EditorState } from 'prosemirror-state'
|
|
@@ -34,9 +31,9 @@ const view = new EditorView(document.querySelector('#editor') as HTMLElement, {
|
|
|
34
31
|
})
|
|
35
32
|
```
|
|
36
33
|
|
|
37
|
-
where `schema`
|
|
34
|
+
where `schema` contains `dataTracked` attributes for tracked nodes and `tracked_insert` & `tracked_delete` marks as shown here: https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/quarterback-packages/track-changes-plugin/test/utils/schema.ts
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
3. That should start tracking all transactions. You can use the following commands to enable/disable/enter read-only mode:
|
|
40
37
|
|
|
41
38
|
```ts
|
|
42
39
|
import { trackCommands, TrackChangesStatus } from '@manuscripts/track-changes-plugin'
|
|
@@ -54,7 +51,11 @@ trackCommands.setTrackingStatus(TrackChangesStatus.disabled))(view.state, view.d
|
|
|
54
51
|
trackCommands.setTrackingStatus(TrackChangesStatus.viewSnapshots))(view.state, view.dispatch, view)
|
|
55
52
|
```
|
|
56
53
|
|
|
57
|
-
See an example app at https://github.com/Atypon-OpenSource/manuscripts-quarterback/tree/main/examples-packages/client
|
|
54
|
+
See an example app at https://github.com/Atypon-OpenSource/manuscripts-quarterback/tree/main/examples-packages/client for a more complete boilerplate.
|
|
55
|
+
|
|
56
|
+
**NOTE**: If you have multiple versions of prosemirror packages, ensure that track-changes' dependencies `prosemirror-model` and `prosemirror-transform` are aliased/deduped to same instance. `prosemirror-state` and `prosemirror-view` are only used at type level. [Example](https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/examples-packages/client/vite.config.js).
|
|
57
|
+
|
|
58
|
+
[More detailed overview](https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/quarterback-packages/track-changes-plugin/OVERVIEW.md)
|
|
58
59
|
|
|
59
60
|
## API
|
|
60
61
|
|
|
@@ -130,44 +131,16 @@ export const refreshChanges = () => Command
|
|
|
130
131
|
|
|
131
132
|
### Actions
|
|
132
133
|
|
|
133
|
-
Actions are used to access/set transaction meta fields.
|
|
134
|
+
Actions are used to access/set transaction meta fields internally. `skipTracking` is exposed publicly to set track-changes to skip certain transaction.
|
|
134
135
|
|
|
135
136
|
```ts
|
|
136
|
-
export type TrackChangesActionParams = {
|
|
137
|
-
[TrackChangesAction.skipTrack]: boolean
|
|
138
|
-
[TrackChangesAction.setUserID]: string
|
|
139
|
-
[TrackChangesAction.setPluginStatus]: TrackChangesStatus
|
|
140
|
-
[TrackChangesAction.setChangeStatuses]: {
|
|
141
|
-
status: CHANGE_STATUS
|
|
142
|
-
ids: string[]
|
|
143
|
-
}
|
|
144
|
-
[TrackChangesAction.updateChanges]: string[]
|
|
145
|
-
[TrackChangesAction.refreshChanges]: boolean
|
|
146
|
-
[TrackChangesAction.applyAndRemoveChanges]: boolean
|
|
147
|
-
}
|
|
148
137
|
/**
|
|
149
|
-
*
|
|
138
|
+
* Skip tracking for a transaction, use this with caution to avoid race-conditions or just to otherwise
|
|
139
|
+
* omitting applying of track attributes or marks.
|
|
150
140
|
* @param tr
|
|
151
|
-
* @
|
|
141
|
+
* @returns
|
|
152
142
|
*/
|
|
153
|
-
export
|
|
154
|
-
return tr.getMeta(action) as TrackChangesActionParams[K] | undefined
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Use this function to set meta keys to transactions that are consumed by the track-changes-plugin.
|
|
159
|
-
* For example, you can skip tracking of a transaction with setAction(tr, TrackChangesAction.skipTrack, true)
|
|
160
|
-
* @param tr
|
|
161
|
-
* @param action
|
|
162
|
-
* @param payload
|
|
163
|
-
*/
|
|
164
|
-
export function setAction<K extends keyof TrackChangesActionParams>(
|
|
165
|
-
tr: Transaction,
|
|
166
|
-
action: K,
|
|
167
|
-
payload: TrackChangesActionParams[K]
|
|
168
|
-
) {
|
|
169
|
-
return tr.setMeta(action, payload)
|
|
170
|
-
}
|
|
143
|
+
export const skipTracking = (tr: Transaction) => setAction(tr, TrackChangesAction.skipTrack, true)
|
|
171
144
|
```
|
|
172
145
|
|
|
173
146
|
### Types
|
package/dist/actions.d.ts
CHANGED
|
@@ -51,3 +51,10 @@ export declare function getAction<K extends keyof TrackChangesActionParams>(tr:
|
|
|
51
51
|
* @param payload
|
|
52
52
|
*/
|
|
53
53
|
export declare function setAction<K extends keyof TrackChangesActionParams>(tr: Transaction, action: K, payload: TrackChangesActionParams[K]): Transaction;
|
|
54
|
+
/**
|
|
55
|
+
* Skip tracking for a transaction, use this with caution to avoid race-conditions or just to otherwise
|
|
56
|
+
* omitting applying of track attributes or marks.
|
|
57
|
+
* @param tr
|
|
58
|
+
* @returns
|
|
59
|
+
*/
|
|
60
|
+
export declare const skipTracking: (tr: Transaction) => Transaction;
|
package/dist/index.cjs
CHANGED
|
@@ -11,7 +11,7 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
|
|
11
11
|
|
|
12
12
|
var debug__default = /*#__PURE__*/_interopDefaultLegacy(debug);
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
var TrackChangesAction;
|
|
15
15
|
(function (TrackChangesAction) {
|
|
16
16
|
TrackChangesAction["skipTrack"] = "track-changes-skip-tracking";
|
|
17
17
|
TrackChangesAction["setUserID"] = "track-changes-set-user-id";
|
|
@@ -20,7 +20,7 @@ exports.TrackChangesAction = void 0;
|
|
|
20
20
|
TrackChangesAction["updateChanges"] = "track-changes-update-changes";
|
|
21
21
|
TrackChangesAction["refreshChanges"] = "track-changes-refresh-changes";
|
|
22
22
|
TrackChangesAction["applyAndRemoveChanges"] = "track-changes-apply-remove-changes";
|
|
23
|
-
})(
|
|
23
|
+
})(TrackChangesAction || (TrackChangesAction = {}));
|
|
24
24
|
/**
|
|
25
25
|
* Gets the value of a meta field, action payload, of a defined track-changes action.
|
|
26
26
|
* @param tr
|
|
@@ -38,7 +38,14 @@ function getAction(tr, action) {
|
|
|
38
38
|
*/
|
|
39
39
|
function setAction(tr, action, payload) {
|
|
40
40
|
return tr.setMeta(action, payload);
|
|
41
|
-
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Skip tracking for a transaction, use this with caution to avoid race-conditions or just to otherwise
|
|
44
|
+
* omitting applying of track attributes or marks.
|
|
45
|
+
* @param tr
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
const skipTracking = (tr) => setAction(tr, TrackChangesAction.skipTrack, true);
|
|
42
49
|
|
|
43
50
|
/******************************************************************************
|
|
44
51
|
Copyright (c) Microsoft Corporation.
|
|
@@ -291,10 +298,22 @@ class ChangeSet {
|
|
|
291
298
|
if ('attrs' in attrs) {
|
|
292
299
|
log.warn('passed "attrs" as property to isValidTrackedAttrs(attrs)', attrs);
|
|
293
300
|
}
|
|
294
|
-
const trackedKeys = [
|
|
295
|
-
|
|
301
|
+
const trackedKeys = [
|
|
302
|
+
'id',
|
|
303
|
+
'authorID',
|
|
304
|
+
'operation',
|
|
305
|
+
'status',
|
|
306
|
+
'createdAt',
|
|
307
|
+
'updatedAt',
|
|
308
|
+
];
|
|
309
|
+
// reviewedByID is set optional since either ProseMirror or Yjs doesn't like persisting null values inside attributes objects
|
|
310
|
+
// So it can be either omitted completely or at least null or string
|
|
311
|
+
const optionalKeys = ['reviewedByID'];
|
|
312
|
+
const entries = Object.entries(attrs).filter(([key, val]) => trackedKeys.includes(key));
|
|
313
|
+
const optionalEntries = Object.entries(attrs).filter(([key, val]) => optionalKeys.includes(key));
|
|
296
314
|
return (entries.length === trackedKeys.length &&
|
|
297
315
|
entries.every(([key, val]) => trackedKeys.includes(key) && val !== undefined) &&
|
|
316
|
+
optionalEntries.every(([key, val]) => optionalKeys.includes(key) && val !== undefined) &&
|
|
298
317
|
(attrs.id || '').length > 0 // Changes created with undefined id have '' as placeholder
|
|
299
318
|
);
|
|
300
319
|
}
|
|
@@ -336,7 +355,7 @@ function deleteNode(node, pos, tr) {
|
|
|
336
355
|
const startPos = tr.doc.resolve(pos + 1);
|
|
337
356
|
const range = startPos.blockRange(tr.doc.resolve(startPos.pos - 2 + node.nodeSize));
|
|
338
357
|
const targetDepth = range && prosemirrorTransform.liftTarget(range);
|
|
339
|
-
// Check with typeof since with
|
|
358
|
+
// Check with typeof since with prosemirror-transform pre 1.6.0 targetDepth is undefined
|
|
340
359
|
if (range && typeof targetDepth === 'number') {
|
|
341
360
|
return tr.lift(range, targetDepth);
|
|
342
361
|
}
|
|
@@ -464,7 +483,7 @@ function shouldMergeTrackedAttributes(left, right) {
|
|
|
464
483
|
}
|
|
465
484
|
return (left.status === right.status &&
|
|
466
485
|
left.operation === right.operation &&
|
|
467
|
-
left.
|
|
486
|
+
left.authorID === right.authorID);
|
|
468
487
|
}
|
|
469
488
|
function getMergeableMarkTrackedAttrs(node, attrs, schema) {
|
|
470
489
|
const nodeAttrs = getInlineNodeTrackedMarkData(node, schema);
|
|
@@ -584,6 +603,7 @@ function findChanges(state) {
|
|
|
584
603
|
type: 'text-change',
|
|
585
604
|
from: pos,
|
|
586
605
|
to: pos + node.nodeSize,
|
|
606
|
+
text: node.text,
|
|
587
607
|
attrs,
|
|
588
608
|
},
|
|
589
609
|
node,
|
|
@@ -628,13 +648,15 @@ function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
|
|
|
628
648
|
const iteratedIds = new Set();
|
|
629
649
|
let changed = false;
|
|
630
650
|
changeSet.invalidChanges.forEach((c) => {
|
|
631
|
-
const { id,
|
|
651
|
+
const { id, authorID, operation, reviewedByID, status, createdAt, updatedAt } = c.attrs;
|
|
632
652
|
const newAttrs = {
|
|
633
653
|
...((!id || iteratedIds.has(id) || id.length === 0) && { id: uuidv4() }),
|
|
634
|
-
...(!
|
|
654
|
+
...(!authorID && { authorID: trackUserID }),
|
|
635
655
|
...(!operation && { operation: exports.CHANGE_OPERATION.insert }),
|
|
656
|
+
...(!reviewedByID && { reviewedByID: null }),
|
|
636
657
|
...(!status && { status: exports.CHANGE_STATUS.pending }),
|
|
637
658
|
...(!createdAt && { createdAt: Date.now() }),
|
|
659
|
+
...(!updatedAt && { updatedAt: Date.now() }),
|
|
638
660
|
};
|
|
639
661
|
if (Object.keys(newAttrs).length > 0) {
|
|
640
662
|
updateChangeAttrs(newTr, c, { ...c.attrs, ...newAttrs }, schema);
|
|
@@ -853,10 +875,12 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
|
|
|
853
875
|
const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
|
|
854
876
|
const fromStartOfMark = start - (leftNode && leftMarks ? leftNode.nodeSize : 0);
|
|
855
877
|
const toEndOfMark = end + (rightNode && rightMarks ? rightNode.nodeSize : 0);
|
|
878
|
+
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);
|
|
856
879
|
const dataTracked = addTrackIdIfDoesntExist({
|
|
857
880
|
...leftMarks,
|
|
858
881
|
...rightMarks,
|
|
859
882
|
...deleteAttrs,
|
|
883
|
+
createdAt,
|
|
860
884
|
});
|
|
861
885
|
newTr.addMark(fromStartOfMark, toEndOfMark, schema.marks.tracked_delete.create({
|
|
862
886
|
dataTracked,
|
|
@@ -873,7 +897,8 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
|
|
|
873
897
|
*/
|
|
874
898
|
function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
|
|
875
899
|
const dataTracked = node.attrs.dataTracked;
|
|
876
|
-
const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === exports.CHANGE_OPERATION.insert &&
|
|
900
|
+
const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === exports.CHANGE_OPERATION.insert &&
|
|
901
|
+
dataTracked.authorID === deleteAttrs.authorID;
|
|
877
902
|
if (wasInsertedBySameUser) {
|
|
878
903
|
deleteNode(node, pos, newTr);
|
|
879
904
|
}
|
|
@@ -1070,18 +1095,21 @@ function mergeTrackedMarks(pos, doc, newTr, schema) {
|
|
|
1070
1095
|
if (!nodeAfter || !nodeBefore || !leftMark || !rightMark || leftMark.type !== rightMark.type) {
|
|
1071
1096
|
return;
|
|
1072
1097
|
}
|
|
1073
|
-
const
|
|
1074
|
-
const
|
|
1075
|
-
if (!shouldMergeTrackedAttributes(
|
|
1098
|
+
const leftDataTracked = leftMark.attrs.dataTracked;
|
|
1099
|
+
const rightDataTracked = rightMark.attrs.dataTracked;
|
|
1100
|
+
if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
|
|
1076
1101
|
return;
|
|
1077
1102
|
}
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1103
|
+
const isLeftOlder = (leftDataTracked.createdAt || Number.MAX_VALUE) <
|
|
1104
|
+
(rightDataTracked.createdAt || Number.MAX_VALUE);
|
|
1105
|
+
const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
|
|
1106
|
+
const dataTracked = {
|
|
1107
|
+
...ancestorAttrs,
|
|
1108
|
+
updatedAt: Date.now(),
|
|
1081
1109
|
};
|
|
1082
1110
|
const fromStartOfMark = pos - nodeBefore.nodeSize;
|
|
1083
1111
|
const toEndOfMark = pos + nodeAfter.nodeSize;
|
|
1084
|
-
newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create(
|
|
1112
|
+
newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create({ ...leftMark.attrs, dataTracked }));
|
|
1085
1113
|
}
|
|
1086
1114
|
|
|
1087
1115
|
/*!
|
|
@@ -1250,13 +1278,15 @@ const getSelectionStaticConstructor = (sel) => Object.getPrototypeOf(sel).constr
|
|
|
1250
1278
|
* @param tr Original transaction
|
|
1251
1279
|
* @param oldState State before transaction
|
|
1252
1280
|
* @param newTr Transaction created from the new editor state
|
|
1253
|
-
* @param
|
|
1281
|
+
* @param authorID User id
|
|
1254
1282
|
* @returns newTr that inverts the initial tr and applies track attributes/marks
|
|
1255
1283
|
*/
|
|
1256
|
-
function trackTransaction(tr, oldState, newTr,
|
|
1284
|
+
function trackTransaction(tr, oldState, newTr, authorID) {
|
|
1257
1285
|
const emptyAttrs = {
|
|
1258
|
-
|
|
1286
|
+
authorID,
|
|
1287
|
+
reviewedByID: null,
|
|
1259
1288
|
createdAt: tr.time,
|
|
1289
|
+
updatedAt: tr.time,
|
|
1260
1290
|
status: exports.CHANGE_STATUS.pending,
|
|
1261
1291
|
};
|
|
1262
1292
|
// Must use constructor.name instead of instanceof as aliasing prosemirror-state is a lot more
|
|
@@ -1368,8 +1398,8 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1368
1398
|
};
|
|
1369
1399
|
},
|
|
1370
1400
|
apply(tr, pluginState, _oldState, newState) {
|
|
1371
|
-
const setUserID = getAction(tr,
|
|
1372
|
-
const setStatus = getAction(tr,
|
|
1401
|
+
const setUserID = getAction(tr, TrackChangesAction.setUserID);
|
|
1402
|
+
const setStatus = getAction(tr, TrackChangesAction.setPluginStatus);
|
|
1373
1403
|
if (setUserID) {
|
|
1374
1404
|
return { ...pluginState, userID: setUserID };
|
|
1375
1405
|
}
|
|
@@ -1384,8 +1414,8 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1384
1414
|
return { ...pluginState, changeSet: new ChangeSet() };
|
|
1385
1415
|
}
|
|
1386
1416
|
let { changeSet, ...rest } = pluginState;
|
|
1387
|
-
const updatedChangeIds = getAction(tr,
|
|
1388
|
-
if (updatedChangeIds || getAction(tr,
|
|
1417
|
+
const updatedChangeIds = getAction(tr, TrackChangesAction.updateChanges);
|
|
1418
|
+
if (updatedChangeIds || getAction(tr, TrackChangesAction.refreshChanges)) {
|
|
1389
1419
|
changeSet = findChanges(newState);
|
|
1390
1420
|
}
|
|
1391
1421
|
return {
|
|
@@ -1414,27 +1444,27 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1414
1444
|
trs.forEach((tr) => {
|
|
1415
1445
|
const wasAppended = tr.getMeta('appendedTransaction');
|
|
1416
1446
|
const skipMetaUsed = skipTrsWithMetas.some((m) => tr.getMeta(m) || (wasAppended === null || wasAppended === void 0 ? void 0 : wasAppended.getMeta(m)));
|
|
1417
|
-
const skipTrackUsed = getAction(tr,
|
|
1418
|
-
(wasAppended && getAction(wasAppended,
|
|
1447
|
+
const skipTrackUsed = getAction(tr, TrackChangesAction.skipTrack) ||
|
|
1448
|
+
(wasAppended && getAction(wasAppended, TrackChangesAction.skipTrack));
|
|
1419
1449
|
if (tr.docChanged && !skipMetaUsed && !skipTrackUsed && !tr.getMeta('history$')) {
|
|
1420
1450
|
createdTr = trackTransaction(tr, oldState, createdTr, userID);
|
|
1421
1451
|
}
|
|
1422
1452
|
docChanged = docChanged || tr.docChanged;
|
|
1423
|
-
const setChangeStatuses = getAction(tr,
|
|
1453
|
+
const setChangeStatuses = getAction(tr, TrackChangesAction.setChangeStatuses);
|
|
1424
1454
|
if (setChangeStatuses) {
|
|
1425
1455
|
const { status, ids } = setChangeStatuses;
|
|
1426
1456
|
ids.forEach((changeId) => {
|
|
1427
1457
|
const change = changeSet === null || changeSet === void 0 ? void 0 : changeSet.get(changeId);
|
|
1428
1458
|
if (change) {
|
|
1429
|
-
createdTr = updateChangeAttrs(createdTr, change, { status }, oldState.schema);
|
|
1430
|
-
setAction(createdTr,
|
|
1459
|
+
createdTr = updateChangeAttrs(createdTr, change, { status, reviewedByID: userID }, oldState.schema);
|
|
1460
|
+
setAction(createdTr, TrackChangesAction.updateChanges, [change.id]);
|
|
1431
1461
|
}
|
|
1432
1462
|
});
|
|
1433
1463
|
}
|
|
1434
|
-
else if (getAction(tr,
|
|
1464
|
+
else if (getAction(tr, TrackChangesAction.applyAndRemoveChanges)) {
|
|
1435
1465
|
const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.nodeChanges);
|
|
1436
1466
|
applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.textChanges, mapping);
|
|
1437
|
-
setAction(createdTr,
|
|
1467
|
+
setAction(createdTr, TrackChangesAction.refreshChanges, true);
|
|
1438
1468
|
}
|
|
1439
1469
|
});
|
|
1440
1470
|
const changed = pluginState.changeSet.hasInconsistentData &&
|
|
@@ -1444,7 +1474,7 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1444
1474
|
}
|
|
1445
1475
|
if (docChanged || createdTr.docChanged || changed) {
|
|
1446
1476
|
createdTr.setMeta('origin', trackChangesPluginKey);
|
|
1447
|
-
return setAction(createdTr,
|
|
1477
|
+
return setAction(createdTr, TrackChangesAction.refreshChanges, true);
|
|
1448
1478
|
}
|
|
1449
1479
|
return null;
|
|
1450
1480
|
},
|
|
@@ -1486,7 +1516,7 @@ const setTrackingStatus = (status) => (state, dispatch) => {
|
|
|
1486
1516
|
? exports.TrackChangesStatus.disabled
|
|
1487
1517
|
: exports.TrackChangesStatus.enabled;
|
|
1488
1518
|
}
|
|
1489
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1519
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.setPluginStatus, newStatus));
|
|
1490
1520
|
return true;
|
|
1491
1521
|
}
|
|
1492
1522
|
return false;
|
|
@@ -1498,7 +1528,7 @@ const setTrackingStatus = (status) => (state, dispatch) => {
|
|
|
1498
1528
|
*/
|
|
1499
1529
|
const setChangeStatuses = (status, ids) => (state, dispatch) => {
|
|
1500
1530
|
dispatch &&
|
|
1501
|
-
dispatch(setAction(state.tr,
|
|
1531
|
+
dispatch(setAction(state.tr, TrackChangesAction.setChangeStatuses, {
|
|
1502
1532
|
status,
|
|
1503
1533
|
ids,
|
|
1504
1534
|
}));
|
|
@@ -1509,21 +1539,21 @@ const setChangeStatuses = (status, ids) => (state, dispatch) => {
|
|
|
1509
1539
|
* @param userID
|
|
1510
1540
|
*/
|
|
1511
1541
|
const setUserID = (userID) => (state, dispatch) => {
|
|
1512
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1542
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.setUserID, userID));
|
|
1513
1543
|
return true;
|
|
1514
1544
|
};
|
|
1515
1545
|
/**
|
|
1516
1546
|
* Appends a transaction that applies all 'accepted' and 'rejected' changes to the document.
|
|
1517
1547
|
*/
|
|
1518
1548
|
const applyAndRemoveChanges = () => (state, dispatch) => {
|
|
1519
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1549
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.applyAndRemoveChanges, true));
|
|
1520
1550
|
return true;
|
|
1521
1551
|
};
|
|
1522
1552
|
/**
|
|
1523
1553
|
* Runs `findChanges` to iterate over the document to collect changes into a new ChangeSet.
|
|
1524
1554
|
*/
|
|
1525
1555
|
const refreshChanges = () => (state, dispatch) => {
|
|
1526
|
-
dispatch && dispatch(setAction(state.tr,
|
|
1556
|
+
dispatch && dispatch(setAction(state.tr, TrackChangesAction.updateChanges, []));
|
|
1527
1557
|
return true;
|
|
1528
1558
|
};
|
|
1529
1559
|
/**
|
|
@@ -1555,8 +1585,7 @@ var commands = /*#__PURE__*/Object.freeze({
|
|
|
1555
1585
|
|
|
1556
1586
|
exports.ChangeSet = ChangeSet;
|
|
1557
1587
|
exports.enableDebug = enableDebug;
|
|
1558
|
-
exports.
|
|
1559
|
-
exports.setAction = setAction;
|
|
1588
|
+
exports.skipTracking = skipTracking;
|
|
1560
1589
|
exports.trackChangesPlugin = trackChangesPlugin;
|
|
1561
1590
|
exports.trackChangesPluginKey = trackChangesPluginKey;
|
|
1562
1591
|
exports.trackCommands = commands;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
export { trackChangesPluginKey, trackChangesPlugin } from './plugin';
|
|
17
|
-
export
|
|
17
|
+
export { skipTracking } from './actions';
|
|
18
18
|
export * as trackCommands from './commands';
|
|
19
19
|
export { enableDebug } from './utils/logger';
|
|
20
20
|
export { ChangeSet } from './ChangeSet';
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,14 @@ function getAction(tr, action) {
|
|
|
30
30
|
*/
|
|
31
31
|
function setAction(tr, action, payload) {
|
|
32
32
|
return tr.setMeta(action, payload);
|
|
33
|
-
}
|
|
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);
|
|
34
41
|
|
|
35
42
|
/******************************************************************************
|
|
36
43
|
Copyright (c) Microsoft Corporation.
|
|
@@ -283,10 +290,22 @@ class ChangeSet {
|
|
|
283
290
|
if ('attrs' in attrs) {
|
|
284
291
|
log.warn('passed "attrs" as property to isValidTrackedAttrs(attrs)', attrs);
|
|
285
292
|
}
|
|
286
|
-
const trackedKeys = [
|
|
287
|
-
|
|
293
|
+
const trackedKeys = [
|
|
294
|
+
'id',
|
|
295
|
+
'authorID',
|
|
296
|
+
'operation',
|
|
297
|
+
'status',
|
|
298
|
+
'createdAt',
|
|
299
|
+
'updatedAt',
|
|
300
|
+
];
|
|
301
|
+
// reviewedByID is set optional since either ProseMirror or Yjs doesn't like persisting null values inside attributes objects
|
|
302
|
+
// So it can be either omitted completely or at least null or string
|
|
303
|
+
const optionalKeys = ['reviewedByID'];
|
|
304
|
+
const entries = Object.entries(attrs).filter(([key, val]) => trackedKeys.includes(key));
|
|
305
|
+
const optionalEntries = Object.entries(attrs).filter(([key, val]) => optionalKeys.includes(key));
|
|
288
306
|
return (entries.length === trackedKeys.length &&
|
|
289
307
|
entries.every(([key, val]) => trackedKeys.includes(key) && val !== undefined) &&
|
|
308
|
+
optionalEntries.every(([key, val]) => optionalKeys.includes(key) && val !== undefined) &&
|
|
290
309
|
(attrs.id || '').length > 0 // Changes created with undefined id have '' as placeholder
|
|
291
310
|
);
|
|
292
311
|
}
|
|
@@ -328,7 +347,7 @@ function deleteNode(node, pos, tr) {
|
|
|
328
347
|
const startPos = tr.doc.resolve(pos + 1);
|
|
329
348
|
const range = startPos.blockRange(tr.doc.resolve(startPos.pos - 2 + node.nodeSize));
|
|
330
349
|
const targetDepth = range && liftTarget(range);
|
|
331
|
-
// Check with typeof since with
|
|
350
|
+
// Check with typeof since with prosemirror-transform pre 1.6.0 targetDepth is undefined
|
|
332
351
|
if (range && typeof targetDepth === 'number') {
|
|
333
352
|
return tr.lift(range, targetDepth);
|
|
334
353
|
}
|
|
@@ -456,7 +475,7 @@ function shouldMergeTrackedAttributes(left, right) {
|
|
|
456
475
|
}
|
|
457
476
|
return (left.status === right.status &&
|
|
458
477
|
left.operation === right.operation &&
|
|
459
|
-
left.
|
|
478
|
+
left.authorID === right.authorID);
|
|
460
479
|
}
|
|
461
480
|
function getMergeableMarkTrackedAttrs(node, attrs, schema) {
|
|
462
481
|
const nodeAttrs = getInlineNodeTrackedMarkData(node, schema);
|
|
@@ -576,6 +595,7 @@ function findChanges(state) {
|
|
|
576
595
|
type: 'text-change',
|
|
577
596
|
from: pos,
|
|
578
597
|
to: pos + node.nodeSize,
|
|
598
|
+
text: node.text,
|
|
579
599
|
attrs,
|
|
580
600
|
},
|
|
581
601
|
node,
|
|
@@ -620,13 +640,15 @@ function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
|
|
|
620
640
|
const iteratedIds = new Set();
|
|
621
641
|
let changed = false;
|
|
622
642
|
changeSet.invalidChanges.forEach((c) => {
|
|
623
|
-
const { id,
|
|
643
|
+
const { id, authorID, operation, reviewedByID, status, createdAt, updatedAt } = c.attrs;
|
|
624
644
|
const newAttrs = {
|
|
625
645
|
...((!id || iteratedIds.has(id) || id.length === 0) && { id: uuidv4() }),
|
|
626
|
-
...(!
|
|
646
|
+
...(!authorID && { authorID: trackUserID }),
|
|
627
647
|
...(!operation && { operation: CHANGE_OPERATION.insert }),
|
|
648
|
+
...(!reviewedByID && { reviewedByID: null }),
|
|
628
649
|
...(!status && { status: CHANGE_STATUS.pending }),
|
|
629
650
|
...(!createdAt && { createdAt: Date.now() }),
|
|
651
|
+
...(!updatedAt && { updatedAt: Date.now() }),
|
|
630
652
|
};
|
|
631
653
|
if (Object.keys(newAttrs).length > 0) {
|
|
632
654
|
updateChangeAttrs(newTr, c, { ...c.attrs, ...newAttrs }, schema);
|
|
@@ -845,10 +867,12 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
|
|
|
845
867
|
const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
|
|
846
868
|
const fromStartOfMark = start - (leftNode && leftMarks ? leftNode.nodeSize : 0);
|
|
847
869
|
const toEndOfMark = end + (rightNode && rightMarks ? rightNode.nodeSize : 0);
|
|
870
|
+
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);
|
|
848
871
|
const dataTracked = addTrackIdIfDoesntExist({
|
|
849
872
|
...leftMarks,
|
|
850
873
|
...rightMarks,
|
|
851
874
|
...deleteAttrs,
|
|
875
|
+
createdAt,
|
|
852
876
|
});
|
|
853
877
|
newTr.addMark(fromStartOfMark, toEndOfMark, schema.marks.tracked_delete.create({
|
|
854
878
|
dataTracked,
|
|
@@ -865,7 +889,8 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
|
|
|
865
889
|
*/
|
|
866
890
|
function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
|
|
867
891
|
const dataTracked = node.attrs.dataTracked;
|
|
868
|
-
const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === CHANGE_OPERATION.insert &&
|
|
892
|
+
const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === CHANGE_OPERATION.insert &&
|
|
893
|
+
dataTracked.authorID === deleteAttrs.authorID;
|
|
869
894
|
if (wasInsertedBySameUser) {
|
|
870
895
|
deleteNode(node, pos, newTr);
|
|
871
896
|
}
|
|
@@ -1062,18 +1087,21 @@ function mergeTrackedMarks(pos, doc, newTr, schema) {
|
|
|
1062
1087
|
if (!nodeAfter || !nodeBefore || !leftMark || !rightMark || leftMark.type !== rightMark.type) {
|
|
1063
1088
|
return;
|
|
1064
1089
|
}
|
|
1065
|
-
const
|
|
1066
|
-
const
|
|
1067
|
-
if (!shouldMergeTrackedAttributes(
|
|
1090
|
+
const leftDataTracked = leftMark.attrs.dataTracked;
|
|
1091
|
+
const rightDataTracked = rightMark.attrs.dataTracked;
|
|
1092
|
+
if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
|
|
1068
1093
|
return;
|
|
1069
1094
|
}
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1095
|
+
const isLeftOlder = (leftDataTracked.createdAt || Number.MAX_VALUE) <
|
|
1096
|
+
(rightDataTracked.createdAt || Number.MAX_VALUE);
|
|
1097
|
+
const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
|
|
1098
|
+
const dataTracked = {
|
|
1099
|
+
...ancestorAttrs,
|
|
1100
|
+
updatedAt: Date.now(),
|
|
1073
1101
|
};
|
|
1074
1102
|
const fromStartOfMark = pos - nodeBefore.nodeSize;
|
|
1075
1103
|
const toEndOfMark = pos + nodeAfter.nodeSize;
|
|
1076
|
-
newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create(
|
|
1104
|
+
newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create({ ...leftMark.attrs, dataTracked }));
|
|
1077
1105
|
}
|
|
1078
1106
|
|
|
1079
1107
|
/*!
|
|
@@ -1242,13 +1270,15 @@ const getSelectionStaticConstructor = (sel) => Object.getPrototypeOf(sel).constr
|
|
|
1242
1270
|
* @param tr Original transaction
|
|
1243
1271
|
* @param oldState State before transaction
|
|
1244
1272
|
* @param newTr Transaction created from the new editor state
|
|
1245
|
-
* @param
|
|
1273
|
+
* @param authorID User id
|
|
1246
1274
|
* @returns newTr that inverts the initial tr and applies track attributes/marks
|
|
1247
1275
|
*/
|
|
1248
|
-
function trackTransaction(tr, oldState, newTr,
|
|
1276
|
+
function trackTransaction(tr, oldState, newTr, authorID) {
|
|
1249
1277
|
const emptyAttrs = {
|
|
1250
|
-
|
|
1278
|
+
authorID,
|
|
1279
|
+
reviewedByID: null,
|
|
1251
1280
|
createdAt: tr.time,
|
|
1281
|
+
updatedAt: tr.time,
|
|
1252
1282
|
status: CHANGE_STATUS.pending,
|
|
1253
1283
|
};
|
|
1254
1284
|
// Must use constructor.name instead of instanceof as aliasing prosemirror-state is a lot more
|
|
@@ -1418,7 +1448,7 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
|
|
|
1418
1448
|
ids.forEach((changeId) => {
|
|
1419
1449
|
const change = changeSet === null || changeSet === void 0 ? void 0 : changeSet.get(changeId);
|
|
1420
1450
|
if (change) {
|
|
1421
|
-
createdTr = updateChangeAttrs(createdTr, change, { status }, oldState.schema);
|
|
1451
|
+
createdTr = updateChangeAttrs(createdTr, change, { status, reviewedByID: userID }, oldState.schema);
|
|
1422
1452
|
setAction(createdTr, TrackChangesAction.updateChanges, [change.id]);
|
|
1423
1453
|
}
|
|
1424
1454
|
});
|
|
@@ -1545,4 +1575,4 @@ var commands = /*#__PURE__*/Object.freeze({
|
|
|
1545
1575
|
setParagraphTestAttribute: setParagraphTestAttribute
|
|
1546
1576
|
});
|
|
1547
1577
|
|
|
1548
|
-
export { CHANGE_OPERATION, CHANGE_STATUS, ChangeSet,
|
|
1578
|
+
export { CHANGE_OPERATION, CHANGE_STATUS, ChangeSet, TrackChangesStatus, enableDebug, skipTracking, trackChangesPlugin, trackChangesPluginKey, commands as trackCommands };
|
|
@@ -14,21 +14,14 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { Node as PMNode, Schema } from 'prosemirror-model';
|
|
17
|
-
import { Transaction } from 'prosemirror-state';
|
|
18
17
|
import { CHANGE_OPERATION, TrackedAttrs } from '../types/change';
|
|
19
18
|
export declare function addTrackIdIfDoesntExist(attrs: Partial<TrackedAttrs>): Partial<TrackedAttrs>;
|
|
20
|
-
|
|
21
|
-
* Not in use, maybe for ReplaceAroundSteps but we'll see
|
|
22
|
-
* @param pos
|
|
23
|
-
* @param tr
|
|
24
|
-
*/
|
|
25
|
-
export declare function liftNode(pos: number, tr: Transaction): Transaction | undefined;
|
|
26
|
-
export declare function getInlineNodeTrackedMarkData(node: PMNode | undefined | null, schema: Schema): {
|
|
19
|
+
export declare function getInlineNodeTrackedMarkData(node: PMNode | undefined | null, schema: Schema): (Omit<Partial<TrackedAttrs>, "operation"> & {
|
|
27
20
|
operation: CHANGE_OPERATION;
|
|
28
|
-
} | undefined;
|
|
21
|
+
}) | undefined;
|
|
29
22
|
export declare function getNodeTrackedData(node: PMNode | undefined | null, schema: Schema): Partial<TrackedAttrs> | undefined;
|
|
30
23
|
export declare function equalMarks(n1: PMNode, n2: PMNode): boolean;
|
|
31
24
|
export declare function shouldMergeTrackedAttributes(left?: Partial<TrackedAttrs>, right?: Partial<TrackedAttrs>): boolean;
|
|
32
|
-
export declare function getMergeableMarkTrackedAttrs(node: PMNode | undefined | null, attrs: Partial<TrackedAttrs>, schema: Schema): {
|
|
25
|
+
export declare function getMergeableMarkTrackedAttrs(node: PMNode | undefined | null, attrs: Partial<TrackedAttrs>, schema: Schema): (Omit<Partial<TrackedAttrs>, "operation"> & {
|
|
33
26
|
operation: CHANGE_OPERATION;
|
|
34
|
-
} | null;
|
|
27
|
+
}) | null;
|
|
@@ -14,6 +14,5 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { NewDeleteAttrs, NewEmptyAttrs, NewInsertAttrs } from '../../types/track';
|
|
17
|
-
export declare function createNewEmptyAttrs(userID: string, createdAt: number): NewEmptyAttrs;
|
|
18
17
|
export declare function createNewInsertAttrs(attrs: NewEmptyAttrs): NewInsertAttrs;
|
|
19
18
|
export declare function createNewDeleteAttrs(attrs: NewEmptyAttrs): NewDeleteAttrs;
|
|
@@ -11,7 +11,7 @@ import type { EditorState, Transaction } from 'prosemirror-state';
|
|
|
11
11
|
* @param tr Original transaction
|
|
12
12
|
* @param oldState State before transaction
|
|
13
13
|
* @param newTr Transaction created from the new editor state
|
|
14
|
-
* @param
|
|
14
|
+
* @param authorID User id
|
|
15
15
|
* @returns newTr that inverts the initial tr and applies track attributes/marks
|
|
16
16
|
*/
|
|
17
|
-
export declare function trackTransaction(tr: Transaction, oldState: EditorState, newTr: Transaction,
|
|
17
|
+
export declare function trackTransaction(tr: Transaction, oldState: EditorState, newTr: Transaction, authorID: string): Transaction;
|
package/dist/types/change.d.ts
CHANGED
|
@@ -29,10 +29,12 @@ export declare enum CHANGE_STATUS {
|
|
|
29
29
|
}
|
|
30
30
|
export interface TrackedAttrs {
|
|
31
31
|
id: string;
|
|
32
|
-
|
|
32
|
+
authorID: string;
|
|
33
|
+
reviewedByID: string | null;
|
|
33
34
|
operation: CHANGE_OPERATION;
|
|
34
35
|
status: CHANGE_STATUS;
|
|
35
36
|
createdAt: number;
|
|
37
|
+
updatedAt: number;
|
|
36
38
|
}
|
|
37
39
|
export declare type Change = {
|
|
38
40
|
id: string;
|
|
@@ -42,6 +44,7 @@ export declare type Change = {
|
|
|
42
44
|
};
|
|
43
45
|
export declare type TextChange = Change & {
|
|
44
46
|
type: 'text-change';
|
|
47
|
+
text: string;
|
|
45
48
|
};
|
|
46
49
|
export declare type NodeChange = Change & {
|
|
47
50
|
type: 'node-change';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manuscripts/track-changes-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"author": "Atypon Systems LLC",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/Atypon-OpenSource/manuscripts-quarterback/tree/main/quarterback-packages/track-changes-plugin",
|