@manuscripts/track-changes-plugin 0.4.0 → 0.4.1

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
@@ -179,8 +179,8 @@ class ChangeSet {
179
179
  get changes() {
180
180
  const iteratedIds = new Set();
181
181
  return __classPrivateFieldGet(this, _ChangeSet_changes, "f").filter((c) => {
182
- const valid = !iteratedIds.has(c.attrs.id) && ChangeSet.isValidTrackedAttrs(c.attrs);
183
- iteratedIds.add(c.attrs.id);
182
+ const valid = !iteratedIds.has(c.dataTracked.id) && ChangeSet.isValidDataTracked(c.dataTracked);
183
+ iteratedIds.add(c.dataTracked.id);
184
184
  return valid;
185
185
  });
186
186
  }
@@ -215,13 +215,13 @@ class ChangeSet {
215
215
  return rootNodes;
216
216
  }
217
217
  get pending() {
218
- return this.changeTree.filter((c) => c.attrs.status === exports.CHANGE_STATUS.pending);
218
+ return this.changeTree.filter((c) => c.dataTracked.status === exports.CHANGE_STATUS.pending);
219
219
  }
220
220
  get accepted() {
221
- return this.changeTree.filter((c) => c.attrs.status === exports.CHANGE_STATUS.accepted);
221
+ return this.changeTree.filter((c) => c.dataTracked.status === exports.CHANGE_STATUS.accepted);
222
222
  }
223
223
  get rejected() {
224
- return this.changeTree.filter((c) => c.attrs.status === exports.CHANGE_STATUS.rejected);
224
+ return this.changeTree.filter((c) => c.dataTracked.status === exports.CHANGE_STATUS.rejected);
225
225
  }
226
226
  get textChanges() {
227
227
  return this.changes.filter((c) => c.type === 'text-change');
@@ -252,7 +252,7 @@ class ChangeSet {
252
252
  });
253
253
  }
254
254
  get hasIncompleteAttrs() {
255
- return __classPrivateFieldGet(this, _ChangeSet_changes, "f").some((c) => !ChangeSet.isValidTrackedAttrs(c.attrs));
255
+ return __classPrivateFieldGet(this, _ChangeSet_changes, "f").some((c) => !ChangeSet.isValidDataTracked(c.dataTracked));
256
256
  }
