@manuscripts/track-changes-plugin 0.3.0 → 0.4.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/dist/index.cjs CHANGED
@@ -94,11 +94,11 @@ exports.CHANGE_OPERATION = void 0;
94
94
  (function (CHANGE_OPERATION) {
95
95
  CHANGE_OPERATION["insert"] = "insert";
96
96
  CHANGE_OPERATION["delete"] = "delete";
97
- CHANGE_OPERATION["set_node_attributes"] = "set_node_attributes";
98
- CHANGE_OPERATION["wrap_with_node"] = "wrap_with_node";
99
- CHANGE_OPERATION["unwrap_from_node"] = "unwrap_from_node";
100
- CHANGE_OPERATION["add_mark"] = "add_mark";
101
- CHANGE_OPERATION["remove_mark"] = "remove_mark";
97
+ CHANGE_OPERATION["set_node_attributes"] = "set_attrs";
98
+ // wrap_with_node = 'wrap_with_node',
99
+ // unwrap_from_node = 'unwrap_from_node',
100
+ // add_mark = 'add_mark',
101
+ // remove_mark = 'remove_mark',
102
102
  })(exports.CHANGE_OPERATION || (exports.CHANGE_OPERATION = {}));
103
103
  exports.CHANGE_STATUS = void 0;
104
104
  (function (CHANGE_STATUS) {
@@ -199,16 +199,13 @@ class ChangeSet {
199
199
  rootNodes.push(currentNodeChange);
200
200
  currentNodeChange = undefined;
201
201
  }
202
- if (c.type === 'node-change' && currentNodeChange && c.from < currentNodeChange.to) {
202
+ if (currentNodeChange && c.from < currentNodeChange.to) {
203
203
  currentNodeChange.children.push(c);
204
204
  }
205
205
  else if (c.type === 'node-change') {
206
206
  currentNodeChange = { ...c, children: [] };
207
207
  }
208
- else if (c.type === 'text-change' && currentNodeChange && c.from < currentNodeChange.to) {
209
- currentNodeChange.children.push(c);
210
- }
211
- else if (c.type === 'text-change') {
208
+ else {
212
209
  rootNodes.push(c);
213
210
  }
214
211
  });
@@ -232,6 +229,9 @@ class ChangeSet {
232
229
  get nodeChanges() {
233
230
  return this.changes.filter((c) => c.type === 'node-change');
234
231
  }
232
+ get nodeAttrChanges() {
233
+ return this.changes.filter((c) => c.type === 'node-attr-change');
234
+ }
235
235
  get isEmpty() {
236
236
  return __classPrivateFieldGet(this, _ChangeSet_changes, "f").length === 0;
237
237
  }
@@ -277,9 +277,7 @@ class ChangeSet {
277
277
  * @param change
278
278
  */
279
279
  static shouldNotDelete(change) {
280
- const { status, operation } = change.attrs;
281
- return ((operation === exports.CHANGE_OPERATION.insert && status === exports.CHANGE_STATUS.accepted) ||
282
- (operation === exports.CHANGE_OPERATION.delete && status === exports.CHANGE_STATUS.rejected));
280
+ return !ChangeSet.shouldDeleteChange(change);
283
281
  }
284
282
  /**
285
283
  * Determines whether a change should be deleted when applying it to the document.
@@ -307,7 +305,7 @@ class ChangeSet {
307
305
  'updatedAt',
308
306
  ];
309
307
  // 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
308
+ // So it can be either omitted completely or at least be null or string
311
309
  const optionalKeys = ['reviewedByID'];
312
310
  const entries = Object.entries(attrs).filter(([key, val]) => trackedKeys.includes(key));
313
311
  const optionalEntries = Object.entries(attrs).filter(([key, val]) => optionalKeys.includes(key));
@@ -323,9 +321,89 @@ class ChangeSet {
323
321
  static isNodeChange(change) {
324
322
  return change.type === 'node-change';
325
323
  }
324
+ static isNodeAttrChange(change) {
325
+ return change.type === 'node-attr-change';
326
+ }
326
327
  }
327
328
  _ChangeSet_changes = new WeakMap();
328
329
 
330
+ /*!
331
+ * © 2021 Atypon Systems LLC
332
+ *
333
+ * Licensed under the Apache License, Version 2.0 (the "License");
334
+ * you may not use this file except in compliance with the License.
335
+ * You may obtain a copy of the License at
336
+ *
337
+ * http://www.apache.org/licenses/LICENSE-2.0
338
+ *
339
+ * Unless required by applicable law or agreed to in writing, software
340
+ * distributed under the License is distributed on an "AS IS" BASIS,
341
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
342
+ * See the License for the specific language governing permissions and
343
+ * limitations under the License.
344
+ */
345
+ function uuidv4() {
346
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
347
+ const r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
348
+ return v.toString(16);
349
+ });
350
+ }
351
+
352
+ function addTrackIdIfDoesntExist(attrs) {
353
+ if (!attrs.id) {
354
+ return {
355
+ id: uuidv4(),
356
+ ...attrs,
357
+ };
358
+ }
359
+ return attrs;
360
+ }
361
+ function getInlineNodeTrackedMarkData(node, schema) {
362
+ if (!node || !node.isInline) {
363
+ return undefined;
364
+ }
365
+ const marksTrackedData = [];
366
+ node.marks.forEach((mark) => {
367
+ if (mark.type === schema.marks.tracked_insert || mark.type === schema.marks.tracked_delete) {
368
+ const operation = mark.type === schema.marks.tracked_insert
369
+ ? exports.CHANGE_OPERATION.insert
370
+ : exports.CHANGE_OPERATION.delete;
371
+ marksTrackedData.push({ ...mark.attrs.dataTracked, operation });
372
+ }
373
+ });
374
+ if (marksTrackedData.length > 1) {
375
+ log.warn('inline node with more than 1 of tracked marks', marksTrackedData);
376
+ }
377
+ return marksTrackedData[0] || undefined;
378
+ }
379
+ function getNodeTrackedData(node, schema) {
380
+ return !node
381
+ ? undefined
382
+ : node.isText
383
+ ? getInlineNodeTrackedMarkData(node, schema)
384
+ : node.attrs.dataTracked;
385
+ }
386
+ function equalMarks(n1, n2) {
387
+ return (n1.marks.length === n2.marks.length &&
388
+ n1.marks.every((mark) => n1.marks.find((m) => m.type === mark.type)));
389
+ }
390
+ function shouldMergeTrackedAttributes(left, right) {
391
+ if (!left || !right) {
392
+ log.warn('passed undefined dataTracked attributes to shouldMergeTrackedAttributes', {
393
+ left,
394
+ right,
395
+ });
396
+ return false;
397
+ }
398
+ return (left.status === right.status &&
399
+ left.operation === right.operation &&
400
+ left.authorID === right.authorID);
401
+ }
402
+ function getMergeableMarkTrackedAttrs(node, attrs, schema) {
403
+ const nodeAttrs = getInlineNodeTrackedMarkData(node, schema);
404
+ return nodeAttrs && shouldMergeTrackedAttributes(nodeAttrs, attrs) ? nodeAttrs : null;
405
+ }
406
+
329
407
  /*!
330
408
  * © 2021 Atypon Systems LLC
331
409
  *
@@ -413,83 +491,6 @@ function mergeNode(node, pos, tr) {
413
491
  return undefined;
414
492
  }
415
493
 
416
- /*!
417
- * © 2021 Atypon Systems LLC
418
- *
419
- * Licensed under the Apache License, Version 2.0 (the "License");
420
- * you may not use this file except in compliance with the License.
421
- * You may obtain a copy of the License at
422
- *
423
- * http://www.apache.org/licenses/LICENSE-2.0
424
- *
425
- * Unless required by applicable law or agreed to in writing, software
426
- * distributed under the License is distributed on an "AS IS" BASIS,
427
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
428
- * See the License for the specific language governing permissions and
429
- * limitations under the License.
430
- */
431
- function uuidv4() {
432
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
433
- const r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
434
- return v.toString(16);
435
- });
436
- }
437
-
438
- function addTrackIdIfDoesntExist(attrs) {
439
- if (!attrs.id) {
440
- return {
441
- id: uuidv4(),
442
- ...attrs,
443
- };
444
- }
445
- return attrs;
446
- }
447
- function getInlineNodeTrackedMarkData(node, schema) {
448
- if (!node || !node.isInline) {
449
- return undefined;
450
- }
451
- const marksTrackedData = [];
452
- node.marks.forEach((mark) => {
453
- if (mark.type === schema.marks.tracked_insert || mark.type === schema.marks.tracked_delete) {
454
- const operation = mark.type === schema.marks.tracked_insert
455
- ? exports.CHANGE_OPERATION.insert
456
- : exports.CHANGE_OPERATION.delete;
457
- marksTrackedData.push({ ...mark.attrs.dataTracked, operation });
458
- }
459
- });
460
- if (marksTrackedData.length > 1) {
461
- log.warn('inline node with more than 1 of tracked marks', marksTrackedData);
462
- }
463
- return marksTrackedData[0] || undefined;
464
- }
465
- function getNodeTrackedData(node, schema) {
466
- return !node
467
- ? undefined
468
- : node.isText
469
- ? getInlineNodeTrackedMarkData(node, schema)
470
- : node.attrs.dataTracked;
471
- }
472
- function equalMarks(n1, n2) {
473
- return (n1.marks.length === n2.marks.length &&
474
- n1.marks.every((mark) => n1.marks.find((m) => m.type === mark.type)));
475
- }
476
- function shouldMergeTrackedAttributes(left, right) {
477
- if (!left || !right) {
478
- log.warn('passed undefined dataTracked attributes to shouldMergeTrackedAttributes', {
479
- left,
480
- right,
481
- });
482
- return false;
483
- }
484
- return (left.status === right.status &&
485
- left.operation === right.operation &&
486
- left.authorID === right.authorID);
487
- }
488
- function getMergeableMarkTrackedAttrs(node, attrs, schema) {
489
- const nodeAttrs = getInlineNodeTrackedMarkData(node, schema);
490
- return nodeAttrs && shouldMergeTrackedAttributes(nodeAttrs, attrs) ? nodeAttrs : null;
491
- }
492
-
493
494
  function updateChangeAttrs(tr, change, trackedAttrs, schema) {
494
495
  const node = tr.doc.nodeAt(change.from);
495
496
  if (!node) {
@@ -562,6 +563,16 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new prose
562
563
  }
563
564
  deleteMap.appendMap(tr.steps[tr.steps.length - 1].getMap());
564
565
  }
