@manuscripts/track-changes-plugin 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/actions.d.ts CHANGED
@@ -21,7 +21,6 @@ export declare enum TrackChangesAction {
21
21
  setUserID = "track-changes-set-user-id",
22
22
  setPluginStatus = "track-changes-set-track-status",
23
23
  setChangeStatuses = "track-changes-set-change-statuses",
24
- updateChanges = "track-changes-update-changes",
25
24
  refreshChanges = "track-changes-refresh-changes",
26
25
  applyAndRemoveChanges = "track-changes-apply-remove-changes"
27
26
  }
@@ -33,7 +32,6 @@ export declare type TrackChangesActionParams = {
33
32
  status: CHANGE_STATUS;
34
33
  ids: string[];
35
34
  };
36
- [TrackChangesAction.updateChanges]: string[];
37
35
  [TrackChangesAction.refreshChanges]: boolean;
38
36
  [TrackChangesAction.applyAndRemoveChanges]: boolean;
39
37
  };
@@ -1,19 +1,2 @@
1
- /*!
2
- * © 2021 Atypon Systems LLC
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import { Schema } from 'prosemirror-model';
17
- import type { Transaction } from 'prosemirror-state';
18
1
  import { ChangeStep, InsertSliceStep } from '../types/step';
19
- export declare function diffChangeSteps(deleted: ChangeStep[], inserted: InsertSliceStep[], newTr: Transaction, schema: Schema): ChangeStep[];
2
+ export declare function diffChangeSteps(deleted: ChangeStep[], inserted: InsertSliceStep[]): ChangeStep[];
@@ -1,3 +1,13 @@
1
1
  import { ExposedFragment } from '../types/pm';
2
2
  import { ChangeStep } from '../types/step';
3
+ /**
4
+ * Matches deleted to inserted content and returns the first pos they differ and the updated
5
+ * ChangeStep list.
6
+ *
7
+ * Based on https://github.com/ProseMirror/prosemirror-model/blob/master/src/diff.ts
8
+ * @param matchedDeleted
9
+ * @param deleted
10
+ * @param inserted
11
+ * @returns
12
+ */
3
13
  export declare function matchInserted(matchedDeleted: number, deleted: ChangeStep[], inserted: ExposedFragment): [number, ChangeStep[]];
@@ -18,9 +18,9 @@ import { ChangeSet } from '../ChangeSet';
18
18
  /**
19
19
  * Finds all changes (basically text marks or node attributes) from document
20
20
  *
21
- * This could be possibly made more efficient by only iterating the sections of doc
22
- * where changes have been applied. This could attempted with eg findDiffStart
23
- * but it might be less robust than just using doc.descendants
21
+ * This could be possibly made more efficient by only iterating the sections of doc where changes have
22
+ * been applied. This could attempted with eg findDiffStart but it might be less robust than just using
23
+ * doc.descendants
24
24
  * @param state
25
25
  * @returns
26
26
  */
@@ -25,7 +25,7 @@ import { TrackChangesStatus } from './types/track';
25
25
  * to the document.
26
26
  * @param status
27
27
  */
28
- export declare const setTrackingStatus: (status?: TrackChangesStatus | undefined) => Command;
28
+ export declare const setTrackingStatus: (status?: TrackChangesStatus) => Command;
29
29
  /**
30
30
  * Appends a transaction to set change attributes/marks' statuses to any of: 'pending' 'accepted' 'rejected'.
31
31
  * @param status
@@ -45,7 +45,3 @@ export declare const applyAndRemoveChanges: () => Command;
45
45
  * Runs `findChanges` to iterate over the document to collect changes into a new ChangeSet.
46
46
  */
47
47
  export declare const refreshChanges: () => Command;
48
- /**
49
- * Adds track attributes for a block node. For testing puroses
50
- */
51
- export declare const setParagraphTestAttribute: (val?: string) => Command;
package/dist/index.cjs CHANGED
@@ -17,7 +17,6 @@ var TrackChangesAction;
17
17
  TrackChangesAction["setUserID"] = "track-changes-set-user-id";
18
18
  TrackChangesAction["setPluginStatus"] = "track-changes-set-track-status";
19
19
  TrackChangesAction["setChangeStatuses"] = "track-changes-set-change-statuses";
20
- TrackChangesAction["updateChanges"] = "track-changes-update-changes";
21
20
  TrackChangesAction["refreshChanges"] = "track-changes-refresh-changes";
22
21
  TrackChangesAction["applyAndRemoveChanges"] = "track-changes-apply-remove-changes";
23
22
  })(TrackChangesAction || (TrackChangesAction = {}));
@@ -373,7 +372,7 @@ function getTextNodeTrackedMarkData(node, schema) {
373
372
  return marksTrackedData[0] || undefined;
374
373
  }