257
257
  get(id) {
258
258
  return __classPrivateFieldGet(this, _ChangeSet_changes, "f").find((c) => c.id === id);
@@ -272,29 +272,22 @@ class ChangeSet {
272
272
  static flattenTreeToIds(changes) {
273
273
  return changes.flatMap((c) => this.isNodeChange(c) ? [c.id, ...c.children.map((c) => c.id)] : c.id);
274
274
  }
275
- /**
276
- * Determines whether a change should not be deleted when applying it to the document.
277
- * @param change
278
- */
279
- static shouldNotDelete(change) {
280
- return !ChangeSet.shouldDeleteChange(change);
281
- }
282
275
  /**
283
276
  * Determines whether a change should be deleted when applying it to the document.
284
277
  * @param change
285
278
  */
286
279
  static shouldDeleteChange(change) {
287
- const { status, operation } = change.attrs;
280
+ const { status, operation } = change.dataTracked;
288
281
  return ((operation === exports.CHANGE_OPERATION.insert && status === exports.CHANGE_STATUS.rejected) ||
289
282
  (operation === exports.CHANGE_OPERATION.delete && status === exports.CHANGE_STATUS.accepted));
290
283
  }
291
284
  /**
292
285
  * Checks whether change attributes contain all TrackedAttrs keys with non-undefined values
293
- * @param attrs
286
+ * @param dataTracked
294
287
  */
295
- static isValidTrackedAttrs(attrs = {}) {
296
- if ('attrs' in attrs) {
297
- log.warn('passed "attrs" as property to isValidTrackedAttrs(attrs)', attrs);
288
+ static isValidDataTracked(dataTracked = {}) {
289
+ if ('dataTracked' in dataTracked) {
290
+ log.warn('passed "dataTracked" as property to isValidTrackedAttrs()', dataTracked);
298
291
  }
299
292
  const trackedKeys = [
300
293
  'id',
@@ -307,12 +300,12 @@ class ChangeSet {
307
300
  // reviewedByID is set optional since either ProseMirror or Yjs doesn't like persisting null values inside attributes objects
308
301
  // So it can be either omitted completely or at least be null or string
309
302
  const optionalKeys = ['reviewedByID'];
310
- const entries = Object.entries(attrs).filter(([key, val]) => trackedKeys.includes(key));
311
- const optionalEntries = Object.entries(attrs).filter(([key, val]) => optionalKeys.includes(key));
303
+ const entries = Object.entries(dataTracked).filter(([key, val]) => trackedKeys.includes(key));
304
+ const optionalEntries = Object.entries(dataTracked).filter(([key, val]) => optionalKeys.includes(key));
312
305
  return (entries.length === trackedKeys.length &&
313
306
  entries.every(([key, val]) => trackedKeys.includes(key) && val !== undefined) &&
314
307
  optionalEntries.every(([key, val]) => optionalKeys.includes(key) && val !== undefined) &&
315
- (attrs.id || '').length > 0 // Changes created with undefined id have '' as placeholder
308
+ (dataTracked.id || '').length > 0 // Changes created with undefined id have '' as placeholder
316
309
  );
317
310
  }
318
311
  static isTextChange(change) {
@@ -358,8 +351,8 @@ function addTrackIdIfDoesntExist(attrs) {
358
351
  }
359
352
  return attrs;
360
353
  }
361
- function getInlineNodeTrackedMarkData(node, schema) {
362
- if (!node || !node.isInline) {
354
+ function getTextNodeTrackedMarkData(node, schema) {
355
+ if (!node || !node.isText) {
363
356
  return undefined;
364
357
  }
365
358
  const marksTrackedData = [];
@@ -376,12 +369,25 @@ function getInlineNodeTrackedMarkData(node, schema) {
376
369
  }
377
370
  return marksTrackedData[0] || undefined;
378
371
  }
372
+ function getBlockInlineTrackedData(node) {
373
+ let { dataTracked } = node.attrs;
374
+ if (dataTracked && !Array.isArray(dataTracked)) {
375
+ return [dataTracked];
376
+ }
377
+ return dataTracked;
378
+ }
379
379
  function getNodeTrackedData(node, schema) {
380
- return !node
381
- ? undefined
382
- : node.isText
383
- ? getInlineNodeTrackedMarkData(node, schema)
384
- : node.attrs.dataTracked;
380
+ let tracked;
381
+ if (node && !node.isText) {
382
+ tracked = getBlockInlineTrackedData(node);
383
+ }
384
+ else if (node === null || node === void 0 ? void 0 : node.isText) {
385
+ tracked = getTextNodeTrackedMarkData(node, schema);
386
+ }
387
+ if (tracked && !Array.isArray(tracked)) {
388
+ tracked = [tracked];
389
+ }
390
+ return tracked;
385
391
  }
386
392
  function equalMarks(n1, n2) {
387
393
  return (n1.marks.length === n2.marks.length &&
@@ -400,7 +406,7 @@ function shouldMergeTrackedAttributes(left, right) {
400
406
  left.authorID === right.authorID);
401
407
  }
402
408
  function getMergeableMarkTrackedAttrs(node, attrs, schema) {
403
- const nodeAttrs = getInlineNodeTrackedMarkData(node, schema);
409
+ const nodeAttrs = getTextNodeTrackedMarkData(node, schema);
404
410
  return nodeAttrs && shouldMergeTrackedAttributes(nodeAttrs, attrs) ? nodeAttrs : null;
405
411
  }
406
412
 
@@ -449,6 +455,29 @@ function deleteNode(node, pos, tr) {
449
455
  // child, say some wrapper blockNode, is also deleted the content could be retained. TODO I guess.
450
456
  return tr.delete(pos, pos + node.nodeSize);
451
457
  }
458
+ }
459
+ /**
460
+ * Deletes inserted block or inline node, otherwise adds `dataTracked` object with CHANGE_STATUS 'deleted'
461
+ * @param node
462
+ * @param pos
463
+ * @param newTr
464
+ * @param deleteAttrs
465
+ */
466
+ function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
467
+ const dataTracked = getBlockInlineTrackedData(node);
468
+ const inserted = dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.find((d) => d.operation === exports.CHANGE_OPERATION.insert);
469
+ const deleted = dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.find((d) => d.operation === exports.CHANGE_OPERATION.delete);
470
+ const updated = dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.find((d) => d.operation === exports.CHANGE_OPERATION.set_node_attributes);
471
+ if (inserted && inserted.authorID === deleteAttrs.authorID) {
472
+ return deleteNode(node, pos, newTr);
473
+ }
474
+ const newDeleted = deleted
475
+ ? { ...deleted, updatedAt: deleteAttrs.updatedAt }
476
+ : addTrackIdIfDoesntExist(deleteAttrs);
477
+ newTr.setNodeMarkup(pos, undefined, {
478
+ ...node.attrs,
479
+ dataTracked: updated ? [newDeleted, updated] : [newDeleted],
480
+ }, node.marks);
452
481
  }
453
482
 
454
483
  /*!
@@ -494,21 +523,46 @@ function mergeNode(node, pos, tr) {
494
523
  function updateChangeAttrs(tr, change, trackedAttrs, schema) {
495
524
  const node = tr.doc.nodeAt(change.from);
496
525
  if (!node) {
497
- throw Error('No node at the from of change' + change);
526
+ log.error('updateChangeAttrs: no node at the from of change ', change);
527
+ return tr;
528
+ }
529
+ const { operation } = trackedAttrs;
530
+ const oldTrackData = change.type === 'text-change'
531
+ ? getTextNodeTrackedMarkData(node, schema)
532
+ : getBlockInlineTrackedData(node);
533
+ if (!operation) {
534
+ log.warn('updateChangeAttrs: unable to determine operation of change ', change);
535
+ }
536
+ else if (!oldTrackData) {
537
+ log.warn('updateChangeAttrs: no old dataTracked for change ', change);
538
+ }
539
+ if (change.type === 'text-change') {
540
+ const oldMark = node.marks.find((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete);
541
+ if (!oldMark) {
542
+ log.warn('updateChangeAttrs: no track marks for a text-change ', change);
543
+ return tr;
544
+ }
545
+ // TODO add operation based on mark type if it's undefined?
546
+ tr.addMark(change.from, change.to, oldMark.type.create({ ...oldMark.attrs, dataTracked: { ...oldTrackData, ...trackedAttrs } }));
498
547
  }
499
- const dataTracked = { ...getNodeTrackedData(node, schema), ...trackedAttrs };
500
- const oldMark = node.marks.find((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete);
501
- if (change.type === 'text-change' && oldMark) {
502
- tr.addMark(change.from, change.to, oldMark.type.create({ ...oldMark.attrs, dataTracked }));
548
+ else if (change.type === 'node-change' && !operation) {
549
+ // Very weird edge-case if this happens
550
+ tr.setNodeMarkup(change.from, undefined, { ...node.attrs, dataTracked: null }, node.marks);
503
551
  }
504
552
  else if (change.type === 'node-change') {
505
- tr.setNodeMarkup(change.from, undefined, { ...node.attrs, dataTracked }, node.marks);
553
+ const newDataTracked = (getBlockInlineTrackedData(node) || []).map((oldTrack) => {
554
+ if (oldTrack.operation === operation) {
555
+ return { ...oldTrack, ...trackedAttrs };
556
+ }
557
+ return oldTrack;
558
+ });
559
+ tr.setNodeMarkup(change.from, undefined, { ...node.attrs, dataTracked: newDataTracked.length === 0 ? null : newDataTracked }, node.marks);
506
560
  }
507
561
  return tr;
508
562
  }
509
563
  function updateChangeChildrenAttributes(changes, tr, mapping) {
510
564
  changes.forEach((c) => {
511
- if (c.type === 'node-change' && ChangeSet.shouldNotDelete(c)) {
565
+ if (c.type === 'node-change' && !ChangeSet.shouldDeleteChange(c)) {
512
566
  const from = mapping.map(c.from);
513
567
  const node = tr.doc.nodeAt(from);
514
568
  if (!node) {
@@ -530,12 +584,12 @@ function updateChangeChildrenAttributes(changes, tr, mapping) {
530
584
  */
531
585
  function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new prosemirrorTransform.Mapping()) {
532
586
  changes.forEach((change) => {
533
- if (change.attrs.status === exports.CHANGE_STATUS.pending) {
587
+ if (change.dataTracked.status === exports.CHANGE_STATUS.pending) {
534
588
  return;
535
589
  }
536
590
  // Map change.from and skip those which dont need to be applied
537
591
  // or were already deleted by an applied block delete
538
- const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || ChangeSet.shouldNotDelete(change);
592
+ const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || !ChangeSet.shouldDeleteChange(change);
539
593
  if (!node) {
540
594
  !deleted && log.warn('no node found to update for change', change);
541
595
  return;
@@ -564,12 +618,12 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new prose
564
618
  deleteMap.appendMap(tr.steps[tr.steps.length - 1].getMap());
565
619
  }
566
620
  else if (ChangeSet.isNodeAttrChange(change) &&
567
- change.attrs.status === exports.CHANGE_STATUS.accepted) {
621
+ change.dataTracked.status === exports.CHANGE_STATUS.accepted) {
568
622
  const attrs = { ...node.attrs, dataTracked: null };
569
623
  tr.setNodeMarkup(from, undefined, attrs, node.marks);
570
624
  }
571
625
  else if (ChangeSet.isNodeAttrChange(change) &&
572
- change.attrs.status === exports.CHANGE_STATUS.rejected) {
626
+ change.dataTracked.status === exports.CHANGE_STATUS.rejected) {
573
627
  const attrs = { ...change.oldAttrs, dataTracked: null };
574
628
  tr.setNodeMarkup(from, undefined, attrs, node.marks);
575
629
  }
@@ -591,9 +645,10 @@ function findChanges(state) {
591
645
  // Store the last iterated change to join adjacent text changes
592
646
  let current;
593
647
  state.doc.descendants((node, pos) => {
594
- const attrs = getNodeTrackedData(node, state.schema);
595
- if (attrs) {
596
- const id = (attrs === null || attrs === void 0 ? void 0 : attrs.id) || '';
648
+ const tracked = getNodeTrackedData(node, state.schema) || [];
649
+ for (let i = 0; i < tracked.length; i += 1) {
650
+ const dataTracked = tracked[i];
651
+ const id = dataTracked.id || '';
597
652
  // Join adjacent text changes that have been broken up due to different marks
598
653
  // eg <ins><b>bold</b>norm<i>italic</i></ins> -> treated as one continuous change
599
654
  // Note the !equalMarks to leave changes separate incase the marks are equal to let fixInconsistentChanges to fix them
@@ -605,38 +660,49 @@ function findChanges(state) {
605
660
  current.change.to = pos + node.nodeSize;
606
661
  // Important to update the node as the text changes might contain multiple parts where some marks equal each other
607
662
  current.node = node;
663
+ continue;
608
664
  }
609
- else if (node.isText) {
610
- current && changes.push(current.change);
611
- current = {
612
- change: {
613
- id,
614
- type: 'text-change',
615
- from: pos,
616
- to: pos + node.nodeSize,
617
- text: node.text,
618
- attrs,
619
- },
620
- node,
665
+ current && changes.push(current.change);
666
+ let change;
667
+ if (node.isText) {
668
+ change = {
669
+ id,
670
+ type: 'text-change',
671
+ from: pos,
672
+ to: pos + node.nodeSize,
673
+ dataTracked,
674
+ text: node.text,
675
+ };
676
+ }
677
+ else if (dataTracked.operation === exports.CHANGE_OPERATION.set_node_attributes) {
678
+ change = {
679
+ id,
680
+ type: 'node-attr-change',
681
+ from: pos,
682
+ to: pos + node.nodeSize,
683
+ dataTracked,
684
+ nodeType: node.type.name,
685
+ newAttrs: node.attrs,
686
+ oldAttrs: dataTracked.oldAttrs,
621
687
  };
622
688
  }
623
689
  else {
624
- current && changes.push(current.change);
625
- current = {
626
- change: {
627
- id,
628
- type: 'node-change',
629
- from: pos,
630
- to: pos + node.nodeSize,
631
- nodeType: node.type.name,
632
- children: [],
633
- attrs,
634
- },
635
- node,
690
+ change = {
691
+ id,
692
+ type: 'node-change',
693
+ from: pos,
694
+ to: pos + node.nodeSize,
695
+ dataTracked,
696
+ nodeType: node.type.name,
697
+ children: [],
636
698
  };
637
699
  }
700
+ current = {
701
+ change,
702
+ node,
703
+ };
638
704
  }
639
- else if (current) {
705
+ if (tracked.length === 0 && current) {
640
706
  changes.push(current.change);
641
707
  current = undefined;
642
708
  }
@@ -655,22 +721,23 @@ function findChanges(state) {
655
721
  * @param schema
656
722
  * @return docWasChanged, a boolean
657
723
  */
658
- function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
724
+ function fixInconsistentChanges(changeSet, currentUserID, newTr, schema) {
659
725
  const iteratedIds = new Set();
660
726
  let changed = false;
661
727
  changeSet.invalidChanges.forEach((c) => {
662
- const { id, authorID, operation, reviewedByID, status, createdAt, updatedAt } = c.attrs;
728
+ const { id, authorID, operation, reviewedByID, status, createdAt, updatedAt } = c.dataTracked;
663
729
  const newAttrs = {
664
730
  ...((!id || iteratedIds.has(id) || id.length === 0) && { id: uuidv4() }),
665
- ...(!authorID && { authorID: trackUserID }),
666
- ...(!operation && { operation: exports.CHANGE_OPERATION.insert }),
731
+ ...(!authorID && { authorID: currentUserID }),
732
+ // Dont add a default operation -> rather have updateChangeAttrs delete the track data
733
+ // ...(!operation && { operation: CHANGE_OPERATION.insert }),
667
734
  ...(!reviewedByID && { reviewedByID: null }),
668
735
  ...(!status && { status: exports.CHANGE_STATUS.pending }),
669
736
  ...(!createdAt && { createdAt: Date.now() }),
670
737
  ...(!updatedAt && { updatedAt: Date.now() }),
671
738
  };
672
739
  if (Object.keys(newAttrs).length > 0) {
673
- updateChangeAttrs(newTr, c, { ...c.attrs, ...newAttrs }, schema);
740
+ updateChangeAttrs(newTr, c, { ...c.dataTracked, ...newAttrs }, schema);
674
741
  changed = true;
675
742
  }
676
743
  iteratedIds.add(newAttrs.id || id);
@@ -800,18 +867,43 @@ function markInlineNodeChange(node, newTrackAttrs, schema) {
800
867
  });
801
868
  return node.mark(filtered.concat(createdMark));
802
869
  }
870
+ /**
871
+ * Iterates over fragment's content and joins pasted text with old track marks
872
+ *
873
+ * This is not strictly necessary but it's kinda bad UX if the inserted text is split into parts
874
+ * even when it's authored by the same user.
875
+ * @param content
876
+ * @param newTrackAttrs
877
+ * @param schema
878
+ * @returns
879
+ */
880
+ function loopContentAndMergeText(content, newTrackAttrs, schema) {
881
+ var _a;
882
+ const updatedChildren = [];
883
+ for (let i = 0; i < content.childCount; i += 1) {
884
+ const recursed = recurseNodeContent(content.child(i), newTrackAttrs, schema);
885
+ const prev = i > 0 ? updatedChildren[i - 1] : null;
886
+ if ((prev === null || prev === void 0 ? void 0 : prev.isText) &&
887
+ recursed.isText &&
888
+ equalMarks(prev, recursed) &&
889
+ ((_a = getTextNodeTrackedMarkData(prev, schema)) === null || _a === void 0 ? void 0 : _a.operation) === exports.CHANGE_OPERATION.insert) {
890
+ updatedChildren.splice(i - 1, 1, schema.text('' + prev.text + recursed.text, prev.marks));
891
+ }
892
+ else {
893
+ updatedChildren.push(recursed);
894
+ }
895
+ }
896
+ return updatedChildren;
897
+ }
803
898
  function recurseNodeContent(node, newTrackAttrs, schema) {
804
899
  if (node.isText) {
805
900
  return markInlineNodeChange(node, newTrackAttrs, schema);
806
901
  }
807
902
  else if (node.isBlock || node.isInline) {
808
- const updatedChildren = [];
809
- node.content.forEach((child) => {
810
- updatedChildren.push(recurseNodeContent(child, newTrackAttrs, schema));
811
- });
903
+ const updatedChildren = loopContentAndMergeText(node.content, newTrackAttrs, schema);
812
904
  return node.type.create({
813
905
  ...node.attrs,
814
- dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
906
+ dataTracked: [addTrackIdIfDoesntExist(newTrackAttrs)],
815
907
  }, prosemirrorModel.Fragment.fromArray(updatedChildren), node.marks);
816
908
  }
817
909
  else {
@@ -821,11 +913,8 @@ function recurseNodeContent(node, newTrackAttrs, schema) {
821
913
  }
822
914
  function setFragmentAsInserted(inserted, insertAttrs, schema) {
823
915
  // 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);
916
+ const updatedInserted = loopContentAndMergeText(inserted, insertAttrs, schema);
917
+ return updatedInserted.length === 0 ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.fromArray(updatedInserted);
829
918
  }
830
919
 
831
920
  /*!
@@ -854,6 +943,13 @@ function createNewDeleteAttrs(attrs) {
854
943
  ...attrs,
855
944
  operation: exports.CHANGE_OPERATION.delete,
856
945
  };
946
+ }
947
+ function createNewUpdateAttrs(attrs, oldAttrs) {
948
+ return {
949
+ ...attrs,
950
+ operation: exports.CHANGE_OPERATION.set_node_attributes,
951
+ oldAttrs,
952
+ };
857
953
  }
858
954
 
859
955
  /*!
@@ -897,13 +993,12 @@ function createNewDeleteAttrs(attrs) {
897
993
  * @returns mapping adjusted by the applied operations & modified insert slice
898
994
  */
899
995
  function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackAttrs, insertSlice) {
900
- const deleteMap = new prosemirrorTransform.Mapping();
901
996
  const steps = [];
902
997
  // No deletion applied, return default values
903
998
  if (from === to) {
904
999
  return {
905
- deleteMap,
906
1000
  newSliceContent: insertSlice.content,
1001
+ sliceWasSplit: false,
907
1002
  steps,
908
1003
  };
909
1004
  }
@@ -911,10 +1006,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
911
1006
  const { updatedSliceNodes, firstMergedNode, lastMergedNode } = splitSliceIntoMergedParts(insertSlice, gap !== undefined);
912
1007
  let mergingStartSide = true;
913
1008
  startDoc.nodesBetween(from, to, (node, pos) => {
914
- const { pos: offsetPos, deleted: nodeWasDeleted } = deleteMap.mapResult(pos, 1);
915
- const offsetFrom = deleteMap.map(from, -1);
916
- const offsetTo = deleteMap.map(to, 1);
917
- const nodeEnd = offsetPos + node.nodeSize;
1009
+ const nodeEnd = pos + node.nodeSize;
918
1010
  // So this insane boolean checks for ReplaceAroundStep gaps and whether the node should be skipped
919
1011
  // since the content inside gap should stay unchanged.
920
1012
  // All other nodes except text nodes consist of one start and end token (or just a single token for atoms).
@@ -925,23 +1017,13 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
925
1017
  // are altered and should not be skipped.
926
1018
  // @TODO ATM 20.7.2022 there doesn't seem to be tests that capture this.
927
1019
  const wasWithinGap = gap &&
928
- ((!node.isText && offsetPos >= deleteMap.map(gap.start, -1)) ||
929
- (node.isText &&
930
- offsetPos <= deleteMap.map(gap.start, -1) &&
931
- nodeEnd >= deleteMap.map(gap.end, -1)));
932
- let step = newTr.steps[newTr.steps.length - 1];
1020
+ ((!node.isText && pos >= gap.start) ||
1021
+ (node.isText && pos <= gap.start && nodeEnd >= gap.start));
933
1022
  // nodeEnd > offsetFrom -> delete touches this node
934
1023
  // eg (del 6 10) <p 5>|<t 6>cdf</t 9></p 10>| -> <p> nodeEnd 10 > from 6
935
- //
936
- // !nodeWasDeleted -> Check node wasn't already deleted by a previous deleteNode
937
- // This is quite tricky to wrap your head around and I've forgotten the nitty-gritty details already.
938
- // But from what I remember what it safeguards against is, when you've already deleted a node
939
- // say an inserted blockquote that had all its children deleted, nodesBetween still iterates over those
940
- // nodes and therefore we have to make this check to ensure they still exist in the doc.
941
- //
942
- if (nodeEnd > offsetFrom && !nodeWasDeleted && !wasWithinGap) {
1024
+ if (nodeEnd > from && !wasWithinGap) {
943
1025
  // |<p>asdf</p>| -> node deleted completely
944
- const nodeCompletelyDeleted = offsetPos >= offsetFrom && nodeEnd <= offsetTo;
1026
+ const nodeCompletelyDeleted = pos >= from && nodeEnd <= to;
945
1027
  // The end token deleted eg:
946
1028
  // <p 1>asdf|</p 7><p 7>bye</p 12>| + [<p>]hello</p> -> <p>asdfhello</p>
947
1029
  // (del 6 12) + (ins [<p>]hello</p> openStart 1 openEnd 0)
@@ -952,14 +1034,14 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
952
1034
  //
953
1035
  // What about:
954
1036
  // <p 1>asdf|</p 7><p 7 op="inserted">|bye</p 12> + empty -> <p>asdfbye</p>
955
- const endTokenDeleted = nodeEnd <= offsetTo;
1037
+ const endTokenDeleted = nodeEnd <= to;
956
1038
  // The start token deleted eg:
957
1039
  // |<p1 0>hey</p 6><p2 6>|asdf</p 12> + <p3>hello [</p>] -> <p3>hello asdf</p2>
958
1040
  // (del 0 7) + (ins <p>hello [</p>] openStart 0 openEnd 1)
959
1041
  // (<p1> pos 0) >= (from 0) && (nodeEnd 6) - 1 > (to 7) == false???
960
1042
  // (<p2> pos 6) >= (from 0) && (nodeEnd 12) - 1 > (to 7) == true
961
1043
  //
962
- const startTokenDeleted = offsetPos >= offsetFrom; // && nodeEnd - 1 > offsetTo
1044
+ const startTokenDeleted = pos >= from; // && nodeEnd - 1 > offsetTo
963
1045
  if (node.isText ||
964
1046
  (!endTokenDeleted && startTokenDeleted) ||
965
1047
  (endTokenDeleted && !startTokenDeleted)) {
@@ -971,7 +1053,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
971
1053
  }
972
1054
  // Depth is often 1 when merging paragraphs or 2 for fully open blockquotes.
973
1055
  // Incase of merging text within a ReplaceAroundStep the depth might be 1
974
- const depth = newTr.doc.resolve(offsetPos).depth;
1056
+ const depth = newTr.doc.resolve(pos).depth;
975
1057
  const mergeContent = mergingStartSide
976
1058
  ? firstMergedNode === null || firstMergedNode === void 0 ? void 0 : firstMergedNode.mergedNodeContent
977
1059
  : lastMergedNode === null || lastMergedNode === void 0 ? void 0 : lastMergedNode.mergedNodeContent;
@@ -989,10 +1071,10 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
989
1071
  // the paragraph.
990
1072
  steps.push({
991
1073
  type: 'merge-fragment',
992
- pos: offsetPos,
993
- mergePos: mergeStartNode ? nodeEnd - openStart : offsetPos + openEnd,
994
- from: offsetFrom,
995
- to: offsetTo,
1074
+ pos,
1075
+ mergePos: mergeStartNode ? nodeEnd - openStart : pos + openEnd,
1076
+ from,
1077
+ to,
996
1078
  node,
997
1079
  fragment: setFragmentAsInserted(mergeContent, createNewInsertAttrs(trackAttrs), schema),
998
1080
  });
@@ -1010,9 +1092,9 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1010
1092
  // (which is basically the case most of the time)
1011
1093
  steps.push({
1012
1094
  type: 'delete-text',
1013
- pos: offsetPos,
1014
- from: Math.max(offsetPos, offsetFrom),
1015
- to: Math.min(nodeEnd, offsetTo),
1095
+ pos,
1096
+ from: Math.max(pos, from),
1097
+ to: Math.min(nodeEnd, to),
1016
1098
  node,
1017
1099
  });
1018
1100
  }
@@ -1021,19 +1103,15 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1021
1103
  else if (nodeCompletelyDeleted) {
1022
1104
  steps.push({
1023
1105
  type: 'delete-node',
1024
- pos: offsetPos,
1025
- nodeEnd: nodeEnd,
1106
+ pos,
1107
+ nodeEnd,
1026
1108
  node,
1027
1109
  });
1028
1110
  }
1029
1111
  }
1030
- const newestStep = newTr.steps[newTr.steps.length - 1];
1031
- if (step !== newestStep) {
1032
- deleteMap.appendMap(newestStep.getMap());
1033
- }
1034
1112
  });
1035
1113
  return {
1036
- deleteMap,
1114
+ sliceWasSplit: !!(firstMergedNode || lastMergedNode),
1037
1115
  newSliceContent: updatedSliceNodes
1038
1116
  ? prosemirrorModel.Fragment.fromArray(updatedSliceNodes)
1039
1117
  : insertSlice.content,
@@ -1127,7 +1205,7 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1127
1205
  log.info('RETAINED GAP CONTENT', gap);
1128
1206
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1129
1207
  // eg partial nodes in an open-ended slice
1130
- const { deleteMap, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1208
+ const { sliceWasSplit, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1131
1209
  let steps = deleteSteps;
1132
1210
  log.info('TR: new steps after applying delete', [...newTr.steps]);
1133
1211
  log.info('DELETE STEPS: ', deleteSteps);
@@ -1147,15 +1225,16 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1147
1225
  }
1148
1226
  deleteSteps.push({
1149
1227
  type: 'insert-slice',
1150
- from: deleteMap.map(gapFrom),
1151
- to: deleteMap.map(gapTo),
1228
+ from: gapFrom,
1229
+ to: gapTo,
1152
1230
  slice: insertedSlice,
1231
+ sliceWasSplit,
1153
1232
  });
1154
1233
  }
1155
1234
  else {
1156
1235
  // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1157
- mergeTrackedMarks(deleteMap.map(gapFrom), newTr.doc, newTr, oldState.schema);
1158
- mergeTrackedMarks(deleteMap.map(gapTo), newTr.doc, newTr, oldState.schema);
1236
+ mergeTrackedMarks(gapFrom, newTr.doc, newTr, oldState.schema);
1237
+ mergeTrackedMarks(gapTo, newTr.doc, newTr, oldState.schema);
1159
1238
  }
1160
1239
  return steps;
1161
1240
  }
@@ -1191,11 +1270,11 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1191
1270
  log.info('TR: steps before applying delete', [...newTr.steps]);
1192
1271
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1193
1272
  // eg partial nodes in an open-ended slice
1194
- const { deleteMap, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(fromA, toA, undefined, oldState.doc, newTr, oldState.schema, attrs, slice);
1273
+ const { sliceWasSplit, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(fromA, toA, undefined, oldState.doc, newTr, oldState.schema, attrs, slice);
1195
1274
  changeSteps.push(...deleteSteps);
1196
1275
  log.info('TR: steps after applying delete', [...newTr.steps]);
1197
1276
  log.info('DELETE STEPS: ', changeSteps);
1198
- const adjustedInsertPos = deleteMap.map(toA);
1277
+ const adjustedInsertPos = toA; // deleteMap.map(toA)
1199
1278
  if (newSliceContent.size > 0) {
1200
1279
  log.info('newSliceContent', newSliceContent);
1201
1280
  // Since deleteAndMergeSplitBlockNodes modified the slice to not to contain any merged nodes,
@@ -1206,6 +1285,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1206
1285
  type: 'insert-slice',
1207
1286
  from: adjustedInsertPos,
1208
1287
  to: adjustedInsertPos,
1288
+ sliceWasSplit,
1209
1289
  slice: new prosemirrorModel.Slice(setFragmentAsInserted(newSliceContent, createNewInsertAttrs(attrs), oldState.schema), openStart, openEnd),
1210
1290
  });
1211
1291
  }
@@ -1278,21 +1358,6 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1278
1358
  }
1279
1359
  }
1280
1360
 
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
1361
  function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1297
1362
  const mapping = new prosemirrorTransform.Mapping();
1298
1363
  const deleteAttrs = createNewDeleteAttrs(emptyAttrs);
@@ -1303,50 +1368,22 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1303
1368
  log.info('process change: ', c);
1304
1369
  // const handled = customStepHandler(changes, newTr, emptyAttrs) // ChangeStep[] | undefined
1305
1370
  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);
1371
+ deleteOrSetNodeDeleted(c.node, mapping.map(c.pos), newTr, deleteAttrs);
1372
+ const newestStep = newTr.steps[newTr.steps.length - 1];
1373
+ if (step !== newestStep) {
1374
+ mapping.appendMap(newestStep.getMap());
1375
+ step = newestStep;
1324
1376
  }
1377
+ mergeTrackedMarks(mapping.map(c.pos), newTr.doc, newTr, schema);
1325
1378
  }
1326
1379
  else if (c.type === 'delete-text') {
1327
- const from = mapping.map(c.from, -1);
1328
- const to = mapping.map(c.to, 1);
1329
1380
  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
- }));
1381
+ if (!node) {
1382
+ log.error(`processChangeSteps: no text node found for text-change`, c);
1383
+ return;
1349
1384
  }
1385
+ const where = deleteTextIfInserted(node, mapping.map(c.pos), newTr, schema, deleteAttrs, mapping.map(c.from), mapping.map(c.to));
1386
+ mergeTrackedMarks(where, newTr.doc, newTr, schema);
1350
1387
  }
1351
1388
  else if (c.type === 'merge-fragment') {
1352
1389
  let insertPos = mapping.map(c.mergePos);
@@ -1372,7 +1409,7 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1372
1409
  const newStep = new prosemirrorTransform.ReplaceStep(mapping.map(c.from), mapping.map(c.to), c.slice, false);
1373
1410
  const stepResult = newTr.maybeStep(newStep);
1374
1411
  if (stepResult.failed) {
1375
- log.error(`insert ReplaceStep failed: "${stepResult.failed}"`, newStep);
1412
+ log.error(`processChangeSteps: insert-slice ReplaceStep failed "${stepResult.failed}"`, newStep);
1376
1413
  return;
1377
1414
  }
1378
1415
  mergeTrackedMarks(mapping.map(c.from), newTr.doc, newTr, schema);
@@ -1380,17 +1417,29 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1380
1417
  selectionPos = mapping.map(c.to) + c.slice.size;
1381
1418
  }
1382
1419
  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 });
1420
+ const oldDataTracked = getBlockInlineTrackedData(c.node) || [];
1421
+ const oldUpdate = oldDataTracked.find((d) => d.operation === exports.CHANGE_OPERATION.set_node_attributes);
1422
+ let newDataTracked = oldDataTracked;
1423
+ if (oldUpdate) {
1424
+ newDataTracked = [
1425
+ ...oldDataTracked.filter((d) => d === oldUpdate),
1426
+ {
1427
+ ...oldUpdate,
1428
+ updatedAt: emptyAttrs.updatedAt,
1429
+ },
1430
+ ];
1431
+ }
1432
+ else if (oldDataTracked.length === 0 ||
1433
+ oldDataTracked.find((d) => d.operation === exports.CHANGE_OPERATION.delete)) {
1434
+ newDataTracked = [
1435
+ ...oldDataTracked,
1436
+ addTrackIdIfDoesntExist(createNewUpdateAttrs(emptyAttrs, c.node.attrs)),
1437
+ ];
1438
+ }
1439
+ newTr.setNodeMarkup(mapping.map(c.pos), undefined, {
1440
+ ...c.newAttrs,
1441
+ dataTracked: newDataTracked.length > 0 ? newDataTracked : null,
1442
+ }, c.node.marks);
1394
1443
  }
1395
1444
  const newestStep = newTr.steps[newTr.steps.length - 1];
1396
1445
  if (step !== newestStep) {
@@ -1415,25 +1464,26 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1415
1464
  * See the License for the specific language governing permissions and
1416
1465
  * limitations under the License.
1417
1466
  */
1418
- function matchInserted(inDeleted, deleted, inserted, newTr, schema) {
1467
+ function matchInserted(matchedDeleted, deleted, inserted, newTr, schema) {
1419
1468
  var _a;
1469
+ let matched = [matchedDeleted, deleted];
1420
1470
  for (let i = 0;; i += 1) {
1421
1471
  if (inserted.childCount === i)
1422
- return [inDeleted, deleted];
1423
- const child = inserted.child(i);
1472
+ return matched;
1473
+ const insNode = inserted.child(i);
1424
1474
  // @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];
1475
+ let adjDeleted = matched[1].find((d) => (d.type === 'delete-text' && Math.max(d.pos, d.from) === matched[0]) ||
1476
+ (d.type === 'delete-node' && d.pos === matched[0]));
1477
+ if (insNode.type !== ((_a = adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node) === null || _a === void 0 ? void 0 : _a.type)) {
1478
+ return matched;
1429
1479
  }
1430
- else if (child.isText && (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node)) {
1480
+ else if (insNode.isText && (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node)) {
1431
1481
  adjDeleted = adjDeleted;
1432
- const { pos, from, to, node } = adjDeleted;
1433
- let j = 0, d = from - pos, maxSteps = Math.max(pos, from) - to;
1482
+ const { pos, from, to, node: delNode } = adjDeleted;
1483
+ let j = 0, d = from - pos, maxSteps = to - Math.max(pos, from);
1434
1484
  // 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;
1485
+ for (; maxSteps !== j && insNode.text[j] !== undefined && insNode.text[j] === delNode.text[d]; j += 1, d += 1) {
1486
+ matched[0] += 1;
1437
1487
  }
1438
1488
  // this is needed incase diffing tr.doc
1439
1489
  // deleted.push({
@@ -1443,46 +1493,108 @@ function matchInserted(inDeleted, deleted, inserted, newTr, schema) {
1443
1493
  // oldAttrs: adjDeleted.node.attrs || {},
1444
1494
  // newAttrs: child.attrs || {},
1445
1495
  // })
1496
+ matched = [matched[0], matched[1].filter((d) => d !== adjDeleted)];
1446
1497
  if (maxSteps !== j) {
1447
- deleted.push({
1498
+ matched[1].push({
1448
1499
  pos,
1449
1500
  from: Math.max(pos, from) + j,
1450
1501
  to,
1451
1502
  type: 'delete-text',
1452
- node,
1503
+ node: delNode,
1453
1504
  });
1505
+ return matched;
1454
1506
  }
1455
- return [inDeleted, deleted.filter((d) => d !== adjDeleted)];
1507
+ continue;
1456
1508
  }
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);
1509
+ else if (insNode.content.size > 0 || (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node.content.size) > 0) {
1510
+ // Move the inDeleted inside the block/inline node's boundary
1511
+ matched = matchInserted(matched[0] + 1, matched[1].filter((d) => d !== adjDeleted), insNode.content);
1460
1512
  }
1461
- deleted.push({
1513
+ else {
1514
+ matched = [matched[0] + insNode.nodeSize, matched[1].filter((d) => d !== adjDeleted)];
1515
+ }
1516
+ matched[1].push({
1462
1517
  pos: adjDeleted.pos,
1463
1518
  type: 'update-node-attrs',
1519
+ node: adjDeleted.node,
1464
1520
  // Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
1465
- oldAttrs: adjDeleted.node.attrs || {},
1466
- newAttrs: child.attrs || {},
1521
+ newAttrs: insNode.attrs || {},
1467
1522
  });
1468
- deleted = deleted.filter((d) => d !== adjDeleted);
1469
- inDeleted -= child.nodeSize;
1470
1523
  }
1471
1524
  }
1525
+ /**
1526
+ * Cuts a fragment similar to Fragment.cut but also removes the parent node.
1527
+ *
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
+ * @param matched
1532
+ * @param deleted
1533
+ * @param content
1534
+ * @returns
1535
+ */
1536
+ function cutFragment(matched, deleted, content) {
1537
+ let newContent = [];
1538
+ for (let i = 0; matched <= deleted && i < content.childCount; i += 1) {
1539
+ const child = content.child(i);
1540
+ if (!child.isText && child.content.size > 0) {
1541
+ const cut = cutFragment(matched + 1, deleted, child.content);
1542
+ matched = cut[0];
1543
+ newContent.push(...cut[1].content);
1544
+ }
1545
+ else if (child.isText && matched + child.nodeSize > deleted) {
1546
+ if (matched - deleted + 1 > 0) {
1547
+ newContent.push(child.cut(0, matched - deleted + 1));
1548
+ }
1549
+ else {
1550
+ newContent.push(child);
1551
+ }
1552
+ matched = deleted + 1;
1553
+ }
1554
+ else {
1555
+ matched += child.nodeSize;
1556
+ }
1557
+ }
1558
+ return [matched, prosemirrorModel.Fragment.fromArray(newContent)];
1559
+ }
1472
1560
  function diffChangeSteps(deleted, inserted, newTr, schema) {
1473
1561
  const updated = [];
1474
1562
  let updatedDeleted = [...deleted];
1475
1563
  inserted.forEach((ins) => {
1476
- const [inDeleted, updatedDel] = matchInserted(ins.from, updatedDeleted, ins.slice.content);
1477
- if (inDeleted === ins.from) {
1564
+ log.info('DIFF ins ', ins);
1565
+ //
1566
+ // @TODO this is a temporary workaround to prevent duplicated diffing between splitSliceIntoMergedParts and
1567
+ // matchInserted.
1568
+ //
1569
+ // As originally authored splitSliceIntoMergedParts splits open slices into their merged parts
1570
+ // leaving out the need to insert the possibly deleted nodes into the doc. However, as matchInserted now
1571
+ // traverses the deleted range checking it against the inserted slice this behaves quite in a same way
1572
+ // where the opened block nodes are traversed but left unmodified. With an openStart > 0 though the
1573
+ // node-attr-updates would additionally have to be filtered out in the processChangeSteps.
1574
+ //
1575
+ // The old logic is still left as it's as refactoring is painful and would probably break something and just
1576
+ // in general, take a lot of time. Therefore, this sliceWasSplit boolean is used to just skip diffing.
1577
+ if (ins.sliceWasSplit) {
1578
+ updated.push(ins);
1579
+ return;
1580
+ }
1581
+ // Start diffing from the start of the deleted range
1582
+ const deleteStart = deleted.reduce((acc, cur) => {
1583
+ if (cur.type === 'delete-node') {
1584
+ return Math.min(acc, cur.pos);
1585
+ }
1586
+ else if (cur.type === 'delete-text') {
1587
+ return Math.min(acc, cur.from);
1588
+ }
1589
+ return acc;
1590
+ }, Number.MAX_SAFE_INTEGER);
1591
+ const [inDeleted, updatedDel] = matchInserted(deleteStart, updatedDeleted, ins.slice.content);
1592
+ if (inDeleted === deleteStart) {
1478
1593
  updated.push(ins);
1479
1594
  return;
1480
1595
  }
1481
1596
  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;
1597
+ const newInserted = cutFragment(0, inDeleted, ins.slice.content)[1];
1486
1598
  if (newInserted.size > 0) {
1487
1599
  updated.push({
1488
1600
  ...ins,
@@ -1490,6 +1602,7 @@ function diffChangeSteps(deleted, inserted, newTr, schema) {
1490
1602
  });
1491
1603
  }
1492
1604
  });
1605
+ log.info('FINISH DIFF: ', [...updatedDeleted, ...updated]);
1493
1606
  return [...updatedDeleted, ...updated];
1494
1607
  }
1495
1608
 
@@ -1705,10 +1818,10 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1705
1818
  ids.forEach((changeId) => {
1706
1819
  const change = changeSet === null || changeSet === void 0 ? void 0 : changeSet.get(changeId);
1707
1820
  if (change) {
1708
- createdTr = updateChangeAttrs(createdTr, change, { status, reviewedByID: userID }, oldState.schema);
1709
- setAction(createdTr, TrackChangesAction.updateChanges, [change.id]);
1821
+ createdTr = updateChangeAttrs(createdTr, change, { ...change.dataTracked, status, reviewedByID: userID }, oldState.schema);
1710
1822
  }
1711
1823
  });
1824
+ setAction(createdTr, TrackChangesAction.updateChanges, ids);
1712
1825
  }
1713
1826
  else if (getAction(tr, TrackChangesAction.applyAndRemoveChanges)) {
1714
1827
  const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.nodeChanges);