566
+ else if (ChangeSet.isNodeAttrChange(change) &&
567
+ change.attrs.status === exports.CHANGE_STATUS.accepted) {
568
+ const attrs = { ...node.attrs, dataTracked: null };
569
+ tr.setNodeMarkup(from, undefined, attrs, node.marks);
570
+ }
571
+ else if (ChangeSet.isNodeAttrChange(change) &&
572
+ change.attrs.status === exports.CHANGE_STATUS.rejected) {
573
+ const attrs = { ...change.oldAttrs, dataTracked: null };
574
+ tr.setNodeMarkup(from, undefined, attrs, node.marks);
575
+ }
565
576
  });
566
577
  return deleteMap;
567
578
  }
@@ -667,87 +678,6 @@ function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
667
678
  return changed;
668
679
  }
669
680
 
670
- /*!
671
- * © 2021 Atypon Systems LLC
672
- *
673
- * Licensed under the Apache License, Version 2.0 (the "License");
674
- * you may not use this file except in compliance with the License.
675
- * You may obtain a copy of the License at
676
- *
677
- * http://www.apache.org/licenses/LICENSE-2.0
678
- *
679
- * Unless required by applicable law or agreed to in writing, software
680
- * distributed under the License is distributed on an "AS IS" BASIS,
681
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
682
- * See the License for the specific language governing permissions and
683
- * limitations under the License.
684
- */
685
- function markInlineNodeChange(node, newTrackAttrs, schema) {
686
- const filtered = node.marks.filter((m) => m.type !== schema.marks.tracked_insert && m.type !== schema.marks.tracked_delete);
687
- const mark = newTrackAttrs.operation === exports.CHANGE_OPERATION.insert
688
- ? schema.marks.tracked_insert
689
- : schema.marks.tracked_delete;
690
- const createdMark = mark.create({
691
- dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
692
- });
693
- return node.mark(filtered.concat(createdMark));
694
- }
695
- function recurseNodeContent(node, newTrackAttrs, schema) {
696
- if (node.isText) {
697
- return markInlineNodeChange(node, newTrackAttrs, schema);
698
- }
699
- else if (node.isBlock || node.isInline) {
700
- const updatedChildren = [];
701
- node.content.forEach((child) => {
702
- updatedChildren.push(recurseNodeContent(child, newTrackAttrs, schema));
703
- });
704
- return node.type.create({
705
- ...node.attrs,
706
- dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
707
- }, prosemirrorModel.Fragment.fromArray(updatedChildren), node.marks);
708
- }
709
- else {
710
- log.error(`unhandled node type: "${node.type.name}"`, node);
711
- return node;
712
- }
713
- }
714
- function setFragmentAsInserted(inserted, insertAttrs, schema) {
715
- // Recurse the content in the inserted slice and either mark it tracked_insert or set node attrs
716
- const updatedInserted = [];
717
- inserted.forEach((n) => {
718
- updatedInserted.push(recurseNodeContent(n, insertAttrs, schema));
719
- });
720
- return updatedInserted.length === 0 ? inserted : prosemirrorModel.Fragment.fromArray(updatedInserted);
721
- }
722
-
723
- /*!
724
- * © 2021 Atypon Systems LLC
725
- *
726
- * Licensed under the Apache License, Version 2.0 (the "License");
727
- * you may not use this file except in compliance with the License.
728
- * You may obtain a copy of the License at
729
- *
730
- * http://www.apache.org/licenses/LICENSE-2.0
731
- *
732
- * Unless required by applicable law or agreed to in writing, software
733
- * distributed under the License is distributed on an "AS IS" BASIS,
734
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
735
- * See the License for the specific language governing permissions and
736
- * limitations under the License.
737
- */
738
- function createNewInsertAttrs(attrs) {
739
- return {
740
- ...attrs,
741
- operation: exports.CHANGE_OPERATION.insert,
742
- };
743
- }
744
- function createNewDeleteAttrs(attrs) {
745
- return {
746
- ...attrs,
747
- operation: exports.CHANGE_OPERATION.delete,
748
- };
749
- }
750
-
751
681
  /*!
752
682
  * © 2021 Atypon Systems LLC
753
683
  *
@@ -843,73 +773,104 @@ function splitSliceIntoMergedParts(insertSlice, mergeEqualSides = false) {
843
773
  firstMergedNode,
844
774
  lastMergedNode,
845
775
  };
846
- }
847
- /**
848
- * Deletes inserted text directly, otherwise wraps it with tracked_delete mark
776
+ }
777
+
778
+ /*!
779
+ * © 2021 Atypon Systems LLC
849
780
  *
850
- * This would work for general inline nodes too, but since node marks don't work properly
851
- * with Yjs, attributes are used instead.
852
- * @param node
853
- * @param pos
854
- * @param newTr
855
- * @param schema
856
- * @param deleteAttrs
857
- * @param from
858
- * @param to
781
+ * Licensed under the Apache License, Version 2.0 (the "License");
782
+ * you may not use this file except in compliance with the License.
783
+ * You may obtain a copy of the License at
784
+ *
785
+ * http://www.apache.org/licenses/LICENSE-2.0
786
+ *
787
+ * Unless required by applicable law or agreed to in writing, software
788
+ * distributed under the License is distributed on an "AS IS" BASIS,
789
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
790
+ * See the License for the specific language governing permissions and
791
+ * limitations under the License.
859
792
  */