375
374
  function getBlockInlineTrackedData(node) {
376
- let { dataTracked } = node.attrs;
375
+ const { dataTracked } = node.attrs;
377
376
  if (dataTracked && !Array.isArray(dataTracked)) {
378
377
  return [dataTracked];
379
378
  }
@@ -635,9 +634,9 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new prose
635
634
  /**
636
635
  * Finds all changes (basically text marks or node attributes) from document
637
636
  *
638
- * This could be possibly made more efficient by only iterating the sections of doc
639
- * where changes have been applied. This could attempted with eg findDiffStart
640
- * but it might be less robust than just using doc.descendants
637
+ * This could be possibly made more efficient by only iterating the sections of doc where changes have
638
+ * been applied. This could attempted with eg findDiffStart but it might be less robust than just using
639
+ * doc.descendants
641
640
  * @param state
642
641
  * @returns
643
642
  */
@@ -1021,7 +1020,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1021
1020
  // @TODO ATM 20.7.2022 there doesn't seem to be tests that capture this.
1022
1021
  const wasWithinGap = gap &&
1023
1022
  ((!node.isText && pos >= gap.start) ||
1024
- (node.isText && pos <= gap.start && nodeEnd >= gap.start));
1023
+ (node.isText && pos >= gap.start && nodeEnd <= gap.end));
1025
1024
  // nodeEnd > offsetFrom -> delete touches this node
1026
1025
  // eg (del 6 10) <p 5>|<t 6>cdf</t 9></p 10>| -> <p> nodeEnd 10 > from 6
1027
1026
  if (nodeEnd > from && !wasWithinGap) {
@@ -1122,41 +1121,6 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1122
1121
  };
1123
1122
  }
1124
1123
 
1125
- /**
1126
- * Merges tracked marks between text nodes at a position
1127
- *
1128
- * Will work for any nodes that use tracked_insert or tracked_delete marks which may not be preferrable
1129
- * if used for block nodes (since we possibly want to show the individual changed nodes).
1130
- * Merging is done based on the userID, operation type and status.
1131
- * @param pos
1132
- * @param doc
1133
- * @param newTr
1134
- * @param schema
1135
- */
1136
- function mergeTrackedMarks(pos, doc, newTr, schema) {
1137
- const resolved = doc.resolve(pos);
1138
- const { nodeAfter, nodeBefore } = resolved;
1139
- const leftMark = nodeBefore === null || nodeBefore === void 0 ? void 0 : nodeBefore.marks.filter((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete)[0];
1140
- const rightMark = nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.marks.filter((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete)[0];
1141
- if (!nodeAfter || !nodeBefore || !leftMark || !rightMark || leftMark.type !== rightMark.type) {
1142
- return;
1143
- }
1144
- const leftDataTracked = leftMark.attrs.dataTracked;
1145
- const rightDataTracked = rightMark.attrs.dataTracked;
1146
- if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
1147
- return;
1148
- }
1149
- const isLeftOlder = (leftDataTracked.createdAt || 0) < (rightDataTracked.createdAt || 0);
1150
- const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
1151
- const dataTracked = {
1152
- ...ancestorAttrs,
1153
- updatedAt: Date.now(),
1154
- };
1155
- const fromStartOfMark = pos - nodeBefore.nodeSize;
1156
- const toEndOfMark = pos + nodeAfter.nodeSize;
1157
- newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create({ ...leftMark.attrs, dataTracked }));
1158
- }
1159
-
1160
1124
  /*!
1161
1125
  * © 2021 Atypon Systems LLC
1162
1126
  *
@@ -1209,7 +1173,7 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1209
1173
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1210
1174
  // eg partial nodes in an open-ended slice
1211
1175
  const { sliceWasSplit, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1212
- let steps = deleteSteps;
1176
+ const steps = deleteSteps;
1213
1177
  log.info('TR: new steps after applying delete', [...newTr.steps]);
1214
1178
  log.info('DELETE STEPS: ', deleteSteps);
1215
1179
  // We only want to insert when there something inside the gap (actually would this be always true?)
@@ -1234,11 +1198,6 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1234
1198
  sliceWasSplit,
1235
1199
  });
1236
1200
  }
1237
- else {
1238
- // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1239
- mergeTrackedMarks(gapFrom, newTr.doc, newTr, oldState.schema);
1240
- mergeTrackedMarks(gapTo, newTr.doc, newTr, oldState.schema);
1241
- }
1242
1201
  return steps;
1243
1202
  }
1244
1203
 
@@ -1277,7 +1236,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1277
1236
  changeSteps.push(...deleteSteps);
1278
1237
  log.info('TR: steps after applying delete', [...newTr.steps]);
1279
1238
  log.info('DELETE STEPS: ', changeSteps);
1280
- const adjustedInsertPos = toA; // deleteMap.map(toA)
1239
+ const adjustedInsertPos = toA;
1281
1240
  if (newSliceContent.size > 0) {
1282
1241
  log.info('newSliceContent', newSliceContent);
1283
1242
  // Since deleteAndMergeSplitBlockNodes modified the slice to not to contain any merged nodes,
@@ -1294,7 +1253,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1294
1253
  }
1295
1254
  else {
1296
1255
  // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1297
- mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema);
1256
+ // mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema)
1298
1257
  selectionPos = fromA;
1299
1258
  }
1300
1259
  });
@@ -1328,6 +1287,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1328
1287
  * @param deleteAttrs
1329
1288
  * @param from
1330
1289
  * @param to
1290
+ * @returns position at the end of the possibly deleted text
1331
1291
  */
1332
1292
  function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1333
