@manuscripts/track-changes-plugin 0.4.1 → 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/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 = {}));
@@ -224,6 +223,9 @@ class ChangeSet {
224
223
  get nodeAttrChanges() {
225
224
  return this.changes.filter((c) => c.type === 'node-attr-change');
226
225
  }
226
+ get bothNodeChanges() {
227
+ return this.changes.filter((c) => c.type === 'node-change' || c.type === 'node-attr-change');
228
+ }
227
229
  get isEmpty() {
228
230
  return __classPrivateFieldGet(this, _ChangeSet_changes, "f").length === 0;
229
231
  }
@@ -362,7 +364,7 @@ function getTextNodeTrackedMarkData(node, schema) {
362
364
  return marksTrackedData[0] || undefined;
363
365
  }
364
366
  function getBlockInlineTrackedData(node) {
365
- let { dataTracked } = node.attrs;
367
+ const { dataTracked } = node.attrs;
366
368
  if (dataTracked && !Array.isArray(dataTracked)) {
367
369
  return [dataTracked];
368
370
  }
@@ -537,11 +539,11 @@ function updateChangeAttrs(tr, change, trackedAttrs, schema) {
537
539
  // TODO add operation based on mark type if it's undefined?
538
540
  tr.addMark(change.from, change.to, oldMark.type.create({ ...oldMark.attrs, dataTracked: { ...oldTrackData, ...trackedAttrs } }));
539
541
  }
540
- else if (change.type === 'node-change' && !operation) {
542
+ else if ((change.type === 'node-change' || change.type === 'node-attr-change') && !operation) {
541
543
  // Very weird edge-case if this happens
542
544
  tr.setNodeMarkup(change.from, undefined, { ...node.attrs, dataTracked: null }, node.marks);
543
545
  }
544
- else if (change.type === 'node-change') {
546
+ else if (change.type === 'node-change' || change.type === 'node-attr-change') {
545
547
  const newDataTracked = (getBlockInlineTrackedData(node) || []).map((oldTrack) => {
546
548
  if (oldTrack.operation === operation) {
547
549
  return { ...oldTrack, ...trackedAttrs };
@@ -611,13 +613,11 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mappi
611
613
  }
612
614
  else if (ChangeSet.isNodeAttrChange(change) &&
613
615
  change.dataTracked.status === CHANGE_STATUS.accepted) {
614
- const attrs = { ...node.attrs, dataTracked: null };
615
- tr.setNodeMarkup(from, undefined, attrs, node.marks);
616
+ tr.setNodeMarkup(from, undefined, { ...node.attrs, dataTracked: null }, node.marks);
616
617
  }
617
618
  else if (ChangeSet.isNodeAttrChange(change) &&
618
619
  change.dataTracked.status === CHANGE_STATUS.rejected) {
619
- const attrs = { ...change.oldAttrs, dataTracked: null };
620
- tr.setNodeMarkup(from, undefined, attrs, node.marks);
620
+ tr.setNodeMarkup(from, undefined, { ...change.oldAttrs, dataTracked: null }, node.marks);
621
621
  }
622
622
  });
623
623
  return deleteMap;
@@ -626,9 +626,9 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mappi
626
626
  /**
627
627
  * Finds all changes (basically text marks or node attributes) from document
628
628
  *
629
- * This could be possibly made more efficient by only iterating the sections of doc
630
- * where changes have been applied. This could attempted with eg findDiffStart
631
- * 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
632
632
  * @param state
633
633
  * @returns
634
634
  */
@@ -937,10 +937,12 @@ function createNewDeleteAttrs(attrs) {
937
937
  };
938
938
  }
939
939
  function createNewUpdateAttrs(attrs, oldAttrs) {
940
+ // Omit dataTracked
941
+ const { dataTracked, ...restAttrs } = oldAttrs;
940
942
  return {
941
943
  ...attrs,
942
944
  operation: CHANGE_OPERATION.set_node_attributes,
943
- oldAttrs,
945
+ oldAttrs: restAttrs,
944
946
  };
945
947
  }
946
948
 
@@ -1010,7 +1012,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1010
1012
  // @TODO ATM 20.7.2022 there doesn't seem to be tests that capture this.
1011
1013
  const wasWithinGap = gap &&
1012
1014
  ((!node.isText && pos >= gap.start) ||
1013
- (node.isText && pos <= gap.start && nodeEnd >= gap.start));
1015
+ (node.isText && pos >= gap.start && nodeEnd <= gap.end));
1014
1016
  // nodeEnd > offsetFrom -> delete touches this node
1015
1017
  // eg (del 6 10) <p 5>|<t 6>cdf</t 9></p 10>| -> <p> nodeEnd 10 > from 6
1016
1018
  if (nodeEnd > from && !wasWithinGap) {
@@ -1111,41 +1113,6 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1111
1113
  };
1112
1114
  }
1113
1115
 
1114
- /**
1115
- * Merges tracked marks between text nodes at a position
1116
- *
1117
- * Will work for any nodes that use tracked_insert or tracked_delete marks which may not be preferrable
1118
- * if used for block nodes (since we possibly want to show the individual changed nodes).
1119
- * Merging is done based on the userID, operation type and status.
1120
- * @param pos
1121
- * @param doc
1122
- * @param newTr
1123
- * @param schema
1124
- */
1125
- function mergeTrackedMarks(pos, doc, newTr, schema) {
1126
- const resolved = doc.resolve(pos);
1127
- const { nodeAfter, nodeBefore } = resolved;
1128
- 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];
1129
- 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];
1130
- if (!nodeAfter || !nodeBefore || !leftMark || !rightMark || leftMark.type !== rightMark.type) {
1131
- return;
1132
- }
1133
- const leftDataTracked = leftMark.attrs.dataTracked;
1134
- const rightDataTracked = rightMark.attrs.dataTracked;
1135
- if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
1136
- return;
1137
- }
1138
- const isLeftOlder = (leftDataTracked.createdAt || 0) < (rightDataTracked.createdAt || 0);
1139
- const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
1140
- const dataTracked = {
1141
- ...ancestorAttrs,
1142
- updatedAt: Date.now(),
1143
- };
1144
- const fromStartOfMark = pos - nodeBefore.nodeSize;
1145
- const toEndOfMark = pos + nodeAfter.nodeSize;
1146
- newTr.addMark(fromStartOfMark, toEndOfMark, leftMark.type.create({ ...leftMark.attrs, dataTracked }));
1147
- }
1148
-
1149
1116
  /*!
1150
1117
  * © 2021 Atypon Systems LLC
1151
1118
  *
@@ -1198,7 +1165,7 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1198
1165
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1199
1166
  // eg partial nodes in an open-ended slice
1200
1167
  const { sliceWasSplit, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1201
- let steps = deleteSteps;
1168
+ const steps = deleteSteps;
1202
1169
  log.info('TR: new steps after applying delete', [...newTr.steps]);
1203
1170
  log.info('DELETE STEPS: ', deleteSteps);
1204
1171
  // We only want to insert when there something inside the gap (actually would this be always true?)
@@ -1223,11 +1190,6 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1223
1190
  sliceWasSplit,
1224
1191
  });
1225
1192
  }
1226
- else {
1227
- // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1228
- mergeTrackedMarks(gapFrom, newTr.doc, newTr, oldState.schema);
1229
- mergeTrackedMarks(gapTo, newTr.doc, newTr, oldState.schema);
1230
- }
1231
1193
  return steps;
1232
1194
  }
1233
1195
 
@@ -1266,7 +1228,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1266
1228
  changeSteps.push(...deleteSteps);
1267
1229
  log.info('TR: steps after applying delete', [...newTr.steps]);
1268
1230
  log.info('DELETE STEPS: ', changeSteps);
1269
- const adjustedInsertPos = toA; // deleteMap.map(toA)
1231
+ const adjustedInsertPos = toA;
1270
1232
  if (newSliceContent.size > 0) {
1271
1233
  log.info('newSliceContent', newSliceContent);
1272
1234
  // Since deleteAndMergeSplitBlockNodes modified the slice to not to contain any merged nodes,
@@ -1283,7 +1245,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1283
1245
  }
1284
1246
  else {
1285
1247
  // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1286
- mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema);
1248
+ // mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema)
1287
1249
  selectionPos = fromA;
1288
1250
  }
1289
1251
  });
@@ -1317,6 +1279,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1317
1279
  * @param deleteAttrs
1318
1280
  * @param from
1319
1281
  * @param to
1282
+ * @returns position at the end of the possibly deleted text
1320
1283
  */