860
- function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
861
- const start = from ? Math.max(pos, from) : pos;
862
- const nodeEnd = pos + node.nodeSize;
863
- const end = to ? Math.min(nodeEnd, to) : nodeEnd;
864
- if (node.marks.find((m) => m.type === schema.marks.tracked_insert)) {
865
- // Math.max(pos, from) is for picking always the start of the node,
866
- // not the start of the change (which might span multiple nodes).
867
- // Pos can be less than from as nodesBetween iterates through all nodes starting from the top block node
868
- newTr.replaceWith(start, end, prosemirrorModel.Fragment.empty);
869
- return start;
793
+ function markInlineNodeChange(node, newTrackAttrs, schema) {
794
+ const filtered = node.marks.filter((m) => m.type !== schema.marks.tracked_insert && m.type !== schema.marks.tracked_delete);
795
+ const mark = newTrackAttrs.operation === exports.CHANGE_OPERATION.insert
796
+ ? schema.marks.tracked_insert
797
+ : schema.marks.tracked_delete;
798
+ const createdMark = mark.create({
799
+ dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
800
+ });
801
+ return node.mark(filtered.concat(createdMark));
802
+ }
803
+ function recurseNodeContent(node, newTrackAttrs, schema) {
804
+ if (node.isText) {
805
+ return markInlineNodeChange(node, newTrackAttrs, schema);
806
+ }
807
+ else if (node.isBlock || node.isInline) {
808
+ const updatedChildren = [];
809
+ node.content.forEach((child) => {
810
+ updatedChildren.push(recurseNodeContent(child, newTrackAttrs, schema));
811
+ });
812
+ return node.type.create({
813
+ ...node.attrs,
814
+ dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
815
+ }, prosemirrorModel.Fragment.fromArray(updatedChildren), node.marks);
870
816
  }
871
817
  else {
872
- const leftNode = newTr.doc.resolve(start).nodeBefore;
873
- const leftMarks = getMergeableMarkTrackedAttrs(leftNode, deleteAttrs, schema);
874
- const rightNode = newTr.doc.resolve(end).nodeAfter;
875
- const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
876
- const fromStartOfMark = start - (leftNode && leftMarks ? leftNode.nodeSize : 0);
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);
879
- const dataTracked = addTrackIdIfDoesntExist({
880
- ...leftMarks,
881
- ...rightMarks,
882
- ...deleteAttrs,
883
- createdAt,
884
- });
885
- newTr.addMark(fromStartOfMark, toEndOfMark, schema.marks.tracked_delete.create({
886
- dataTracked,
887
- }));
888
- return toEndOfMark;
818
+ log.error(`unhandled node type: "${node.type.name}"`, node);
819
+ return node;
889
820
  }
890
821
  }