1293
  const start = from ? Math.max(pos, from) : pos;
@@ -1361,6 +1321,41 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1361
1321
  }
1362
1322
  }
1363
1323
 
1324
+ /**
1325
+ * Merges tracked marks between text nodes at a position
1326
+ *
1327
+ * Will work for any nodes that use tracked_insert or tracked_delete marks which may not be preferrable
1328
+ * if used for block nodes (since we possibly want to show the individual changed nodes).
1329
+ * Merging is done based on the userID, operation type and status.
1330
+ * @param pos
1331
+ * @param doc
1332
+ * @param newTr
1333
+ * @param schema
1334
+ */
1335
+ function mergeTrackedMarks(pos, doc, newTr, schema) {
1336
+ const resolved = doc.resolve(pos);
1337
+ const { nodeAfter, nodeBefore } = resolved;
1338
+ const leftMark = nodeBefore === null || nodeBefore === void 0 ? void 0 : nodeBefore.marks.filter((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete)[0];
1339
+ const rightMark = nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.marks.filter((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete)[0];
1340
+ if (!nodeAfter || !nodeBefore || !leftMark || !rightMark || leftMark.type !== rightMark.type) {
1341
+ return;
1342
+ }
1343
+ const leftDataTracked = leftMark.attrs.dataTracked;
1344
+ const rightDataTracked = rightMark.attrs.dataTracked;
1345
+ if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
1346
+ return;
1347
+ }
1348
+ const isLeftOlder = (leftDataTracked.createdAt || 0) < (rightDataTracked.createdAt || 0);
1349
+ const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
1350
+ const dataTracked = {
1351
+ ...ancestorAttrs,
1352
+ updatedAt: Date.now(),
1353
+ };
1354
+ const fromStartOfMark = pos - nodeBefore.nodeSize;
1355
+ const toEndOfMark = pos + nodeAfter.nodeSize;
1356
+ newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create({ ...leftMark.attrs, dataTracked }));
1357
+ }
1358
+
1364
1359
  function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1365
1360
  const mapping = new prosemirrorTransform.Mapping();
1366
1361
  const deleteAttrs = createNewDeleteAttrs(emptyAttrs);
@@ -1448,6 +1443,19 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1448
1443
  return [mapping, selectionPos];
1449
1444
  }
1450
1445
 
1446
+ /**
1447
+ * Matches deleted-text recursively to inserted text
1448
+ *
1449
+ * This is needed as text containing various marks is split into multiple parts even though it's
1450
+ * continously deleted. Therefore, we need to find the next part if there is any and keep going until
1451
+ * we've reached the end of the deleted text or inserted content.
1452
+ * @param adjDeleted
1453
+ * @param insNode
1454
+ * @param offset
1455
+ * @param matchedDeleted
1456
+ * @param deleted
1457
+ * @returns
1458
+ */
1451
1459
  function matchText(adjDeleted, insNode, offset, matchedDeleted, deleted) {
1452
1460
  const { pos, from, to, node: delNode } = adjDeleted;
1453
1461
  let j = offset, d = from - pos, maxSteps = to - Math.max(pos, from);
@@ -1480,15 +1488,26 @@ function matchText(adjDeleted, insNode, offset, matchedDeleted, deleted) {
1480
1488
  }
1481
1489
  return [matchedDeleted, deleted];
1482
1490
  }