1321
1284
  function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1322
1285
  const start = from ? Math.max(pos, from) : pos;
@@ -1350,6 +1313,41 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1350
1313
  }
1351
1314
  }
1352
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
+
1353
1351
  function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1354
1352
  const mapping = new Mapping();
1355
1353
  const deleteAttrs = createNewDeleteAttrs(emptyAttrs);
@@ -1411,22 +1409,18 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1411
1409
  else if (c.type === 'update-node-attrs') {
1412
1410
  const oldDataTracked = getBlockInlineTrackedData(c.node) || [];
1413
1411
  const oldUpdate = oldDataTracked.find((d) => d.operation === CHANGE_OPERATION.set_node_attributes);
1414
- let newDataTracked = oldDataTracked;
1415
- if (oldUpdate) {
1416
- newDataTracked = [
1417
- ...oldDataTracked.filter((d) => d === oldUpdate),
1418
- {
1419
- ...oldUpdate,
1420
- updatedAt: emptyAttrs.updatedAt,
1421
- },
1422
- ];
1423
- }
1424
- else if (oldDataTracked.length === 0 ||
1425
- oldDataTracked.find((d) => d.operation === CHANGE_OPERATION.delete)) {
1426
- newDataTracked = [
1427
- ...oldDataTracked,
1428
- addTrackIdIfDoesntExist(createNewUpdateAttrs(emptyAttrs, c.node.attrs)),
1429
- ];
1412
+ const { dataTracked, ...oldAttrs } = (oldUpdate === null || oldUpdate === void 0 ? void 0 : oldUpdate.oldAttrs) || c.node.attrs;
1413
+ const newDataTracked = [...oldDataTracked.filter((d) => !oldUpdate || d.id !== oldUpdate.id)];
1414
+ const newUpdate = oldUpdate
1415
+ ? {
1416
+ ...oldUpdate,
1417
+ updatedAt: emptyAttrs.updatedAt,
1418
+ }
1419
+ : addTrackIdIfDoesntExist(createNewUpdateAttrs(emptyAttrs, c.node.attrs));
1420
+ // Dont add update changes if there exists already an insert change for this node
1421
+ if (JSON.stringify(oldAttrs) !== JSON.stringify(c.newAttrs) &&
1422
+ !oldDataTracked.find((d) => d.operation === CHANGE_OPERATION.insert)) {
1423
+ newDataTracked.push(newUpdate);
1430
1424
  }
1431
1425
  newTr.setNodeMarkup(mapping.map(c.pos), undefined, {
1432
1426
  ...c.newAttrs,
@@ -1441,61 +1435,77 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1441
1435
  return [mapping, selectionPos];
1442
1436
  }
1443
1437
 
1444
- /*!
1445
- * © 2021 Atypon Systems LLC
1446
- *
1447
- * Licensed under the Apache License, Version 2.0 (the "License");
1448
- * you may not use this file except in compliance with the License.
1449
- * You may obtain a copy of the License at
1438
+ /**
1439
+ * Matches deleted-text recursively to inserted text
1450
1440
  *
1451
- * http://www.apache.org/licenses/LICENSE-2.0
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
+ */
1451
+ function matchText(adjDeleted, insNode, offset, matchedDeleted, deleted) {
1452
+ const { pos, from, to, node: delNode } = adjDeleted;
1453
+ let j = offset, d = from - pos, maxSteps = to - Math.max(pos, from);
1454
+ // Match text inside the inserted text node to the deleted text node
1455
+ for (; maxSteps !== j && insNode.text[j] !== undefined && insNode.text[j] === delNode.text[d]; j += 1, d += 1) {
1456
+ matchedDeleted += 1;
1457
+ }
1458
+ // this is needed incase diffing tr.doc
1459
+ // deleted.push({
1460
+ // pos: pos,
1461
+ // type: 'update-node-attrs',
1462
+ // // Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
1463
+ // oldAttrs: adjDeleted.node.attrs || {},
1464
+ // newAttrs: child.attrs || {},
1465
+ // })
1466
+ deleted = deleted.filter((d) => d !== adjDeleted);
1467
+ if (maxSteps !== j) {
1468
+ deleted.push({
1469
+ pos,
1470
+ from: from + j - offset,
1471
+ to,
1472
+ type: 'delete-text',
1473
+ node: delNode,
1474
+ });
1475
+ return [matchedDeleted, deleted];
1476
+ }
1477
+ const nextTextDelete = deleted.find((d) => d.type === 'delete-text' && d.pos === to);
1478
+ if (nextTextDelete) {
1479
+ return matchText(nextTextDelete, insNode, j, matchedDeleted, deleted);
1480
+ }
1481
+ return [matchedDeleted, deleted];
1482
+ }
1483
+ /**
1484
+ * Matches deleted to inserted content and returns the first pos they differ and the updated
1485
+ * ChangeStep list.
1452
1486
  *
1453
- * Unless required by applicable law or agreed to in writing, software
1454
- * distributed under the License is distributed on an "AS IS" BASIS,
1455
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1456
- * See the License for the specific language governing permissions and
1457
- * limitations under the License.
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
1458
1492
  */
1459
- function matchInserted(matchedDeleted, deleted, inserted, newTr, schema) {
1493
+ function matchInserted(matchedDeleted, deleted, inserted) {
1460
1494
  var _a;
1461
1495
  let matched = [matchedDeleted, deleted];
1462
1496
  for (let i = 0;; i += 1) {
1463
- if (inserted.childCount === i)
1497
+ if (inserted.childCount === i) {
1464
1498
  return matched;
1499
+ }
1465
1500
  const insNode = inserted.child(i);
1466
1501
  // @ts-ignore
1467
- 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]) ||
1468
1503
  (d.type === 'delete-node' && d.pos === matched[0]));
1469
1504
  if (insNode.type !== ((_a = adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node) === null || _a === void 0 ? void 0 : _a.type)) {
1470
1505
  return matched;
1471
1506
  }
1472
1507
  else if (insNode.isText && (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node)) {
1473
- adjDeleted = adjDeleted;
1474
- const { pos, from, to, node: delNode } = adjDeleted;
1475
- let j = 0, d = from - pos, maxSteps = to - Math.max(pos, from);
1476
- // Match text inside the inserted text node to the deleted text node
1477
- for (; maxSteps !== j && insNode.text[j] !== undefined && insNode.text[j] === delNode.text[d]; j += 1, d += 1) {
1478
- matched[0] += 1;
1479
- }
1480
- // this is needed incase diffing tr.doc
1481
- // deleted.push({
1482
- // pos: pos,
1483
- // type: 'update-node-attrs',
1484
- // // Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
1485
- // oldAttrs: adjDeleted.node.attrs || {},
1486
- // newAttrs: child.attrs || {},
1487
- // })
1488
- matched = [matched[0], matched[1].filter((d) => d !== adjDeleted)];
1489
- if (maxSteps !== j) {
1490
- matched[1].push({
1491
- pos,
1492
- from: Math.max(pos, from) + j,
1493
- to,
1494
- type: 'delete-text',
1495
- node: delNode,
1496
- });
1497
- return matched;
1498
- }
1508
+ matched = matchText(adjDeleted, insNode, 0, matched[0], matched[1]);
1499
1509
  continue;
1500
1510
  }
1501
1511
  else if (insNode.content.size > 0 || (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node.content.size) > 0) {
@@ -1505,28 +1515,42 @@ function matchInserted(matchedDeleted, deleted, inserted, newTr, schema) {
1505
1515
  else {
1506
1516
  matched = [matched[0] + insNode.nodeSize, matched[1].filter((d) => d !== adjDeleted)];
1507
1517
  }
1518
+ // Omit dataTracked
1519
+ const { dataTracked, ...newAttrs } = insNode.attrs || {};
1508
1520
  matched[1].push({
1509
1521
  pos: adjDeleted.pos,
1510
1522
  type: 'update-node-attrs',
1511
1523
  node: adjDeleted.node,
1512
- // Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
1513
- newAttrs: insNode.attrs || {},
1524
+ newAttrs,
1514
1525
  });
1515
1526
  }
1516
- }
1527
+ }
1528
+
1529
+ /*!
1530
+ * © 2021 Atypon Systems LLC
1531
+ *
1532
+ * Licensed under the Apache License, Version 2.0 (the "License");
1533
+ * you may not use this file except in compliance with the License.
1534
+ * You may obtain a copy of the License at
1535
+ *
1536
+ * http://www.apache.org/licenses/LICENSE-2.0
1537
+ *
1538
+ * Unless required by applicable law or agreed to in writing, software
1539
+ * distributed under the License is distributed on an "AS IS" BASIS,
1540
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1541
+ * See the License for the specific language governing permissions and
1542
+ * limitations under the License.
1543
+ */
1517
1544
  /**
1518
1545
  * Cuts a fragment similar to Fragment.cut but also removes the parent node.
1519
1546
  *
1520
- * @TODO there is however, some silly calculation mistake so that I need to use matched - deleted + 1 > 0
1521
- * inside it to check whether to actually cut a text node. The offset might be cascading, therefore it should
1522
- * be fixed at some point.
1523
1547
  * @param matched
1524
1548
  * @param deleted
1525
1549
  * @param content
1526
1550
  * @returns
1527
1551
  */
1528
1552
  function cutFragment(matched, deleted, content) {
1529
- let newContent = [];
1553
+ const newContent = [];
1530
1554
  for (let i = 0; matched <= deleted && i < content.childCount; i += 1) {
1531
1555
  const child = content.child(i);
1532
1556
  if (!child.isText && child.content.size > 0) {
@@ -1535,8 +1559,8 @@ function cutFragment(matched, deleted, content) {
1535
1559
  newContent.push(...cut[1].content);
1536
1560
  }
1537
1561
  else if (child.isText && matched + child.nodeSize > deleted) {
1538
- if (matched - deleted + 1 > 0) {
1539
- newContent.push(child.cut(0, matched - deleted + 1));
1562
+ if (deleted - matched > 0) {
1563
+ newContent.push(child.cut(deleted - matched));
1540
1564
  }
1541
1565
  else {
1542
1566
  newContent.push(child);
@@ -1549,7 +1573,7 @@ function cutFragment(matched, deleted, content) {
1549
1573
  }
1550
1574
  return [matched, Fragment.fromArray(newContent)];
1551
1575
  }
1552
- function diffChangeSteps(deleted, inserted, newTr, schema) {
1576
+ function diffChangeSteps(deleted, inserted) {
1553
1577
  const updated = [];
1554
1578
  let updatedDeleted = [...deleted];
1555
1579
  inserted.forEach((ins) => {
@@ -1571,7 +1595,7 @@ function diffChangeSteps(deleted, inserted, newTr, schema) {
1571
1595
  return;
1572
1596
  }
1573
1597
  // Start diffing from the start of the deleted range
1574
- const deleteStart = deleted.reduce((acc, cur) => {
1598
+ const deleteStart = updatedDeleted.reduce((acc, cur) => {
1575
1599
  if (cur.type === 'delete-node') {
1576
1600
  return Math.min(acc, cur.pos);
1577
1601
  }
@@ -1580,13 +1604,13 @@ function diffChangeSteps(deleted, inserted, newTr, schema) {
1580
1604
  }
1581
1605
  return acc;
1582
1606
  }, Number.MAX_SAFE_INTEGER);
1583
- const [inDeleted, updatedDel] = matchInserted(deleteStart, updatedDeleted, ins.slice.content);
1584
- if (inDeleted === deleteStart) {
1607
+ const [matchedDeleted, updatedDel] = matchInserted(deleteStart, updatedDeleted, ins.slice.content);
1608
+ if (matchedDeleted === deleteStart) {
1585
1609
  updated.push(ins);
1586
1610
  return;
1587
1611
  }
1588
1612
  updatedDeleted = updatedDel;
1589
- const newInserted = cutFragment(0, inDeleted, ins.slice.content)[1];
1613
+ const [_, newInserted] = cutFragment(0, matchedDeleted - deleteStart, ins.slice.content);
1590
1614
  if (newInserted.size > 0) {
1591
1615
  updated.push({
1592
1616
  ...ins,
@@ -1654,9 +1678,10 @@ function trackTransaction(tr, oldState, newTr, authorID) {
1654
1678
  // deleted and merged really...
1655
1679
  const deleted = steps.filter((s) => s.type !== 'insert-slice');
1656
1680
  const inserted = steps.filter((s) => s.type === 'insert-slice');
1657
- steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
1681
+ steps = diffChangeSteps(deleted, inserted);
1658
1682
  log.info('DIFFED STEPS: ', steps);
1659
- const [mapping, selectionPos] = processChangeSteps(steps, startPos, newTr, emptyAttrs, oldState.schema);
1683
+ const [mapping, selectionPos] = processChangeSteps(steps, startPos || tr.selection.head, // Incase startPos is it's default value 0, use the old selection head
1684
+ newTr, emptyAttrs, oldState.schema);
1660
1685
  if (!wasNodeSelection) {
1661
1686
  const sel = getSelectionStaticConstructor(tr.selection);
1662
1687
  // Use Selection.near to fix selections that point to a block node instead of inline content
@@ -1671,7 +1696,7 @@ function trackTransaction(tr, oldState, newTr, authorID) {
1671
1696
  const deleted = steps.filter((s) => s.type !== 'insert-slice');
1672
1697
  const inserted = steps.filter((s) => s.type === 'insert-slice');
1673
1698
  log.info('INSERT STEPS: ', inserted);
1674
- steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
1699
+ steps = diffChangeSteps(deleted, inserted);
1675
1700
  log.info('DIFFED STEPS: ', steps);
1676
1701
  processChangeSteps(steps, tr.selection.from, newTr, emptyAttrs, oldState.schema);
1677
1702
  }
@@ -1761,15 +1786,14 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1761
1786
  return {
1762
1787
  ...pluginState,
1763
1788
  status: setStatus,
1764
- changeSet: findChanges(newState),
1789
+ changeSet: setStatus === TrackChangesStatus.disabled ? new ChangeSet() : findChanges(newState),
1765
1790
  };
1766
1791
  }
1767
1792
  else if (pluginState.status === TrackChangesStatus.disabled) {
1768
1793
  return { ...pluginState, changeSet: new ChangeSet() };
1769
1794
  }
1770
1795
  let { changeSet, ...rest } = pluginState;
1771
- const updatedChangeIds = getAction(tr, TrackChangesAction.updateChanges);
1772
- if (updatedChangeIds || getAction(tr, TrackChangesAction.refreshChanges)) {
1796
+ if (getAction(tr, TrackChangesAction.refreshChanges)) {
1773
1797
  changeSet = findChanges(newState);
1774
1798
  }
1775
1799
  return {
@@ -1813,10 +1837,9 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1813
1837
  createdTr = updateChangeAttrs(createdTr, change, { ...change.dataTracked, status, reviewedByID: userID }, oldState.schema);
1814
1838
  }
1815
1839
  });
1816
- setAction(createdTr, TrackChangesAction.updateChanges, ids);
1817
1840
  }
1818
1841
  else if (getAction(tr, TrackChangesAction.applyAndRemoveChanges)) {
1819
- const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.nodeChanges);
1842
+ const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.bothNodeChanges);
1820
1843
  applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.textChanges, mapping);
1821
1844
  setAction(createdTr, TrackChangesAction.refreshChanges, true);
1822
1845
  }
@@ -1892,24 +1915,8 @@ const applyAndRemoveChanges = () => (state, dispatch) => {
1892
1915
  * Runs `findChanges` to iterate over the document to collect changes into a new ChangeSet.
1893
1916
  */
1894
1917
  const refreshChanges = () => (state, dispatch) => {
1895
- dispatch && dispatch(setAction(state.tr, TrackChangesAction.updateChanges, []));
1918
+ dispatch && dispatch(setAction(state.tr, TrackChangesAction.refreshChanges, true));
1896
1919
  return true;
1897
- };
1898
- /**
1899
- * Adds track attributes for a block node. For testing puroses
1900
- */
1901
- const setParagraphTestAttribute = (val = 'changed') => (state, dispatch) => {
1902
- var _a;
1903
- const cursor = state.selection.head;
1904
- const blockNodePos = state.doc.resolve(cursor).start(1) - 1;
1905
- if (((_a = state.doc.resolve(blockNodePos).nodeAfter) === null || _a === void 0 ? void 0 : _a.type) === state.schema.nodes.paragraph &&
1906
- dispatch) {
1907
- dispatch(state.tr.setNodeMarkup(blockNodePos, undefined, {
1908
- testAttribute: val,
1909
- }));
1910
- return true;
1911
- }
1912
- return false;
1913
1920
  };
1914
1921
 
1915
1922
  var commands = /*#__PURE__*/Object.freeze({
@@ -1918,8 +1925,7 @@ var commands = /*#__PURE__*/Object.freeze({
1918
1925
  setChangeStatuses: setChangeStatuses,
1919
1926
  setUserID: setUserID,
1920
1927
  applyAndRemoveChanges: applyAndRemoveChanges,
1921
- refreshChanges: refreshChanges,
1922
- setParagraphTestAttribute: setParagraphTestAttribute
1928
+ refreshChanges: refreshChanges
1923
1929
  });
1924
1930
 
1925
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;
@@ -32,7 +32,7 @@ declare type InsertDeleteAttrs = {
32
32
  createdAt: number;
33
33
  updatedAt: number;
34
34
  };
35
- declare type UpdateAttrs = Omit<InsertDeleteAttrs, 'operation'> & {
35
+ export declare type UpdateAttrs = Omit<InsertDeleteAttrs, 'operation'> & {
36
36
  operation: CHANGE_OPERATION.set_node_attributes;
37
37
  oldAttrs: Record<string, any>;
38
38
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manuscripts/track-changes-plugin",
3
- "version": "0.4.1",
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",