891
- /**
892
- * Deletes inserted block or inline node, otherwise adds `dataTracked` object with CHANGE_STATUS 'deleted'
893
- * @param node
894
- * @param pos
895
- * @param newTr
896
- * @param deleteAttrs
822
+ function setFragmentAsInserted(inserted, insertAttrs, schema) {
823
+ // Recurse the content in the inserted slice and either mark it tracked_insert or set node attrs
824
+ const updatedInserted = [];
825
+ inserted.forEach((n) => {
826
+ updatedInserted.push(recurseNodeContent(n, insertAttrs, schema));
827
+ });
828
+ return updatedInserted.length === 0 ? inserted : prosemirrorModel.Fragment.fromArray(updatedInserted);
829
+ }
830
+
831
+ /*!
832
+ * © 2021 Atypon Systems LLC
833
+ *
834
+ * Licensed under the Apache License, Version 2.0 (the "License");
835
+ * you may not use this file except in compliance with the License.
836
+ * You may obtain a copy of the License at
837
+ *
838
+ * http://www.apache.org/licenses/LICENSE-2.0
839
+ *
840
+ * Unless required by applicable law or agreed to in writing, software
841
+ * distributed under the License is distributed on an "AS IS" BASIS,
842
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
843
+ * See the License for the specific language governing permissions and
844
+ * limitations under the License.
897
845
  */
898
- function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
899
- const dataTracked = node.attrs.dataTracked;
900
- const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === exports.CHANGE_OPERATION.insert &&
901
- dataTracked.authorID === deleteAttrs.authorID;
902
- if (wasInsertedBySameUser) {
903
- deleteNode(node, pos, newTr);
904
- }
905
- else {
906
- const attrs = {
907
- ...node.attrs,
908
- dataTracked: addTrackIdIfDoesntExist(deleteAttrs),
909
- };
910
- newTr.setNodeMarkup(pos, undefined, attrs, node.marks);
911
- }
846
+ function createNewInsertAttrs(attrs) {
847
+ return {
848
+ ...attrs,
849
+ operation: exports.CHANGE_OPERATION.insert,
850
+ };
912
851
  }
852
+ function createNewDeleteAttrs(attrs) {
853
+ return {
854
+ ...attrs,
855
+ operation: exports.CHANGE_OPERATION.delete,
856
+ };
857
+ }
858
+
859
+ /*!
860
+ * © 2021 Atypon Systems LLC
861
+ *
862
+ * Licensed under the Apache License, Version 2.0 (the "License");
863
+ * you may not use this file except in compliance with the License.
864
+ * You may obtain a copy of the License at
865
+ *
866
+ * http://www.apache.org/licenses/LICENSE-2.0
867
+ *
868
+ * Unless required by applicable law or agreed to in writing, software
869
+ * distributed under the License is distributed on an "AS IS" BASIS,
870
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
871
+ * See the License for the specific language governing permissions and
872
+ * limitations under the License.
873
+ */
913
874
  /**
914
875
  * Applies deletion to the doc without actually deleting nodes that have not been inserted
915
876
  *
@@ -937,18 +898,17 @@ function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
937
898
  */
938
899
  function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackAttrs, insertSlice) {
939
900
  const deleteMap = new prosemirrorTransform.Mapping();
940
- const mergedInsertPos = undefined;
901
+ const steps = [];
941
902
  // No deletion applied, return default values
942
903
  if (from === to) {
943
904
  return {
944
905
  deleteMap,
945
- mergedInsertPos,
946
906
  newSliceContent: insertSlice.content,
907
+ steps,
947
908
  };
948
909
  }
949
910
  const { openStart, openEnd } = insertSlice;
950
911
  const { updatedSliceNodes, firstMergedNode, lastMergedNode } = splitSliceIntoMergedParts(insertSlice, gap !== undefined);
951
- const deleteAttrs = createNewDeleteAttrs(trackAttrs);
952
912
  let mergingStartSide = true;
953
913
  startDoc.nodesBetween(from, to, (node, pos) => {
954
914
  const { pos: offsetPos, deleted: nodeWasDeleted } = deleteMap.mapResult(pos, 1);
@@ -1024,24 +984,18 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1024
984
  // ProseMirror node semantics as start tokens are considered to contain the actual node itself.
1025
985
  const mergeEndNode = startTokenDeleted && openEnd > 0 && depth === openEnd && mergeContent !== undefined;
1026
986
  if (mergeStartNode || mergeEndNode) {
1027
- // The default insert position for block nodes is either the start of the merged content or the end.
1028
- // Incase text was merged, this must be updated as the start or end of the node doesn't map to the
1029
- // actual position of the merge. Currently the inserted content is inserted at the start or end
1030
- // of the merged content, TODO reverse the start/end when end/start token?
1031
- let insertPos = mergeStartNode ? nodeEnd - openStart : offsetPos + openEnd;
1032
- if (node.isText) {
1033
- // When merging text we must delete text in the same go as well, as the from/to boundary goes through
1034
- // the text node.
1035
- insertPos = deleteTextIfInserted(node, offsetPos, newTr, schema, deleteAttrs, offsetFrom, offsetTo);
1036
- deleteMap.appendMap(newTr.steps[newTr.steps.length - 1].getMap());
1037
- step = newTr.steps[newTr.steps.length - 1];
1038
- }
1039
987
  // Just as a fun fact that I found out while debugging this. Inserting text at paragraph position wraps
1040
988
  // it into a new paragraph(!). So that's why you always offset your positions to insert it _inside_
1041
989
  // the paragraph.
1042
- if (mergeContent.size !== 0) {
1043
- newTr.insert(insertPos, setFragmentAsInserted(mergeContent, createNewInsertAttrs(trackAttrs), schema));
1044
- }
990
+ steps.push({
991
+ type: 'merge-fragment',
992
+ pos: offsetPos,
993
+ mergePos: mergeStartNode ? nodeEnd - openStart : offsetPos + openEnd,
994
+ from: offsetFrom,
995
+ to: offsetTo,
996
+ node,
997
+ fragment: setFragmentAsInserted(mergeContent, createNewInsertAttrs(trackAttrs), schema),
998
+ });
1045
999
  // Okay this is a bit ridiculous but it's used to adjust the insert pos when track changes prevents deletions
1046
1000
  // of merged nodes & content, as just using mapped toA in that case isn't the same.
1047
1001
  // The calculation is a bit mysterious, I admit.
@@ -1054,12 +1008,23 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1054
1008
  else if (node.isText) {
1055
1009
  // Text deletion is handled even when the deletion doesn't completely wrap the text node
1056
1010
  // (which is basically the case most of the time)
1057
- deleteTextIfInserted(node, offsetPos, newTr, schema, deleteAttrs, offsetFrom, offsetTo);
1011
+ steps.push({
1012
+ type: 'delete-text',
1013
+ pos: offsetPos,
1014
+ from: Math.max(offsetPos, offsetFrom),
1015
+ to: Math.min(nodeEnd, offsetTo),
1016
+ node,
1017
+ });
1058
1018
  }
1059
1019
  else ;
1060
1020
  }
1061
1021
  else if (nodeCompletelyDeleted) {
1062
- deleteOrSetNodeDeleted(node, offsetPos, newTr, deleteAttrs);
1022
+ steps.push({
1023
+ type: 'delete-node',
1024
+ pos: offsetPos,
1025
+ nodeEnd: nodeEnd,
1026
+ node,
1027
+ });
1063
1028
  }
1064
1029
  }
1065
1030
  const newestStep = newTr.steps[newTr.steps.length - 1];
@@ -1069,10 +1034,10 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1069
1034
  });