1491
+ /**
1492
+ * Matches deleted to inserted content and returns the first pos they differ and the updated
1493
+ * ChangeStep list.
1494
+ *
1495
+ * Based on https://github.com/ProseMirror/prosemirror-model/blob/master/src/diff.ts
1496
+ * @param matchedDeleted
1497
+ * @param deleted
1498
+ * @param inserted
1499
+ * @returns
1500
+ */
1483
1501
  function matchInserted(matchedDeleted, deleted, inserted) {
1484
1502
  var _a;
1485
1503
  let matched = [matchedDeleted, deleted];
1486
1504
  for (let i = 0;; i += 1) {
1487
- if (inserted.childCount === i)
1505
+ if (inserted.childCount === i) {
1488
1506
  return matched;
1507
+ }
1489
1508
  const insNode = inserted.child(i);
1490
1509
  // @ts-ignore
1491
- let adjDeleted = matched[1].find((d) => (d.type === 'delete-text' && Math.max(d.pos, d.from) === matched[0]) ||
1510
+ const adjDeleted = matched[1].find((d) => (d.type === 'delete-text' && Math.max(d.pos, d.from) === matched[0]) ||
1492
1511
  (d.type === 'delete-node' && d.pos === matched[0]));
1493
1512
  if (insNode.type !== ((_a = adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node) === null || _a === void 0 ? void 0 : _a.type)) {
1494
1513
  return matched;
@@ -1533,16 +1552,13 @@ function matchInserted(matchedDeleted, deleted, inserted) {
1533
1552
  /**
1534
1553
  * Cuts a fragment similar to Fragment.cut but also removes the parent node.
1535
1554
  *
1536
- * @TODO there is however, some silly calculation mistake so that I need to use matched - deleted + 1 > 0
1537
- * inside it to check whether to actually cut a text node. The offset might be cascading, therefore it should
1538
- * be fixed at some point.
1539
1555
  * @param matched
1540
1556
  * @param deleted
1541
1557
  * @param content
1542
1558
  * @returns
1543
1559
  */
1544
1560
  function cutFragment(matched, deleted, content) {
1545
- let newContent = [];
1561
+ const newContent = [];
1546
1562
  for (let i = 0; matched <= deleted && i < content.childCount; i += 1) {
1547
1563
  const child = content.child(i);
1548
1564
  if (!child.isText && child.content.size > 0) {
@@ -1565,7 +1581,7 @@ function cutFragment(matched, deleted, content) {
1565
1581
  }
1566
1582
  return [matched, prosemirrorModel.Fragment.fromArray(newContent)];
1567
1583
  }
1568
- function diffChangeSteps(deleted, inserted, newTr, schema) {
1584
+ function diffChangeSteps(deleted, inserted) {
1569
1585
  const updated = [];
1570
1586
  let updatedDeleted = [...deleted];
1571
1587
  inserted.forEach((ins) => {
@@ -1670,7 +1686,7 @@ function trackTransaction(tr, oldState, newTr, authorID) {
1670
1686
  // deleted and merged really...
1671
1687
  const deleted = steps.filter((s) => s.type !== 'insert-slice');
1672
1688
  const inserted = steps.filter((s) => s.type === 'insert-slice');
1673
- steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
1689
+ steps = diffChangeSteps(deleted, inserted);
1674
1690
  log.info('DIFFED STEPS: ', steps);
1675
1691
  const [mapping, selectionPos] = processChangeSteps(steps, startPos || tr.selection.head, // Incase startPos is it's default value 0, use the old selection head
1676
1692
  newTr, emptyAttrs, oldState.schema);
@@ -1688,7 +1704,7 @@ function trackTransaction(tr, oldState, newTr, authorID) {
1688
1704
  const deleted = steps.filter((s) => s.type !== 'insert-slice');
1689
1705
  const inserted = steps.filter((s) => s.type === 'insert-slice');
1690
1706
  log.info('INSERT STEPS: ', inserted);
1691
- steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
1707
+ steps = diffChangeSteps(deleted, inserted);
1692
1708
  log.info('DIFFED STEPS: ', steps);
1693
1709
  processChangeSteps(steps, tr.selection.from, newTr, emptyAttrs, oldState.schema);
1694
1710
  }
@@ -1778,15 +1794,14 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1778
1794
  return {
1779
1795
  ...pluginState,
1780
1796
  status: setStatus,
1781
- changeSet: findChanges(newState),
1797
+ changeSet: setStatus === exports.TrackChangesStatus.disabled ? new ChangeSet() : findChanges(newState),
1782
1798
  };
1783
1799
  }
1784
1800
  else if (pluginState.status === exports.TrackChangesStatus.disabled) {
1785
1801
  return { ...pluginState, changeSet: new ChangeSet() };
1786
1802
  }
1787
1803
  let { changeSet, ...rest } = pluginState;
1788
- const updatedChangeIds = getAction(tr, TrackChangesAction.updateChanges);
1789
- if (updatedChangeIds || getAction(tr, TrackChangesAction.refreshChanges)) {
1804
+ if (getAction(tr, TrackChangesAction.refreshChanges)) {
1790
1805
  changeSet = findChanges(newState);
1791
1806
  }
1792
1807
  return {
@@ -1830,7 +1845,6 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1830
1845
  createdTr = updateChangeAttrs(createdTr, change, { ...change.dataTracked, status, reviewedByID: userID }, oldState.schema);
1831
1846
  }
1832
1847
  });
1833
- setAction(createdTr, TrackChangesAction.updateChanges, ids);
1834
1848
  }
1835
1849
  else if (getAction(tr, TrackChangesAction.applyAndRemoveChanges)) {
1836
1850
  const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.bothNodeChanges);
@@ -1909,24 +1923,8 @@ const applyAndRemoveChanges = () => (state, dispatch) => {
1909
1923
  * Runs `findChanges` to iterate over the document to collect changes into a new ChangeSet.
1910
1924
  */
1911
1925
  const refreshChanges = () => (state, dispatch) => {
1912
- dispatch && dispatch(setAction(state.tr, TrackChangesAction.updateChanges, []));
1926
+ dispatch && dispatch(setAction(state.tr, TrackChangesAction.refreshChanges, true));
1913
1927
  return true;
1914
- };
1915
- /**
1916
- * Adds track attributes for a block node. For testing puroses
1917
- */
1918
- const setParagraphTestAttribute = (val = 'changed') => (state, dispatch) => {
1919
- var _a;
1920
- const cursor = state.selection.head;
1921
- const blockNodePos = state.doc.resolve(cursor).start(1) - 1;
1922
- if (((_a = state.doc.resolve(blockNodePos).nodeAfter) === null || _a === void 0 ? void 0 : _a.type) === state.schema.nodes.paragraph &&
1923
- dispatch) {
1924
- dispatch(state.tr.setNodeMarkup(blockNodePos, undefined, {
1925
- testAttribute: val,
1926
- }));
1927
- return true;
1928
- }
1929
- return false;
1930
1928
  };
1931
1929
 
1932
1930
  var commands = /*#__PURE__*/Object.freeze({
@@ -1935,8 +1933,7 @@ var commands = /*#__PURE__*/Object.freeze({
1935
1933
  setChangeStatuses: setChangeStatuses,
1936
1934
  setUserID: setUserID,
1937
1935
  applyAndRemoveChanges: applyAndRemoveChanges,
1938
- refreshChanges: refreshChanges,
1939
- setParagraphTestAttribute: setParagraphTestAttribute
1936
+ refreshChanges: refreshChanges
1940
1937
  });
1941
1938
 
1942
1939
  exports.ChangeSet = ChangeSet;
package/dist/index.js CHANGED
@@ -9,7 +9,6 @@ var TrackChangesAction;
9
9
  TrackChangesAction["setUserID"] = "track-changes-set-user-id";
10
10
  TrackChangesAction["setPluginStatus"] = "track-changes-set-track-status";
11
11
  TrackChangesAction["setChangeStatuses"] = "track-changes-set-change-statuses";
12
- TrackChangesAction["updateChanges"] = "track-changes-update-changes";
13
12
  TrackChangesAction["refreshChanges"] = "track-changes-refresh-changes";
14
13
  TrackChangesAction["applyAndRemoveChanges"] = "track-changes-apply-remove-changes";
15
14
  })(TrackChangesAction || (TrackChangesAction = {}));
@@ -365,7 +364,7 @@ function getTextNodeTrackedMarkData(node, schema) {
365
364
  return marksTrackedData[0] || undefined;
366
365
  }
367
366
  function getBlockInlineTrackedData(node) {
368
- let { dataTracked } = node.attrs;
367
+ const { dataTracked } = node.attrs;
369
368
  if (dataTracked && !Array.isArray(dataTracked)) {
370
369
  return [dataTracked];
371
370
  }
@@ -627,9 +626,9 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mappi
627
626
  /**
628
627
  * Finds all changes (basically text marks or node attributes) from document
629
628
  *
630
- * This could be possibly made more efficient by only iterating the sections of doc
631
- * where changes have been applied. This could attempted with eg findDiffStart
632
- * but it might be less robust than just using doc.descendants
629
+ * This could be possibly made more efficient by only iterating the sections of doc where changes have
630
+ * been applied. This could attempted with eg findDiffStart but it might be less robust than just using
631
+ * doc.descendants
633
632
  * @param state
634
633
  * @returns
635
634
  */
@@ -1013,7 +1012,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1013
1012
  // @TODO ATM 20.7.2022 there doesn't seem to be tests that capture this.
1014
1013
  const wasWithinGap = gap &&
1015
1014
  ((!node.isText && pos >= gap.start) ||
1016
- (node.isText && pos <= gap.start && nodeEnd >= gap.start));
1015
+ (node.isText && pos >= gap.start && nodeEnd <= gap.end));
1017
1016
  // nodeEnd > offsetFrom -> delete touches this node
1018
1017
  // eg (del 6 10) <p 5>|<t 6>cdf</t 9></p 10>| -> <p> nodeEnd 10 > from 6
1019
1018
  if (nodeEnd > from && !wasWithinGap) {
@@ -1114,41 +1113,6 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1114
1113
  };
1115
1114
  }
1116
1115
 
1117
- /**
1118
- * Merges tracked marks between text nodes at a position
1119
- *
1120
- * Will work for any nodes that use tracked_insert or tracked_delete marks which may not be preferrable
1121
- * if used for block nodes (since we possibly want to show the individual changed nodes).
1122
- * Merging is done based on the userID, operation type and status.
1123
- * @param pos
1124
- * @param doc
1125
- * @param newTr
1126
- * @param schema
1127
- */
1128
- function mergeTrackedMarks(pos, doc, newTr, schema) {
1129
- const resolved = doc.resolve(pos);
1130
- const { nodeAfter, nodeBefore } = resolved;
1131
- const leftMark = nodeBefore === null || nodeBefore === void 0 ? void 0 : nodeBefore.marks.filter((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete)[0];
1132
- const rightMark = nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.marks.filter((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete)[0];
1133
- if (!nodeAfter || !nodeBefore || !leftMark || !rightMark || leftMark.type !== rightMark.type) {
1134
- return;
1135
- }
1136
- const leftDataTracked = leftMark.attrs.dataTracked;
1137
- const rightDataTracked = rightMark.attrs.dataTracked;
1138
- if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
1139
- return;
1140
- }
1141
- const isLeftOlder = (leftDataTracked.createdAt || 0) < (rightDataTracked.createdAt || 0);
1142
- const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
1143
- const dataTracked = {
1144
- ...ancestorAttrs,
1145
- updatedAt: Date.now(),
1146
- };
1147
- const fromStartOfMark = pos - nodeBefore.nodeSize;
1148
- const toEndOfMark = pos + nodeAfter.nodeSize;
1149
- newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create({ ...leftMark.attrs, dataTracked }));
1150
- }
1151
-
1152
1116
  /*!
1153
1117
  * © 2021 Atypon Systems LLC
1154
1118
  *
@@ -1201,7 +1165,7 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1201
1165
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1202
1166
  // eg partial nodes in an open-ended slice
1203
1167
  const { sliceWasSplit, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1204
- let steps = deleteSteps;
1168
+ const steps = deleteSteps;
1205
1169
  log.info('TR: new steps after applying delete', [...newTr.steps]);
1206
1170
  log.info('DELETE STEPS: ', deleteSteps);
1207
1171
  // We only want to insert when there something inside the gap (actually would this be always true?)
@@ -1226,11 +1190,6 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1226
1190
  sliceWasSplit,
1227
1191
  });
1228
1192
  }
1229
- else {
1230
- // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1231
- mergeTrackedMarks(gapFrom, newTr.doc, newTr, oldState.schema);
1232
- mergeTrackedMarks(gapTo, newTr.doc, newTr, oldState.schema);
1233
- }
1234
1193
  return steps;
1235
1194
  }
1236
1195
 
@@ -1269,7 +1228,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1269
1228
  changeSteps.push(...deleteSteps);
1270
1229
  log.info('TR: steps after applying delete', [...newTr.steps]);
1271
1230
  log.info('DELETE STEPS: ', changeSteps);
1272
- const adjustedInsertPos = toA; // deleteMap.map(toA)
1231
+ const adjustedInsertPos = toA;
1273
1232
  if (newSliceContent.size > 0) {
1274
1233
  log.info('newSliceContent', newSliceContent);
1275
1234
  // Since deleteAndMergeSplitBlockNodes modified the slice to not to contain any merged nodes,
@@ -1286,7 +1245,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1286
1245
  }
1287
1246
  else {
1288
1247
  // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1289
- mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema);
1248
+ // mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema)
1290
1249
  selectionPos = fromA;
1291
1250
  }
1292
1251
  });
@@ -1320,6 +1279,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1320
1279
  * @param deleteAttrs
1321
1280
  * @param from
1322
1281
  * @param to
1282
+ * @returns position at the end of the possibly deleted text
1323
1283
  */
1324
1284
  function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1325
1285
  const start = from ? Math.max(pos, from) : pos;
@@ -1353,6 +1313,41 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1353
1313
  }
1354
1314
  }
1355
1315
 
1316
+ /**
1317
+ * Merges tracked marks between text nodes at a position
1318
+ *
1319
+ * Will work for any nodes that use tracked_insert or tracked_delete marks which may not be preferrable
1320
+ * if used for block nodes (since we possibly want to show the individual changed nodes).
1321
+ * Merging is done based on the userID, operation type and status.
1322
+ * @param pos
1323
+ * @param doc
1324
+ * @param newTr
1325
+ * @param schema
1326
+ */
1327
+ function mergeTrackedMarks(pos, doc, newTr, schema) {
1328
+ const resolved = doc.resolve(pos);
1329
+ const { nodeAfter, nodeBefore } = resolved;
1330
+ const leftMark = nodeBefore === null || nodeBefore === void 0 ? void 0 : nodeBefore.marks.filter((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete)[0];
1331
+ const rightMark = nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.marks.filter((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete)[0];
1332
+ if (!nodeAfter || !nodeBefore || !leftMark || !rightMark || leftMark.type !== rightMark.type) {
1333
+ return;
1334
+ }
1335
+ const leftDataTracked = leftMark.attrs.dataTracked;
1336
+ const rightDataTracked = rightMark.attrs.dataTracked;
1337
+ if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
1338
+ return;
1339
+ }
1340
+ const isLeftOlder = (leftDataTracked.createdAt || 0) < (rightDataTracked.createdAt || 0);
1341
+ const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
1342
+ const dataTracked = {
1343
+ ...ancestorAttrs,
1344
+ updatedAt: Date.now(),
1345
+ };
1346
+ const fromStartOfMark = pos - nodeBefore.nodeSize;
1347
+ const toEndOfMark = pos + nodeAfter.nodeSize;
1348
+ newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create({ ...leftMark.attrs, dataTracked }));
1349
+ }
1350
+
1356
1351
  function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1357
1352
  const mapping = new Mapping();
1358
1353
  const deleteAttrs = createNewDeleteAttrs(emptyAttrs);
@@ -1440,6 +1435,19 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1440
1435
  return [mapping, selectionPos];
1441
1436
  }
1442
1437
 
1438
+ /**
1439
+ * Matches deleted-text recursively to inserted text
1440
+ *
1441
+ * This is needed as text containing various marks is split into multiple parts even though it's
1442
+ * continously deleted. Therefore, we need to find the next part if there is any and keep going until
1443
+ * we've reached the end of the deleted text or inserted content.
1444
+ * @param adjDeleted
1445
+ * @param insNode
1446
+ * @param offset
1447
+ * @param matchedDeleted
1448
+ * @param deleted
1449
+ * @returns
1450
+ */
1443
1451
  function matchText(adjDeleted, insNode, offset, matchedDeleted, deleted) {
1444
1452
  const { pos, from, to, node: delNode } = adjDeleted;
1445
1453
  let j = offset, d = from - pos, maxSteps = to - Math.max(pos, from);
@@ -1472,15 +1480,26 @@ function matchText(adjDeleted, insNode, offset, matchedDeleted, deleted) {
1472
1480
  }
1473
1481
  return [matchedDeleted, deleted];
1474
1482
  }
1483
+ /**
1484
+ * Matches deleted to inserted content and returns the first pos they differ and the updated
1485
+ * ChangeStep list.
1486
+ *
1487
+ * Based on https://github.com/ProseMirror/prosemirror-model/blob/master/src/diff.ts
1488
+ * @param matchedDeleted
1489
+ * @param deleted
1490
+ * @param inserted
1491
+ * @returns
1492
+ */
1475
1493
  function matchInserted(matchedDeleted, deleted, inserted) {
1476
1494
  var _a;
1477
1495
  let matched = [matchedDeleted, deleted];
1478
1496
  for (let i = 0;; i += 1) {
1479
- if (inserted.childCount === i)
1497
+ if (inserted.childCount === i) {
1480
1498
  return matched;
1499
+ }
1481
1500
  const insNode = inserted.child(i);
1482
1501
  // @ts-ignore
1483
- let adjDeleted = matched[1].find((d) => (d.type === 'delete-text' && Math.max(d.pos, d.from) === matched[0]) ||
1502
+ const adjDeleted = matched[1].find((d) => (d.type === 'delete-text' && Math.max(d.pos, d.from) === matched[0]) ||
1484
1503
  (d.type === 'delete-node' && d.pos === matched[0]));
1485
1504
  if (insNode.type !== ((_a = adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node) === null || _a === void 0 ? void 0 : _a.type)) {
1486
1505
  return matched;
@@ -1525,16 +1544,13 @@ function matchInserted(matchedDeleted, deleted, inserted) {
1525
1544
  /**
1526
1545
  * Cuts a fragment similar to Fragment.cut but also removes the parent node.
1527
1546
  *
1528
- * @TODO there is however, some silly calculation mistake so that I need to use matched - deleted + 1 > 0
1529
- * inside it to check whether to actually cut a text node. The offset might be cascading, therefore it should
1530
- * be fixed at some point.
1531
1547
  * @param matched
1532
1548
  * @param deleted
1533
1549
  * @param content
1534
1550
  * @returns
1535
1551
  */
1536
1552
  function cutFragment(matched, deleted, content) {
1537
- let newContent = [];
1553
+ const newContent = [];
1538
1554
  for (let i = 0; matched <= deleted && i < content.childCount; i += 1) {
1539
1555
  const child = content.child(i);
1540
1556
  if (!child.isText && child.content.size > 0) {
@@ -1557,7 +1573,7 @@ function cutFragment(matched, deleted, content) {
1557
1573
  }
1558
1574
  return [matched, Fragment.fromArray(newContent)];
1559
1575
  }
1560
- function diffChangeSteps(deleted, inserted, newTr, schema) {
1576
+ function diffChangeSteps(deleted, inserted) {
1561
1577
  const updated = [];
1562
1578
  let updatedDeleted = [...deleted];
1563
1579
  inserted.forEach((ins) => {
@@ -1662,7 +1678,7 @@ function trackTransaction(tr, oldState, newTr, authorID) {
1662
1678
  // deleted and merged really...
1663
1679
  const deleted = steps.filter((s) => s.type !== 'insert-slice');
1664
1680
  const inserted = steps.filter((s) => s.type === 'insert-slice');
1665
- steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
1681
+ steps = diffChangeSteps(deleted, inserted);
1666
1682
  log.info('DIFFED STEPS: ', steps);
1667
1683
  const [mapping, selectionPos] = processChangeSteps(steps, startPos || tr.selection.head, // Incase startPos is it's default value 0, use the old selection head
1668
1684
  newTr, emptyAttrs, oldState.schema);
@@ -1680,7 +1696,7 @@ function trackTransaction(tr, oldState, newTr, authorID) {
1680
1696
  const deleted = steps.filter((s) => s.type !== 'insert-slice');
1681
1697
  const inserted = steps.filter((s) => s.type === 'insert-slice');
1682
1698
  log.info('INSERT STEPS: ', inserted);
1683
- steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
1699
+ steps = diffChangeSteps(deleted, inserted);
1684
1700
  log.info('DIFFED STEPS: ', steps);
1685
1701
  processChangeSteps(steps, tr.selection.from, newTr, emptyAttrs, oldState.schema);
1686
1702
  }
@@ -1770,15 +1786,14 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1770
1786
  return {
1771
1787
  ...pluginState,
1772
1788
  status: setStatus,
1773
- changeSet: findChanges(newState),
1789
+ changeSet: setStatus === TrackChangesStatus.disabled ? new ChangeSet() : findChanges(newState),
1774
1790
  };
1775
1791
  }
1776
1792
  else if (pluginState.status === TrackChangesStatus.disabled) {
1777
1793
  return { ...pluginState, changeSet: new ChangeSet() };
1778
1794
  }
1779
1795
  let { changeSet, ...rest } = pluginState;
1780
- const updatedChangeIds = getAction(tr, TrackChangesAction.updateChanges);
1781
- if (updatedChangeIds || getAction(tr, TrackChangesAction.refreshChanges)) {
1796
+ if (getAction(tr, TrackChangesAction.refreshChanges)) {
1782
1797
  changeSet = findChanges(newState);
1783
1798
  }
1784
1799
  return {
@@ -1822,7 +1837,6 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1822
1837
  createdTr = updateChangeAttrs(createdTr, change, { ...change.dataTracked, status, reviewedByID: userID }, oldState.schema);
1823
1838
  }
1824
1839
  });
1825
- setAction(createdTr, TrackChangesAction.updateChanges, ids);
1826
1840
  }
1827
1841
  else if (getAction(tr, TrackChangesAction.applyAndRemoveChanges)) {
1828
1842
  const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.bothNodeChanges);
@@ -1901,24 +1915,8 @@ const applyAndRemoveChanges = () => (state, dispatch) => {
1901
1915
  * Runs `findChanges` to iterate over the document to collect changes into a new ChangeSet.
1902
1916
  */
1903
1917
  const refreshChanges = () => (state, dispatch) => {
1904
- dispatch && dispatch(setAction(state.tr, TrackChangesAction.updateChanges, []));
1918
+ dispatch && dispatch(setAction(state.tr, TrackChangesAction.refreshChanges, true));
1905
1919
  return true;
1906
- };
1907
- /**
1908
- * Adds track attributes for a block node. For testing puroses
1909
- */
1910
- const setParagraphTestAttribute = (val = 'changed') => (state, dispatch) => {
1911
- var _a;
1912
- const cursor = state.selection.head;
1913
- const blockNodePos = state.doc.resolve(cursor).start(1) - 1;
1914
- if (((_a = state.doc.resolve(blockNodePos).nodeAfter) === null || _a === void 0 ? void 0 : _a.type) === state.schema.nodes.paragraph &&
1915
- dispatch) {
1916
- dispatch(state.tr.setNodeMarkup(blockNodePos, undefined, {
1917
- testAttribute: val,
1918
- }));
1919
- return true;
1920
- }
1921
- return false;
1922
1920
  };
1923
1921
 
1924
1922
  var commands = /*#__PURE__*/Object.freeze({
@@ -1927,8 +1925,7 @@ var commands = /*#__PURE__*/Object.freeze({
1927
1925
  setChangeStatuses: setChangeStatuses,
1928
1926
  setUserID: setUserID,
1929
1927
  applyAndRemoveChanges: applyAndRemoveChanges,
1930
- refreshChanges: refreshChanges,
1931
- setParagraphTestAttribute: setParagraphTestAttribute
1928
+ refreshChanges: refreshChanges
1932
1929
  });
1933
1930
 
1934
1931
  export { CHANGE_OPERATION, CHANGE_STATUS, ChangeSet, TrackChangesStatus, enableDebug, skipTracking, trackChangesPlugin, trackChangesPluginKey, commands as trackCommands };
@@ -28,5 +28,6 @@ import { NewDeleteAttrs } from '../types/track';
28
28
  * @param deleteAttrs
29
29
  * @param from
30
30
  * @param to
31
+ * @returns position at the end of the possibly deleted text
31
32
  */
32
33
  export declare function deleteTextIfInserted(node: PMNode, pos: number, newTr: Transaction, schema: Schema, deleteAttrs: NewDeleteAttrs, from?: number, to?: number): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manuscripts/track-changes-plugin",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
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",
@@ -21,28 +21,28 @@
21
21
  "access": "public"
22
22
  },
23
23
  "devDependencies": {
24
- "@manuscripts/manuscript-transform": "^0.49.4",
25
- "@rollup/plugin-commonjs": "^22.0.0",
24
+ "@manuscripts/manuscript-transform": "^0.54.0",
25
+ "@rollup/plugin-commonjs": "^22.0.2",
26
26
  "@types/debug": "^4.1.7",
27
27
  "@types/jest": "27.5.1",
28
- "@types/node": "^17.0.35",
28
+ "@types/node": "^18.7.18",
29
29
  "jest": "27.5.1",
30
30
  "jest-environment-jsdom": "27.5.1",
31
- "jsdom": "^19.0.0",
32
- "prosemirror-commands": "^1.3.0",
31
+ "jsdom": "^20.0.0",
32
+ "prosemirror-commands": "^1.3.1",
33
33
  "prosemirror-example-setup": "^1.2.1",
34
34
  "prosemirror-history": "^1.3.0",
35
35
  "prosemirror-keymap": "^1.2.0",
36
36
  "prosemirror-model": "^1.18.1",
37
- "prosemirror-schema-list": "^1.2.0",
37
+ "prosemirror-schema-list": "^1.2.2",
38
38
  "prosemirror-state": "^1.4.1",
39
- "prosemirror-transform": "^1.6.0",
40
- "prosemirror-view": "^1.27.0",
41
- "rollup": "^2.74.0",
42
- "rollup-plugin-typescript2": "^0.31.2",
39
+ "prosemirror-transform": "^1.7.0",
40
+ "prosemirror-view": "^1.28.1",
41
+ "rollup": "^2.79.0",
42
+ "rollup-plugin-typescript2": "^0.34.0",
43
43
  "ts-jest": "27.1.4",
44
44
  "tslib": "^2.4.0",
45
- "typescript": "^4.6.4"
45
+ "typescript": "^4.8.3"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "prosemirror-model": ">=1.14.0",