@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 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
- First install the plugin: `npm i @manuscripts/track-changes-plugin`
7
+ Requires normal ProseMirror editor dependencies.
12
8
 
13
- Then add the plugin to ProseMirror plugins:
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` is https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/quarterback-packages/track-changes-plugin/test/utils/schema.ts
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
- Enable or disable the plugin with:
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. I don't think you ever would need to use other than `TrackChangesAction.skipTrack` but they are all exposed, nonetheless.
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
- * Gets the value of a meta field, action payload, of a defined track-changes action.
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
- * @param action
141
+ * @returns
152
142
  */
153
- export function getAction<K extends keyof TrackChangesActionParams>(tr: Transaction, action: K) {
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
- exports.TrackChangesAction = void 0;
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
- })(exports.TrackChangesAction || (exports.TrackChangesAction = {}));
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 = ['id', 'userID', 'operation', 'status', 'createdAt'];
295
- const entries = Object.entries(attrs);
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 old prosemirror-transform targetDepth is undefined
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.userID === right.userID);
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, userID, operation, status, createdAt } = c.attrs;
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
- ...(!userID && { userID: trackUserID }),
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 && dataTracked.userID === deleteAttrs.userID;
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 leftAttrs = leftMark.attrs;
1074
- const rightAttrs = rightMark.attrs;
1075
- if (!shouldMergeTrackedAttributes(leftAttrs.dataTracked, rightAttrs.dataTracked)) {
1098
+ const leftDataTracked = leftMark.attrs.dataTracked;
1099
+ const rightDataTracked = rightMark.attrs.dataTracked;
1100
+ if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
1076
1101
  return;
1077
1102
  }
1078
- const newAttrs = {
1079
- ...leftAttrs,
1080
- createdAt: Math.max(leftAttrs.createdAt || 0, rightAttrs.createdAt || 0) || Date.now(),
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(newAttrs));
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 userID User id
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, userID) {
1284
+ function trackTransaction(tr, oldState, newTr, authorID) {
1257
1285
  const emptyAttrs = {
1258
- userID,
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, exports.TrackChangesAction.setUserID);
1372
- const setStatus = getAction(tr, exports.TrackChangesAction.setPluginStatus);
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, exports.TrackChangesAction.updateChanges);
1388
- if (updatedChangeIds || getAction(tr, exports.TrackChangesAction.refreshChanges)) {
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, exports.TrackChangesAction.skipTrack) ||
1418
- (wasAppended && getAction(wasAppended, exports.TrackChangesAction.skipTrack));
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, exports.TrackChangesAction.setChangeStatuses);
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, exports.TrackChangesAction.updateChanges, [change.id]);
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, exports.TrackChangesAction.applyAndRemoveChanges)) {
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, exports.TrackChangesAction.refreshChanges, true);
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, exports.TrackChangesAction.refreshChanges, true);
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, exports.TrackChangesAction.setPluginStatus, newStatus));
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, exports.TrackChangesAction.setChangeStatuses, {
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, exports.TrackChangesAction.setUserID, userID));
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, exports.TrackChangesAction.applyAndRemoveChanges, true));
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, exports.TrackChangesAction.updateChanges, []));
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.getAction = getAction;
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 * from './actions';
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 = ['id', 'userID', 'operation', 'status', 'createdAt'];
287
- const entries = Object.entries(attrs);
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 old prosemirror-transform targetDepth is undefined
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.userID === right.userID);
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, userID, operation, status, createdAt } = c.attrs;
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
- ...(!userID && { userID: trackUserID }),
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 && dataTracked.userID === deleteAttrs.userID;
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 leftAttrs = leftMark.attrs;
1066
- const rightAttrs = rightMark.attrs;
1067
- if (!shouldMergeTrackedAttributes(leftAttrs.dataTracked, rightAttrs.dataTracked)) {
1090
+ const leftDataTracked = leftMark.attrs.dataTracked;
1091
+ const rightDataTracked = rightMark.attrs.dataTracked;
1092
+ if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
1068
1093
  return;
1069
1094
  }
1070
- const newAttrs = {
1071
- ...leftAttrs,
1072
- createdAt: Math.max(leftAttrs.createdAt || 0, rightAttrs.createdAt || 0) || Date.now(),
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(newAttrs));
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 userID User id
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, userID) {
1276
+ function trackTransaction(tr, oldState, newTr, authorID) {
1249
1277
  const emptyAttrs = {
1250
- userID,
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, TrackChangesAction, TrackChangesStatus, enableDebug, getAction, setAction, trackChangesPlugin, trackChangesPluginKey, commands as trackCommands };
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 userID User id
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, userID: string): Transaction;
17
+ export declare function trackTransaction(tr: Transaction, oldState: EditorState, newTr: Transaction, authorID: string): Transaction;
@@ -29,10 +29,12 @@ export declare enum CHANGE_STATUS {
29
29
  }
30
30
  export interface TrackedAttrs {
31
31
  id: string;
32
- userID: string;
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.2.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",