1070
1035
  return {
1071
1036
  deleteMap,
1072
- mergedInsertPos,
1073
1037
  newSliceContent: updatedSliceNodes
1074
1038
  ? prosemirrorModel.Fragment.fromArray(updatedSliceNodes)
1075
1039
  : insertSlice.content,
1040
+ steps,
1076
1041
  };
1077
1042
  }
1078
1043
 
@@ -1100,8 +1065,7 @@ function mergeTrackedMarks(pos, doc, newTr, schema) {
1100
1065
  if (!shouldMergeTrackedAttributes(leftDataTracked, rightDataTracked)) {
1101
1066
  return;
1102
1067
  }
1103
- const isLeftOlder = (leftDataTracked.createdAt || Number.MAX_VALUE) <
1104
- (rightDataTracked.createdAt || Number.MAX_VALUE);
1068
+ const isLeftOlder = (leftDataTracked.createdAt || 0) < (rightDataTracked.createdAt || 0);
1105
1069
  const ancestorAttrs = isLeftOlder ? leftDataTracked : rightDataTracked;
1106
1070
  const dataTracked = {
1107
1071
  ...ancestorAttrs,
@@ -1157,14 +1121,16 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1157
1121
  const stepResult = newTr.maybeStep(newStep);
1158
1122
  if (stepResult.failed) {
1159
1123
  log.error(`inverting ReplaceAroundStep failed: "${stepResult.failed}"`, newStep);
1160
- return;
1124
+ return [];
1161
1125
  }
1162
1126
  const gap = oldState.doc.slice(gapFrom, gapTo);
1163
1127
  log.info('RETAINED GAP CONTENT', gap);
1164
1128
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1165
1129
  // eg partial nodes in an open-ended slice
1166
- const { deleteMap, newSliceContent } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1130
+ const { deleteMap, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1131
+ let steps = deleteSteps;
1167
1132
  log.info('TR: new steps after applying delete', [...newTr.steps]);
1133
+ log.info('DELETE STEPS: ', deleteSteps);
1168
1134
  // We only want to insert when there something inside the gap (actually would this be always true?)
1169
1135
  // or insert slice wasn't just start/end tokens (which we already merged inside deleteAndMergeSplitBlockNodes)
1170
1136
  if (gap.size > 0 || (!structure && newSliceContent.size > 0)) {
@@ -1179,21 +1145,19 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1179
1145
  insertedSlice = insertedSlice.insertAt(insertedSlice.size === 0 ? 0 : insert, gap.content);
1180
1146
  log.info('insertedSlice after inserted gap', insertedSlice);
1181
1147
  }
1182
- const newStep = new prosemirrorTransform.ReplaceStep(deleteMap.map(gapFrom), deleteMap.map(gapTo), insertedSlice, false);
1183
- const stepResult = newTr.maybeStep(newStep);
1184
- if (stepResult.failed) {
1185
- log.error(`insert ReplaceStep failed: "${stepResult.failed}"`, newStep);
1186
- return;
1187
- }
1188
- log.info('new steps after applying insert', [...newTr.steps]);
1189
- mergeTrackedMarks(deleteMap.map(gapFrom), newTr.doc, newTr, oldState.schema);
1190
- mergeTrackedMarks(deleteMap.map(gapTo), newTr.doc, newTr, oldState.schema);
1148
+ deleteSteps.push({
1149
+ type: 'insert-slice',
1150
+ from: deleteMap.map(gapFrom),
1151
+ to: deleteMap.map(gapTo),
1152
+ slice: insertedSlice,
1153
+ });
1191
1154
  }
1192
1155
  else {
1193
1156
  // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1194
1157
  mergeTrackedMarks(deleteMap.map(gapFrom), newTr.doc, newTr, oldState.schema);
1195
1158
  mergeTrackedMarks(deleteMap.map(gapTo), newTr.doc, newTr, oldState.schema);
1196
1159
  }
1160
+ return steps;
1197
1161
  }
1198
1162
 
1199
1163
  /*!
@@ -1213,7 +1177,7 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1213
1177
  */
1214
1178
  function trackReplaceStep(step, oldState, newTr, attrs) {
1215
1179
  log.info('###### ReplaceStep ######');
1216
- let selectionPos = 0;
1180
+ let selectionPos = 0, changeSteps = [];
1217
1181
  step.getMap().forEach((fromA, toA, fromB, toB) => {
1218
1182
  log.info(`changed ranges: ${fromA} ${toA} ${fromB} ${toB}`);
1219
1183
  const { slice } = step;
@@ -1227,34 +1191,306 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1227
1191
  log.info('TR: steps before applying delete', [...newTr.steps]);
1228
1192
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1229
1193
  // eg partial nodes in an open-ended slice
1230
- const { deleteMap, mergedInsertPos, newSliceContent } = deleteAndMergeSplitNodes(fromA, toA, undefined, oldState.doc, newTr, oldState.schema, attrs, slice);
1194
+ const { deleteMap, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(fromA, toA, undefined, oldState.doc, newTr, oldState.schema, attrs, slice);
1195
+ changeSteps.push(...deleteSteps);
1231
1196
  log.info('TR: steps after applying delete', [...newTr.steps]);
1232
- const adjustedInsertPos = mergedInsertPos !== null && mergedInsertPos !== void 0 ? mergedInsertPos : deleteMap.map(toA);
1197
+ log.info('DELETE STEPS: ', changeSteps);
1198
+ const adjustedInsertPos = deleteMap.map(toA);
1233
1199
  if (newSliceContent.size > 0) {
1234
1200
  log.info('newSliceContent', newSliceContent);
1235
1201
  // Since deleteAndMergeSplitBlockNodes modified the slice to not to contain any merged nodes,
1236
1202
  // the sides should be equal. TODO can they be other than 0?
1237
1203
  const openStart = slice.openStart !== slice.openEnd ? 0 : slice.openStart;
1238
1204
  const openEnd = slice.openStart !== slice.openEnd ? 0 : slice.openEnd;
1239
- const insertedSlice = new prosemirrorModel.Slice(setFragmentAsInserted(newSliceContent, createNewInsertAttrs(attrs), oldState.schema), openStart, openEnd);
1240
- const newStep = new prosemirrorTransform.ReplaceStep(adjustedInsertPos, adjustedInsertPos, insertedSlice);
1205
+ changeSteps.push({
1206
+ type: 'insert-slice',
1207
+ from: adjustedInsertPos,
1208
+ to: adjustedInsertPos,
1209
+ slice: new prosemirrorModel.Slice(setFragmentAsInserted(newSliceContent, createNewInsertAttrs(attrs), oldState.schema), openStart, openEnd),
1210
+ });
1211
+ }
1212
+ else {
1213
+ // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1214
+ mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema);
1215
+ selectionPos = fromA;
1216
+ }
1217
+ });
1218
+ return [changeSteps, selectionPos];
1219
+ }
1220
+
1221
+ /*!
1222
+ * © 2021 Atypon Systems LLC
1223
+ *
1224
+ * Licensed under the Apache License, Version 2.0 (the "License");
1225
+ * you may not use this file except in compliance with the License.
1226
+ * You may obtain a copy of the License at
1227
+ *
1228
+ * http://www.apache.org/licenses/LICENSE-2.0
1229
+ *
1230
+ * Unless required by applicable law or agreed to in writing, software
1231
+ * distributed under the License is distributed on an "AS IS" BASIS,
1232
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1233
+ * See the License for the specific language governing permissions and
1234
+ * limitations under the License.
1235
+ */
1236
+ /**
1237
+ * Deletes inserted text directly, otherwise wraps it with tracked_delete mark
1238
+ *
1239
+ * This would work for general inline nodes too, but since node marks don't work properly
1240
+ * with Yjs, attributes are used instead.
1241
+ * @param node
1242
+ * @param pos
1243
+ * @param newTr
1244
+ * @param schema
1245
+ * @param deleteAttrs
1246
+ * @param from
1247
+ * @param to
1248
+ */
1249
+ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1250
+ const start = from ? Math.max(pos, from) : pos;
1251
+ const nodeEnd = pos + node.nodeSize;
1252
+ const end = to ? Math.min(nodeEnd, to) : nodeEnd;
1253
+ if (node.marks.find((m) => m.type === schema.marks.tracked_insert)) {
1254
+ // Math.max(pos, from) is for picking always the start of the node,
1255
+ // not the start of the change (which might span multiple nodes).
1256
+ // Pos can be less than from as nodesBetween iterates through all nodes starting from the top block node
1257
+ newTr.replaceWith(start, end, prosemirrorModel.Fragment.empty);
1258
+ return start;
1259
+ }
1260
+ else {
1261
+ const leftNode = newTr.doc.resolve(start).nodeBefore;
1262
+ const leftMarks = getMergeableMarkTrackedAttrs(leftNode, deleteAttrs, schema);
1263
+ const rightNode = newTr.doc.resolve(end).nodeAfter;
1264
+ const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
1265
+ const fromStartOfMark = start - (leftNode && leftMarks ? leftNode.nodeSize : 0);
1266
+ const toEndOfMark = end + (rightNode && rightMarks ? rightNode.nodeSize : 0);
1267
+ 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);
1268
+ const dataTracked = addTrackIdIfDoesntExist({
1269
+ ...leftMarks,
1270
+ ...rightMarks,
1271
+ ...deleteAttrs,
1272
+ createdAt,
1273
+ });
1274
+ newTr.addMark(fromStartOfMark, toEndOfMark, schema.marks.tracked_delete.create({
1275
+ dataTracked,
1276
+ }));
1277
+ return toEndOfMark;
1278
+ }
1279
+ }
1280
+
1281
+ /*!
1282
+ * © 2021 Atypon Systems LLC
1283
+ *
1284
+ * Licensed under the Apache License, Version 2.0 (the "License");
1285
+ * you may not use this file except in compliance with the License.
1286
+ * You may obtain a copy of the License at
1287
+ *
1288
+ * http://www.apache.org/licenses/LICENSE-2.0
1289
+ *
1290
+ * Unless required by applicable law or agreed to in writing, software
1291
+ * distributed under the License is distributed on an "AS IS" BASIS,
1292
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1293
+ * See the License for the specific language governing permissions and
1294
+ * limitations under the License.
1295
+ */
1296
+ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1297
+ const mapping = new prosemirrorTransform.Mapping();
1298
+ const deleteAttrs = createNewDeleteAttrs(emptyAttrs);
1299
+ let selectionPos = startPos;
1300
+ // @TODO add custom handler / condition?
1301
+ changes.forEach((c) => {
1302
+ let step = newTr.steps[newTr.steps.length - 1];
1303
+ log.info('process change: ', c);
1304
+ // const handled = customStepHandler(changes, newTr, emptyAttrs) // ChangeStep[] | undefined
1305
+ if (c.type === 'delete-node') {
1306
+ const dataTracked = c.node.attrs.dataTracked;
1307
+ const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === exports.CHANGE_OPERATION.insert &&
1308
+ dataTracked.authorID === emptyAttrs.authorID;
1309
+ if (wasInsertedBySameUser) {
1310
+ deleteNode(c.node, mapping.map(c.pos), newTr);
1311
+ const newestStep = newTr.steps[newTr.steps.length - 1];
1312
+ if (step !== newestStep) {
1313
+ mapping.appendMap(newestStep.getMap());
1314
+ step = newestStep;
1315
+ }
1316
+ mergeTrackedMarks(mapping.map(c.pos), newTr.doc, newTr, schema);
1317
+ }
1318
+ else {
1319
+ const attrs = {
1320
+ ...c.node.attrs,
1321
+ dataTracked: addTrackIdIfDoesntExist(deleteAttrs),
1322
+ };
1323
+ newTr.setNodeMarkup(mapping.map(c.pos), undefined, attrs, c.node.marks);
1324
+ }
1325
+ }
1326
+ else if (c.type === 'delete-text') {
1327
+ const from = mapping.map(c.from, -1);
1328
+ const to = mapping.map(c.to, 1);
1329
+ const node = newTr.doc.nodeAt(mapping.map(c.pos));
1330
+ if (node === null || node === void 0 ? void 0 : node.marks.find((m) => m.type === schema.marks.tracked_insert)) {
1331
+ newTr.replaceWith(from, to, prosemirrorModel.Fragment.empty);
1332
+ mergeTrackedMarks(from, newTr.doc, newTr, schema);
1333
+ }
1334
+ else {
1335
+ const leftNode = newTr.doc.resolve(from).nodeBefore;
1336
+ const leftMarks = getMergeableMarkTrackedAttrs(leftNode, deleteAttrs, schema);
1337
+ const rightNode = newTr.doc.resolve(to).nodeAfter;
1338
+ const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
1339
+ const fromStartOfMark = from - (leftNode && leftMarks ? leftNode.nodeSize : 0);
1340
+ const toEndOfMark = to + (rightNode && rightMarks ? rightNode.nodeSize : 0);
1341
+ const dataTracked = addTrackIdIfDoesntExist({
1342
+ ...leftMarks,
1343
+ ...rightMarks,
1344
+ ...deleteAttrs,
1345
+ });
1346
+ newTr.addMark(fromStartOfMark, toEndOfMark, schema.marks.tracked_delete.create({
1347
+ dataTracked,
1348
+ }));
1349
+ }
1350
+ }
1351
+ else if (c.type === 'merge-fragment') {
1352
+ let insertPos = mapping.map(c.mergePos);
1353
+ // The default insert position for block nodes is either the start of the merged content or the end.
1354
+ // Incase text was merged, this must be updated as the start or end of the node doesn't map to the
1355
+ // actual position of the merge. Currently the inserted content is inserted at the start or end
1356
+ // of the merged content, TODO reverse the start/end when end/start token?
1357
+ if (c.node.isText) {
1358
+ // When merging text we must delete text in the same go as well, as the from/to boundary goes through
1359
+ // the text node.
1360
+ insertPos = deleteTextIfInserted(c.node, mapping.map(c.pos), newTr, schema, deleteAttrs, mapping.map(c.from), mapping.map(c.to));
1361
+ const newestStep = newTr.steps[newTr.steps.length - 1];
1362
+ if (step !== newestStep) {
1363
+ mapping.appendMap(newestStep.getMap());
1364
+ step = newestStep;
1365
+ }
1366
+ }
1367
+ if (c.fragment.size > 0) {
1368
+ newTr.insert(insertPos, c.fragment);
1369
+ }
1370
+ }
1371
+ else if (c.type === 'insert-slice') {
1372
+ const newStep = new prosemirrorTransform.ReplaceStep(mapping.map(c.from), mapping.map(c.to), c.slice, false);
1241
1373
  const stepResult = newTr.maybeStep(newStep);
1242
1374
  if (stepResult.failed) {
1243
1375
  log.error(`insert ReplaceStep failed: "${stepResult.failed}"`, newStep);
1244
1376
  return;
1245
1377
  }
1246
- log.info('new steps after applying insert', [...newTr.steps]);
1247
- mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema);
1248
- mergeTrackedMarks(adjustedInsertPos + insertedSlice.size, newTr.doc, newTr, oldState.schema);
1249
- selectionPos = adjustedInsertPos + insertedSlice.size;
1378
+ mergeTrackedMarks(mapping.map(c.from), newTr.doc, newTr, schema);
1379
+ mergeTrackedMarks(mapping.map(c.to), newTr.doc, newTr, schema);
1380
+ selectionPos = mapping.map(c.to) + c.slice.size;
1250
1381
  }
1251
- else {
1252
- // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1253
- mergeTrackedMarks(adjustedInsertPos, newTr.doc, newTr, oldState.schema);
1254
- selectionPos = fromA;
1382
+ else if (c.type === 'update-node-attrs') {
1383
+ const oldDataTracked = c.oldAttrs.dataTracked;
1384
+ const oldAttrs = (oldDataTracked === null || oldDataTracked === void 0 ? void 0 : oldDataTracked.operation) === exports.CHANGE_OPERATION.set_node_attributes
1385
+ ? oldDataTracked.oldAttrs
1386
+ : c.oldAttrs;
1387
+ const dataTracked = addTrackIdIfDoesntExist({
1388
+ ...oldDataTracked,
1389
+ oldAttrs,
1390
+ ...emptyAttrs,
1391
+ operation: exports.CHANGE_OPERATION.set_node_attributes,
1392
+ });
1393
+ newTr.setNodeMarkup(mapping.map(c.pos), undefined, { ...c.newAttrs, dataTracked });
1394
+ }
1395
+ const newestStep = newTr.steps[newTr.steps.length - 1];
1396
+ if (step !== newestStep) {
1397
+ mapping.appendMap(newestStep.getMap());
1398
+ }
1399
+ });
1400
+ return [mapping, selectionPos];
1401
+ }
1402
+
1403
+ /*!
1404
+ * © 2021 Atypon Systems LLC
1405
+ *
1406
+ * Licensed under the Apache License, Version 2.0 (the "License");
1407
+ * you may not use this file except in compliance with the License.
1408
+ * You may obtain a copy of the License at
1409
+ *
1410
+ * http://www.apache.org/licenses/LICENSE-2.0
1411
+ *
1412
+ * Unless required by applicable law or agreed to in writing, software
1413
+ * distributed under the License is distributed on an "AS IS" BASIS,
1414
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1415
+ * See the License for the specific language governing permissions and
1416
+ * limitations under the License.
1417
+ */
1418
+ function matchInserted(inDeleted, deleted, inserted, newTr, schema) {
1419
+ var _a;
1420
+ for (let i = 0;; i += 1) {
1421
+ if (inserted.childCount === i)
1422
+ return [inDeleted, deleted];
1423
+ const child = inserted.child(i);
1424
+ // @ts-ignore
1425
+ let adjDeleted = deleted.find((d) => (d.type === 'delete-text' && d.to === inDeleted) ||
1426
+ (d.type === 'delete-node' && d.nodeEnd === inDeleted));
1427
+ if (child.type !== ((_a = adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node) === null || _a === void 0 ? void 0 : _a.type)) {
1428
+ return [inDeleted, deleted];
1429
+ }
1430
+ else if (child.isText && (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node)) {
1431
+ adjDeleted = adjDeleted;
1432
+ const { pos, from, to, node } = adjDeleted;
1433
+ let j = 0, d = from - pos, maxSteps = Math.max(pos, from) - to;
1434
+ // Match text inside the inserted text node to the deleted text node
1435
+ for (; maxSteps !== j && child.text[j] === node.text[d]; j += 1, d += 1) {
1436
+ inDeleted -= 1;
1437
+ }
1438
+ // this is needed incase diffing tr.doc
1439
+ // deleted.push({
1440
+ // pos: pos,
1441
+ // type: 'update-node-attrs',
1442
+ // // Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
1443
+ // oldAttrs: adjDeleted.node.attrs || {},
1444
+ // newAttrs: child.attrs || {},
1445
+ // })
1446
+ if (maxSteps !== j) {
1447
+ deleted.push({
1448
+ pos,
1449
+ from: Math.max(pos, from) + j,
1450
+ to,
1451
+ type: 'delete-text',
1452
+ node,
1453
+ });
1454
+ }
1455
+ return [inDeleted, deleted.filter((d) => d !== adjDeleted)];
1456
+ }
1457
+ else if (child.content.size > 0 || (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node.content.size) > 0) {
1458
+ // Move the inDeleted inside the block node's boundary
1459
+ return matchInserted(inDeleted - 1, deleted.filter((d) => d !== adjDeleted), child.content);
1460
+ }
1461
+ deleted.push({
1462
+ pos: adjDeleted.pos,
1463
+ type: 'update-node-attrs',
1464
+ // Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
1465
+ oldAttrs: adjDeleted.node.attrs || {},
1466
+ newAttrs: child.attrs || {},
1467
+ });
1468
+ deleted = deleted.filter((d) => d !== adjDeleted);
1469
+ inDeleted -= child.nodeSize;
1470
+ }
1471
+ }
1472
+ function diffChangeSteps(deleted, inserted, newTr, schema) {
1473
+ const updated = [];
1474
+ let updatedDeleted = [...deleted];
1475
+ inserted.forEach((ins) => {
1476
+ const [inDeleted, updatedDel] = matchInserted(ins.from, updatedDeleted, ins.slice.content);
1477
+ if (inDeleted === ins.from) {
1478
+ updated.push(ins);
1479
+ return;
1480
+ }
1481
+ updatedDeleted = updatedDel;
1482
+ const newInsertedA = ins.slice.content.cut(ins.from - inDeleted);
1483
+ const newInsertedB = ins.slice.content.cut(ins.from - inDeleted + 1);
1484
+ // Super hax to cut over block node boundaries in the inserted fragment
1485
+ const newInserted = newInsertedA.size === newInsertedB.size + 2 ? newInsertedB : newInsertedA;
1486
+ if (newInserted.size > 0) {
1487
+ updated.push({
1488
+ ...ins,
1489
+ slice: new prosemirrorModel.Slice(newInserted, ins.slice.openStart, ins.slice.openEnd),
1490
+ });
1255
1491
  }
1256
1492
  });
1257
- return selectionPos;
1493
+ return [...updatedDeleted, ...updated];
1258
1494
  }
1259
1495
 
1260
1496
  /**
@@ -1308,7 +1544,14 @@ function trackTransaction(tr, oldState, newTr, authorID) {
1308
1544
  return;
1309
1545
  }
1310
1546
  else if (step instanceof prosemirrorTransform.ReplaceStep) {
1311
- const selectionPos = trackReplaceStep(step, oldState, newTr, emptyAttrs);
1547
+ let [steps, startPos] = trackReplaceStep(step, oldState, newTr, emptyAttrs);
1548
+ log.info('CHANGES: ', steps);
1549
+ // deleted and merged really...
1550
+ const deleted = steps.filter((s) => s.type !== 'insert-slice');
1551
+ const inserted = steps.filter((s) => s.type === 'insert-slice');
1552
+ steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
1553
+ log.info('DIFFED STEPS: ', steps);
1554
+ const [mapping, selectionPos] = processChangeSteps(steps, startPos, newTr, emptyAttrs, oldState.schema);
1312
1555
  if (!wasNodeSelection) {
1313
1556
  const sel = getSelectionStaticConstructor(tr.selection);
1314
1557
  // Use Selection.near to fix selections that point to a block node instead of inline content
@@ -1319,10 +1562,16 @@ function trackTransaction(tr, oldState, newTr, authorID) {
1319
1562
  }
1320
1563
  }
1321
1564
  else if (step instanceof prosemirrorTransform.ReplaceAroundStep) {
1322
- trackReplaceAroundStep(step, oldState, newTr, emptyAttrs);
1323
- // } else if (step instanceof AddMarkStep) {
1324
- // } else if (step instanceof RemoveMarkStep) {
1565
+ let steps = trackReplaceAroundStep(step, oldState, newTr, emptyAttrs);
1566
+ const deleted = steps.filter((s) => s.type !== 'insert-slice');
1567
+ const inserted = steps.filter((s) => s.type === 'insert-slice');
1568
+ log.info('INSERT STEPS: ', inserted);
1569
+ steps = diffChangeSteps(deleted, inserted, newTr, oldState.schema);
1570
+ log.info('DIFFED STEPS: ', steps);
1571
+ processChangeSteps(steps, tr.selection.from, newTr, emptyAttrs, oldState.schema);
1325
1572
  }
1573
+ // } else if (step instanceof AddMarkStep) {
1574
+ // } else if (step instanceof RemoveMarkStep) {
1326
1575
  // TODO: here we could check whether adjacent inserts & deletes cancel each other out.
1327
1576
  // However, this should not be done by diffing and only matching node or char by char instead since
1328
1577
  // it's A easier and B more intuitive to user.
@@ -1481,21 +1730,6 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1481
1730
  });
1482
1731
  };
1483
1732
 
1484
- /*!
1485
- * © 2021 Atypon Systems LLC
1486
- *
1487
- * Licensed under the Apache License, Version 2.0 (the "License");
1488
- * you may not use this file except in compliance with the License.
1489
- * You may obtain a copy of the License at
1490
- *
1491
- * http://www.apache.org/licenses/LICENSE-2.0
1492
- *
1493
- * Unless required by applicable law or agreed to in writing, software
1494
- * distributed under the License is distributed on an "AS IS" BASIS,
1495
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1496
- * See the License for the specific language governing permissions and
1497
- * limitations under the License.
1498
- */
1499
1733
  /**
1500
1734
  * Sets track-changes plugin's status to any of: 'enabled' 'disabled' 'viewSnapshots'. Passing undefined will
1501
1735
  * set 'enabled' status to 'disabled' and 'disabled' | 'viewSnapshots' status to 'enabled'.