@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.js CHANGED
@@ -171,8 +171,8 @@ class ChangeSet {
171
171
  get changes() {
172
172
  const iteratedIds = new Set();
173
173
  return __classPrivateFieldGet(this, _ChangeSet_changes, "f").filter((c) => {
174
- const valid = !iteratedIds.has(c.attrs.id) && ChangeSet.isValidTrackedAttrs(c.attrs);
175
- iteratedIds.add(c.attrs.id);
174
+ const valid = !iteratedIds.has(c.dataTracked.id) && ChangeSet.isValidDataTracked(c.dataTracked);
175
+ iteratedIds.add(c.dataTracked.id);
176
176
  return valid;
177
177
  });
178
178
  }
@@ -207,13 +207,13 @@ class ChangeSet {
207
207
  return rootNodes;
208
208
  }
209
209
  get pending() {
210
- return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.pending);
210
+ return this.changeTree.filter((c) => c.dataTracked.status === CHANGE_STATUS.pending);
211
211
  }
212
212
  get accepted() {
213
- return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.accepted);
213
+ return this.changeTree.filter((c) => c.dataTracked.status === CHANGE_STATUS.accepted);
214
214
  }
215
215
  get rejected() {
216
- return this.changeTree.filter((c) => c.attrs.status === CHANGE_STATUS.rejected);
216
+ return this.changeTree.filter((c) => c.dataTracked.status === CHANGE_STATUS.rejected);
217
217
  }
218
218
  get textChanges() {
219
219
  return this.changes.filter((c) => c.type === 'text-change');
@@ -244,7 +244,7 @@ class ChangeSet {
244
244
  });
245
245
  }
246
246
  get hasIncompleteAttrs() {
247
- return __classPrivateFieldGet(this, _ChangeSet_changes, "f").some((c) => !ChangeSet.isValidTrackedAttrs(c.attrs));
247
+ return __classPrivateFieldGet(this, _ChangeSet_changes, "f").some((c) => !ChangeSet.isValidDataTracked(c.dataTracked));
248
248
  }
249
249
  get(id) {
250
250
  return __classPrivateFieldGet(this, _ChangeSet_changes, "f").find((c) => c.id === id);
@@ -264,29 +264,22 @@ class ChangeSet {
264
264
  static flattenTreeToIds(changes) {
265
265
  return changes.flatMap((c) => this.isNodeChange(c) ? [c.id, ...c.children.map((c) => c.id)] : c.id);
266
266
  }
267
- /**
268
- * Determines whether a change should not be deleted when applying it to the document.
269
- * @param change
270
- */
271
- static shouldNotDelete(change) {
272
- return !ChangeSet.shouldDeleteChange(change);
273
- }
274
267
  /**
275
268
  * Determines whether a change should be deleted when applying it to the document.
276
269
  * @param change
277
270
  */
278
271
  static shouldDeleteChange(change) {
279
- const { status, operation } = change.attrs;
272
+ const { status, operation } = change.dataTracked;
280
273
  return ((operation === CHANGE_OPERATION.insert && status === CHANGE_STATUS.rejected) ||
281
274
  (operation === CHANGE_OPERATION.delete && status === CHANGE_STATUS.accepted));
282
275
  }
283
276
  /**
284
277
  * Checks whether change attributes contain all TrackedAttrs keys with non-undefined values
285
- * @param attrs
278
+ * @param dataTracked
286
279
  */
287
- static isValidTrackedAttrs(attrs = {}) {
288
- if ('attrs' in attrs) {
289
- log.warn('passed "attrs" as property to isValidTrackedAttrs(attrs)', attrs);
280
+ static isValidDataTracked(dataTracked = {}) {
281
+ if ('dataTracked' in dataTracked) {
282
+ log.warn('passed "dataTracked" as property to isValidTrackedAttrs()', dataTracked);
290
283
  }
291
284
  const trackedKeys = [
292
285
  'id',
@@ -299,12 +292,12 @@ class ChangeSet {
299
292
  // reviewedByID is set optional since either ProseMirror or Yjs doesn't like persisting null values inside attributes objects
300
293
  // So it can be either omitted completely or at least be null or string
301
294
  const optionalKeys = ['reviewedByID'];
302
- const entries = Object.entries(attrs).filter(([key, val]) => trackedKeys.includes(key));
303
- const optionalEntries = Object.entries(attrs).filter(([key, val]) => optionalKeys.includes(key));
295
+ const entries = Object.entries(dataTracked).filter(([key, val]) => trackedKeys.includes(key));
296
+ const optionalEntries = Object.entries(dataTracked).filter(([key, val]) => optionalKeys.includes(key));
304
297
  return (entries.length === trackedKeys.length &&
305
298
  entries.every(([key, val]) => trackedKeys.includes(key) && val !== undefined) &&
306
299
  optionalEntries.every(([key, val]) => optionalKeys.includes(key) && val !== undefined) &&
307
- (attrs.id || '').length > 0 // Changes created with undefined id have '' as placeholder
300
+ (dataTracked.id || '').length > 0 // Changes created with undefined id have '' as placeholder
308
301
  );
309
302
  }
310
303
  static isTextChange(change) {
@@ -350,8 +343,8 @@ function addTrackIdIfDoesntExist(attrs) {
350
343
  }
351
344
  return attrs;
352
345
  }
353
- function getInlineNodeTrackedMarkData(node, schema) {
354
- if (!node || !node.isInline) {
346
+ function getTextNodeTrackedMarkData(node, schema) {
347
+ if (!node || !node.isText) {
355
348
  return undefined;
356
349
  }
357
350
  const marksTrackedData = [];
@@ -368,12 +361,25 @@ function getInlineNodeTrackedMarkData(node, schema) {
368
361
  }
369
362
  return marksTrackedData[0] || undefined;
370
363
  }
364
+ function getBlockInlineTrackedData(node) {
365
+ let { dataTracked } = node.attrs;
366
+ if (dataTracked && !Array.isArray(dataTracked)) {
367
+ return [dataTracked];
368
+ }
369
+ return dataTracked;
370
+ }
371
371
  function getNodeTrackedData(node, schema) {
372
- return !node
373
- ? undefined
374
- : node.isText
375
- ? getInlineNodeTrackedMarkData(node, schema)
376
- : node.attrs.dataTracked;
372
+ let tracked;
373
+ if (node && !node.isText) {
374
+ tracked = getBlockInlineTrackedData(node);
375
+ }
376
+ else if (node === null || node === void 0 ? void 0 : node.isText) {
377
+ tracked = getTextNodeTrackedMarkData(node, schema);
378
+ }
379
+ if (tracked && !Array.isArray(tracked)) {
380
+ tracked = [tracked];
381
+ }
382
+ return tracked;
377
383
  }
378
384
  function equalMarks(n1, n2) {
379
385
  return (n1.marks.length === n2.marks.length &&
@@ -392,7 +398,7 @@ function shouldMergeTrackedAttributes(left, right) {
392
398
  left.authorID === right.authorID);
393
399
  }
394
400
  function getMergeableMarkTrackedAttrs(node, attrs, schema) {
395
- const nodeAttrs = getInlineNodeTrackedMarkData(node, schema);
401
+ const nodeAttrs = getTextNodeTrackedMarkData(node, schema);
396
402
  return nodeAttrs && shouldMergeTrackedAttributes(nodeAttrs, attrs) ? nodeAttrs : null;
397
403
  }
398
404
 
@@ -441,6 +447,29 @@ function deleteNode(node, pos, tr) {
441
447
  // child, say some wrapper blockNode, is also deleted the content could be retained. TODO I guess.
442
448
  return tr.delete(pos, pos + node.nodeSize);
443
449
  }
450
+ }
451
+ /**
452
+ * Deletes inserted block or inline node, otherwise adds `dataTracked` object with CHANGE_STATUS 'deleted'
453
+ * @param node
454
+ * @param pos
455
+ * @param newTr
456
+ * @param deleteAttrs
457
+ */
458
+ function deleteOrSetNodeDeleted(node, pos, newTr, deleteAttrs) {
459
+ const dataTracked = getBlockInlineTrackedData(node);
460
+ const inserted = dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.find((d) => d.operation === CHANGE_OPERATION.insert);
461
+ const deleted = dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.find((d) => d.operation === CHANGE_OPERATION.delete);
462
+ const updated = dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.find((d) => d.operation === CHANGE_OPERATION.set_node_attributes);
463
+ if (inserted && inserted.authorID === deleteAttrs.authorID) {
464
+ return deleteNode(node, pos, newTr);
465
+ }
466
+ const newDeleted = deleted
467
+ ? { ...deleted, updatedAt: deleteAttrs.updatedAt }
468
+ : addTrackIdIfDoesntExist(deleteAttrs);
469
+ newTr.setNodeMarkup(pos, undefined, {
470
+ ...node.attrs,
471
+ dataTracked: updated ? [newDeleted, updated] : [newDeleted],
472
+ }, node.marks);
444
473
  }
445
474
 
446
475
  /*!
@@ -486,21 +515,46 @@ function mergeNode(node, pos, tr) {
486
515
  function updateChangeAttrs(tr, change, trackedAttrs, schema) {
487
516
  const node = tr.doc.nodeAt(change.from);
488
517
  if (!node) {
489
- throw Error('No node at the from of change' + change);
518
+ log.error('updateChangeAttrs: no node at the from of change ', change);
519
+ return tr;
520
+ }
521
+ const { operation } = trackedAttrs;
522
+ const oldTrackData = change.type === 'text-change'
523
+ ? getTextNodeTrackedMarkData(node, schema)
524
+ : getBlockInlineTrackedData(node);
525
+ if (!operation) {
526
+ log.warn('updateChangeAttrs: unable to determine operation of change ', change);
527
+ }
528
+ else if (!oldTrackData) {
529
+ log.warn('updateChangeAttrs: no old dataTracked for change ', change);
530
+ }
531
+ if (change.type === 'text-change') {
532
+ const oldMark = node.marks.find((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete);
533
+ if (!oldMark) {
534
+ log.warn('updateChangeAttrs: no track marks for a text-change ', change);
535
+ return tr;
536
+ }
537
+ // TODO add operation based on mark type if it's undefined?
538
+ tr.addMark(change.from, change.to, oldMark.type.create({ ...oldMark.attrs, dataTracked: { ...oldTrackData, ...trackedAttrs } }));
490
539
  }
491
- const dataTracked = { ...getNodeTrackedData(node, schema), ...trackedAttrs };
492
- const oldMark = node.marks.find((m) => m.type === schema.marks.tracked_insert || m.type === schema.marks.tracked_delete);
493
- if (change.type === 'text-change' && oldMark) {
494
- tr.addMark(change.from, change.to, oldMark.type.create({ ...oldMark.attrs, dataTracked }));
540
+ else if (change.type === 'node-change' && !operation) {
541
+ // Very weird edge-case if this happens
542
+ tr.setNodeMarkup(change.from, undefined, { ...node.attrs, dataTracked: null }, node.marks);
495
543
  }
496
544
  else if (change.type === 'node-change') {
497
- tr.setNodeMarkup(change.from, undefined, { ...node.attrs, dataTracked }, node.marks);
545
+ const newDataTracked = (getBlockInlineTrackedData(node) || []).map((oldTrack) => {
546
+ if (oldTrack.operation === operation) {
547
+ return { ...oldTrack, ...trackedAttrs };
548
+ }
549
+ return oldTrack;
550
+ });
551
+ tr.setNodeMarkup(change.from, undefined, { ...node.attrs, dataTracked: newDataTracked.length === 0 ? null : newDataTracked }, node.marks);
498
552
  }
499
553
  return tr;
500
554
  }
501
555
  function updateChangeChildrenAttributes(changes, tr, mapping) {
502
556
  changes.forEach((c) => {
503
- if (c.type === 'node-change' && ChangeSet.shouldNotDelete(c)) {
557
+ if (c.type === 'node-change' && !ChangeSet.shouldDeleteChange(c)) {
504
558
  const from = mapping.map(c.from);
505
559
  const node = tr.doc.nodeAt(from);
506
560
  if (!node) {
@@ -522,12 +576,12 @@ function updateChangeChildrenAttributes(changes, tr, mapping) {
522
576
  */
523
577
  function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mapping()) {
524
578
  changes.forEach((change) => {
525
- if (change.attrs.status === CHANGE_STATUS.pending) {
579
+ if (change.dataTracked.status === CHANGE_STATUS.pending) {
526
580
  return;
527
581
  }
528
582
  // Map change.from and skip those which dont need to be applied
529
583
  // or were already deleted by an applied block delete
530
- const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || ChangeSet.shouldNotDelete(change);
584
+ const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || !ChangeSet.shouldDeleteChange(change);
531
585
  if (!node) {
532
586
  !deleted && log.warn('no node found to update for change', change);
533
587
  return;
@@ -556,12 +610,12 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mappi
556
610
  deleteMap.appendMap(tr.steps[tr.steps.length - 1].getMap());
557
611
  }
558
612
  else if (ChangeSet.isNodeAttrChange(change) &&
559
- change.attrs.status === CHANGE_STATUS.accepted) {
613
+ change.dataTracked.status === CHANGE_STATUS.accepted) {
560
614
  const attrs = { ...node.attrs, dataTracked: null };
561
615
  tr.setNodeMarkup(from, undefined, attrs, node.marks);
562
616
  }
563
617
  else if (ChangeSet.isNodeAttrChange(change) &&
564
- change.attrs.status === CHANGE_STATUS.rejected) {
618
+ change.dataTracked.status === CHANGE_STATUS.rejected) {
565
619
  const attrs = { ...change.oldAttrs, dataTracked: null };
566
620
  tr.setNodeMarkup(from, undefined, attrs, node.marks);
567
621
  }
@@ -583,9 +637,10 @@ function findChanges(state) {
583
637
  // Store the last iterated change to join adjacent text changes
584
638
  let current;
585
639
  state.doc.descendants((node, pos) => {
586
- const attrs = getNodeTrackedData(node, state.schema);
587
- if (attrs) {
588
- const id = (attrs === null || attrs === void 0 ? void 0 : attrs.id) || '';
640
+ const tracked = getNodeTrackedData(node, state.schema) || [];
641
+ for (let i = 0; i < tracked.length; i += 1) {
642
+ const dataTracked = tracked[i];
643
+ const id = dataTracked.id || '';
589
644
  // Join adjacent text changes that have been broken up due to different marks
590
645
  // eg <ins><b>bold</b>norm<i>italic</i></ins> -> treated as one continuous change
591
646
  // Note the !equalMarks to leave changes separate incase the marks are equal to let fixInconsistentChanges to fix them
@@ -597,38 +652,49 @@ function findChanges(state) {
597
652
  current.change.to = pos + node.nodeSize;
598
653
  // Important to update the node as the text changes might contain multiple parts where some marks equal each other
599
654
  current.node = node;
655
+ continue;
600
656
  }
601
- else if (node.isText) {
602
- current && changes.push(current.change);
603
- current = {
604
- change: {
605
- id,
606
- type: 'text-change',
607
- from: pos,
608
- to: pos + node.nodeSize,
609
- text: node.text,
610
- attrs,
611
- },
612
- node,
657
+ current && changes.push(current.change);
658
+ let change;
659
+ if (node.isText) {
660
+ change = {
661
+ id,
662
+ type: 'text-change',
663
+ from: pos,
664
+ to: pos + node.nodeSize,
665
+ dataTracked,
666
+ text: node.text,
667
+ };
668
+ }
669
+ else if (dataTracked.operation === CHANGE_OPERATION.set_node_attributes) {
670
+ change = {
671
+ id,
672
+ type: 'node-attr-change',
673
+ from: pos,
674
+ to: pos + node.nodeSize,
675
+ dataTracked,
676
+ nodeType: node.type.name,
677
+ newAttrs: node.attrs,
678
+ oldAttrs: dataTracked.oldAttrs,
613
679
  };
614
680
  }
615
681
  else {
616
- current && changes.push(current.change);
617
- current = {
618
- change: {
619
- id,
620
- type: 'node-change',
621
- from: pos,
622
- to: pos + node.nodeSize,
623
- nodeType: node.type.name,
624
- children: [],
625
- attrs,
626
- },
627
- node,
682
+ change = {
683
+ id,
684
+ type: 'node-change',
685
+ from: pos,
686
+ to: pos + node.nodeSize,
687
+ dataTracked,
688
+ nodeType: node.type.name,
689
+ children: [],
628
690
  };
629
691
  }
692
+ current = {
693
+ change,
694
+ node,
695
+ };
630
696
  }
631
- else if (current) {
697
+ if (tracked.length === 0 && current) {
632
698
  changes.push(current.change);
633
699
  current = undefined;
634
700
  }
@@ -647,22 +713,23 @@ function findChanges(state) {
647
713
  * @param schema
648
714
  * @return docWasChanged, a boolean
649
715
  */
650
- function fixInconsistentChanges(changeSet, trackUserID, newTr, schema) {
716
+ function fixInconsistentChanges(changeSet, currentUserID, newTr, schema) {
651
717
  const iteratedIds = new Set();
652
718
  let changed = false;
653
719
  changeSet.invalidChanges.forEach((c) => {
654
- const { id, authorID, operation, reviewedByID, status, createdAt, updatedAt } = c.attrs;
720
+ const { id, authorID, operation, reviewedByID, status, createdAt, updatedAt } = c.dataTracked;
655
721
  const newAttrs = {
656
722
  ...((!id || iteratedIds.has(id) || id.length === 0) && { id: uuidv4() }),
657
- ...(!authorID && { authorID: trackUserID }),
658
- ...(!operation && { operation: CHANGE_OPERATION.insert }),
723
+ ...(!authorID && { authorID: currentUserID }),
724
+ // Dont add a default operation -> rather have updateChangeAttrs delete the track data
725
+ // ...(!operation && { operation: CHANGE_OPERATION.insert }),
659
726
  ...(!reviewedByID && { reviewedByID: null }),
660
727
  ...(!status && { status: CHANGE_STATUS.pending }),
661
728
  ...(!createdAt && { createdAt: Date.now() }),
662
729
  ...(!updatedAt && { updatedAt: Date.now() }),
663
730
  };
664
731
  if (Object.keys(newAttrs).length > 0) {
665
- updateChangeAttrs(newTr, c, { ...c.attrs, ...newAttrs }, schema);
732
+ updateChangeAttrs(newTr, c, { ...c.dataTracked, ...newAttrs }, schema);
666
733
  changed = true;
667
734
  }
668
735
  iteratedIds.add(newAttrs.id || id);
@@ -792,18 +859,43 @@ function markInlineNodeChange(node, newTrackAttrs, schema) {
792
859
  });
793
860
  return node.mark(filtered.concat(createdMark));
794
861
  }
862
+ /**
863
+ * Iterates over fragment's content and joins pasted text with old track marks
864
+ *
865
+ * This is not strictly necessary but it's kinda bad UX if the inserted text is split into parts
866
+ * even when it's authored by the same user.
867
+ * @param content
868
+ * @param newTrackAttrs
869
+ * @param schema
870
+ * @returns
871
+ */
872
+ function loopContentAndMergeText(content, newTrackAttrs, schema) {
873
+ var _a;
874
+ const updatedChildren = [];
875
+ for (let i = 0; i < content.childCount; i += 1) {
876
+ const recursed = recurseNodeContent(content.child(i), newTrackAttrs, schema);
877
+ const prev = i > 0 ? updatedChildren[i - 1] : null;
878
+ if ((prev === null || prev === void 0 ? void 0 : prev.isText) &&
879
+ recursed.isText &&
880
+ equalMarks(prev, recursed) &&
881
+ ((_a = getTextNodeTrackedMarkData(prev, schema)) === null || _a === void 0 ? void 0 : _a.operation) === CHANGE_OPERATION.insert) {
882
+ updatedChildren.splice(i - 1, 1, schema.text('' + prev.text + recursed.text, prev.marks));
883
+ }
884
+ else {
885
+ updatedChildren.push(recursed);
886
+ }
887
+ }
888
+ return updatedChildren;
889
+ }
795
890
  function recurseNodeContent(node, newTrackAttrs, schema) {
796
891
  if (node.isText) {
797
892
  return markInlineNodeChange(node, newTrackAttrs, schema);
798
893
  }
799
894
  else if (node.isBlock || node.isInline) {
800
- const updatedChildren = [];
801
- node.content.forEach((child) => {
802
- updatedChildren.push(recurseNodeContent(child, newTrackAttrs, schema));
803
- });
895
+ const updatedChildren = loopContentAndMergeText(node.content, newTrackAttrs, schema);
804
896
  return node.type.create({
805
897
  ...node.attrs,
806
- dataTracked: addTrackIdIfDoesntExist(newTrackAttrs),
898
+ dataTracked: [addTrackIdIfDoesntExist(newTrackAttrs)],
807
899
  }, Fragment.fromArray(updatedChildren), node.marks);
808
900
  }
809
901
  else {
@@ -813,11 +905,8 @@ function recurseNodeContent(node, newTrackAttrs, schema) {
813
905
  }
814
906
  function setFragmentAsInserted(inserted, insertAttrs, schema) {
815
907
  // Recurse the content in the inserted slice and either mark it tracked_insert or set node attrs
816
- const updatedInserted = [];
817
- inserted.forEach((n) => {
818
- updatedInserted.push(recurseNodeContent(n, insertAttrs, schema));
819
- });
820
- return updatedInserted.length === 0 ? inserted : Fragment.fromArray(updatedInserted);
908
+ const updatedInserted = loopContentAndMergeText(inserted, insertAttrs, schema);
909
+ return updatedInserted.length === 0 ? Fragment.empty : Fragment.fromArray(updatedInserted);
821
910
  }
822
911
 
823
912
  /*!
@@ -846,6 +935,13 @@ function createNewDeleteAttrs(attrs) {
846
935
  ...attrs,
847
936
  operation: CHANGE_OPERATION.delete,
848
937
  };
938
+ }
939
+ function createNewUpdateAttrs(attrs, oldAttrs) {
940
+ return {
941
+ ...attrs,
942
+ operation: CHANGE_OPERATION.set_node_attributes,
943
+ oldAttrs,
944
+ };
849
945
  }
850
946
 
851
947
  /*!
@@ -889,13 +985,12 @@ function createNewDeleteAttrs(attrs) {
889
985
  * @returns mapping adjusted by the applied operations & modified insert slice
890
986
  */
891
987
  function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackAttrs, insertSlice) {
892
- const deleteMap = new Mapping();
893
988
  const steps = [];
894
989
  // No deletion applied, return default values
895
990
  if (from === to) {
896
991
  return {
897
- deleteMap,
898
992
  newSliceContent: insertSlice.content,
993
+ sliceWasSplit: false,
899
994
  steps,
900
995
  };
901
996
  }
@@ -903,10 +998,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
903
998
  const { updatedSliceNodes, firstMergedNode, lastMergedNode } = splitSliceIntoMergedParts(insertSlice, gap !== undefined);
904
999
  let mergingStartSide = true;
905
1000
  startDoc.nodesBetween(from, to, (node, pos) => {
906
- const { pos: offsetPos, deleted: nodeWasDeleted } = deleteMap.mapResult(pos, 1);
907
- const offsetFrom = deleteMap.map(from, -1);
908
- const offsetTo = deleteMap.map(to, 1);
909
- const nodeEnd = offsetPos + node.nodeSize;
1001
+ const nodeEnd = pos + node.nodeSize;
910
1002
  // So this insane boolean checks for ReplaceAroundStep gaps and whether the node should be skipped
911
1003
  // since the content inside gap should stay unchanged.
912
1004
  // All other nodes except text nodes consist of one start and end token (or just a single token for atoms).
@@ -917,23 +1009,13 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
917
1009
  // are altered and should not be skipped.
918
1010
  // @TODO ATM 20.7.2022 there doesn't seem to be tests that capture this.
919
1011
  const wasWithinGap = gap &&
920
- ((!node.isText && offsetPos >= deleteMap.map(gap.start, -1)) ||
921
- (node.isText &&
922
- offsetPos <= deleteMap.map(gap.start, -1) &&
923
- nodeEnd >= deleteMap.map(gap.end, -1)));
924
- let step = newTr.steps[newTr.steps.length - 1];
1012
+ ((!node.isText && pos >= gap.start) ||
1013
+ (node.isText && pos <= gap.start && nodeEnd >= gap.start));
925
1014
  // nodeEnd > offsetFrom -> delete touches this node
926
1015
  // eg (del 6 10) <p 5>|<t 6>cdf</t 9></p 10>| -> <p> nodeEnd 10 > from 6
927
- //
928
- // !nodeWasDeleted -> Check node wasn't already deleted by a previous deleteNode
929
- // This is quite tricky to wrap your head around and I've forgotten the nitty-gritty details already.
930
- // But from what I remember what it safeguards against is, when you've already deleted a node
931
- // say an inserted blockquote that had all its children deleted, nodesBetween still iterates over those
932
- // nodes and therefore we have to make this check to ensure they still exist in the doc.
933
- //
934
- if (nodeEnd > offsetFrom && !nodeWasDeleted && !wasWithinGap) {
1016
+ if (nodeEnd > from && !wasWithinGap) {
935
1017
  // |<p>asdf</p>| -> node deleted completely
936
- const nodeCompletelyDeleted = offsetPos >= offsetFrom && nodeEnd <= offsetTo;
1018
+ const nodeCompletelyDeleted = pos >= from && nodeEnd <= to;
937
1019
  // The end token deleted eg:
938
1020
  // <p 1>asdf|</p 7><p 7>bye</p 12>| + [<p>]hello</p> -> <p>asdfhello</p>
939
1021
  // (del 6 12) + (ins [<p>]hello</p> openStart 1 openEnd 0)
@@ -944,14 +1026,14 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
944
1026
  //
945
1027
  // What about:
946
1028
  // <p 1>asdf|</p 7><p 7 op="inserted">|bye</p 12> + empty -> <p>asdfbye</p>
947
- const endTokenDeleted = nodeEnd <= offsetTo;
1029
+ const endTokenDeleted = nodeEnd <= to;
948
1030
  // The start token deleted eg:
949
1031
  // |<p1 0>hey</p 6><p2 6>|asdf</p 12> + <p3>hello [</p>] -> <p3>hello asdf</p2>
950
1032
  // (del 0 7) + (ins <p>hello [</p>] openStart 0 openEnd 1)
951
1033
  // (<p1> pos 0) >= (from 0) && (nodeEnd 6) - 1 > (to 7) == false???
952
1034
  // (<p2> pos 6) >= (from 0) && (nodeEnd 12) - 1 > (to 7) == true
953
1035
  //
954
- const startTokenDeleted = offsetPos >= offsetFrom; // && nodeEnd - 1 > offsetTo
1036
+ const startTokenDeleted = pos >= from; // && nodeEnd - 1 > offsetTo
955
1037
  if (node.isText ||
956
1038
  (!endTokenDeleted && startTokenDeleted) ||
957
1039
  (endTokenDeleted && !startTokenDeleted)) {
@@ -963,7 +1045,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
963
1045
  }
964
1046
  // Depth is often 1 when merging paragraphs or 2 for fully open blockquotes.
965
1047
  // Incase of merging text within a ReplaceAroundStep the depth might be 1
966
- const depth = newTr.doc.resolve(offsetPos).depth;
1048
+ const depth = newTr.doc.resolve(pos).depth;
967
1049
  const mergeContent = mergingStartSide
968
1050
  ? firstMergedNode === null || firstMergedNode === void 0 ? void 0 : firstMergedNode.mergedNodeContent
969
1051
  : lastMergedNode === null || lastMergedNode === void 0 ? void 0 : lastMergedNode.mergedNodeContent;
@@ -981,10 +1063,10 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
981
1063
  // the paragraph.
982
1064
  steps.push({
983
1065
  type: 'merge-fragment',
984
- pos: offsetPos,
985
- mergePos: mergeStartNode ? nodeEnd - openStart : offsetPos + openEnd,
986
- from: offsetFrom,
987
- to: offsetTo,
1066
+ pos,
1067
+ mergePos: mergeStartNode ? nodeEnd - openStart : pos + openEnd,
1068
+ from,
1069
+ to,
988
1070
  node,
989
1071
  fragment: setFragmentAsInserted(mergeContent, createNewInsertAttrs(trackAttrs), schema),
990
1072
  });
@@ -1002,9 +1084,9 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1002
1084
  // (which is basically the case most of the time)
1003
1085
  steps.push({
1004
1086
  type: 'delete-text',
1005
- pos: offsetPos,
1006
- from: Math.max(offsetPos, offsetFrom),
1007
- to: Math.min(nodeEnd, offsetTo),
1087
+ pos,
1088
+ from: Math.max(pos, from),
1089
+ to: Math.min(nodeEnd, to),
1008
1090
  node,
1009
1091
  });
1010
1092
  }
@@ -1013,19 +1095,15 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
1013
1095
  else if (nodeCompletelyDeleted) {
1014
1096
  steps.push({
1015
1097
  type: 'delete-node',
1016
- pos: offsetPos,
1017
- nodeEnd: nodeEnd,
1098
+ pos,
1099
+ nodeEnd,
1018
1100
  node,
1019
1101
  });
1020
1102
  }
1021
1103
  }
1022
- const newestStep = newTr.steps[newTr.steps.length - 1];
1023
- if (step !== newestStep) {
1024
- deleteMap.appendMap(newestStep.getMap());
1025
- }
1026
1104
  });
1027
1105
  return {
1028
- deleteMap,
1106
+ sliceWasSplit: !!(firstMergedNode || lastMergedNode),
1029
1107
  newSliceContent: updatedSliceNodes
1030
1108
  ? Fragment.fromArray(updatedSliceNodes)
1031
1109
  : insertSlice.content,
@@ -1119,7 +1197,7 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1119
1197
  log.info('RETAINED GAP CONTENT', gap);
1120
1198
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1121
1199
  // eg partial nodes in an open-ended slice
1122
- const { deleteMap, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1200
+ const { sliceWasSplit, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(from, to, { start: gapFrom, end: gapTo }, newTr.doc, newTr, oldState.schema, attrs, slice);
1123
1201
  let steps = deleteSteps;
1124
1202
  log.info('TR: new steps after applying delete', [...newTr.steps]);
1125
1203
  log.info('DELETE STEPS: ', deleteSteps);
@@ -1139,15 +1217,16 @@ function trackReplaceAroundStep(step, oldState, newTr, attrs) {
1139
1217
  }
1140
1218
  deleteSteps.push({
1141
1219
  type: 'insert-slice',
1142
- from: deleteMap.map(gapFrom),
1143
- to: deleteMap.map(gapTo),
1220
+ from: gapFrom,
1221
+ to: gapTo,
1144
1222
  slice: insertedSlice,
1223
+ sliceWasSplit,
1145
1224
  });
1146
1225
  }
1147
1226
  else {
1148
1227
  // Incase only deletion was applied, check whether tracked marks around deleted content can be merged
1149
- mergeTrackedMarks(deleteMap.map(gapFrom), newTr.doc, newTr, oldState.schema);
1150
- mergeTrackedMarks(deleteMap.map(gapTo), newTr.doc, newTr, oldState.schema);
1228
+ mergeTrackedMarks(gapFrom, newTr.doc, newTr, oldState.schema);
1229
+ mergeTrackedMarks(gapTo, newTr.doc, newTr, oldState.schema);
1151
1230
  }
1152
1231
  return steps;
1153
1232
  }
@@ -1183,11 +1262,11 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1183
1262
  log.info('TR: steps before applying delete', [...newTr.steps]);
1184
1263
  // First apply the deleted range and update the insert slice to not include content that was deleted,
1185
1264
  // eg partial nodes in an open-ended slice
1186
- const { deleteMap, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(fromA, toA, undefined, oldState.doc, newTr, oldState.schema, attrs, slice);
1265
+ const { sliceWasSplit, newSliceContent, steps: deleteSteps, } = deleteAndMergeSplitNodes(fromA, toA, undefined, oldState.doc, newTr, oldState.schema, attrs, slice);
1187
1266
  changeSteps.push(...deleteSteps);
1188
1267
  log.info('TR: steps after applying delete', [...newTr.steps]);
1189
1268
  log.info('DELETE STEPS: ', changeSteps);
1190
- const adjustedInsertPos = deleteMap.map(toA);
1269
+ const adjustedInsertPos = toA; // deleteMap.map(toA)
1191
1270
  if (newSliceContent.size > 0) {
1192
1271
  log.info('newSliceContent', newSliceContent);
1193
1272
  // Since deleteAndMergeSplitBlockNodes modified the slice to not to contain any merged nodes,
@@ -1198,6 +1277,7 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1198
1277
  type: 'insert-slice',
1199
1278
  from: adjustedInsertPos,
1200
1279
  to: adjustedInsertPos,
1280
+ sliceWasSplit,
1201
1281
  slice: new Slice(setFragmentAsInserted(newSliceContent, createNewInsertAttrs(attrs), oldState.schema), openStart, openEnd),
1202
1282
  });
1203
1283
  }
@@ -1270,21 +1350,6 @@ function deleteTextIfInserted(node, pos, newTr, schema, deleteAttrs, from, to) {
1270
1350
  }
1271
1351
  }
1272
1352
 
1273
- /*!
1274
- * © 2021 Atypon Systems LLC
1275
- *
1276
- * Licensed under the Apache License, Version 2.0 (the "License");
1277
- * you may not use this file except in compliance with the License.
1278
- * You may obtain a copy of the License at
1279
- *
1280
- * http://www.apache.org/licenses/LICENSE-2.0
1281
- *
1282
- * Unless required by applicable law or agreed to in writing, software
1283
- * distributed under the License is distributed on an "AS IS" BASIS,
1284
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1285
- * See the License for the specific language governing permissions and
1286
- * limitations under the License.
1287
- */
1288
1353
  function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1289
1354
  const mapping = new Mapping();
1290
1355
  const deleteAttrs = createNewDeleteAttrs(emptyAttrs);
@@ -1295,50 +1360,22 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1295
1360
  log.info('process change: ', c);
1296
1361
  // const handled = customStepHandler(changes, newTr, emptyAttrs) // ChangeStep[] | undefined
1297
1362
  if (c.type === 'delete-node') {
1298
- const dataTracked = c.node.attrs.dataTracked;
1299
- const wasInsertedBySameUser = (dataTracked === null || dataTracked === void 0 ? void 0 : dataTracked.operation) === CHANGE_OPERATION.insert &&
1300
- dataTracked.authorID === emptyAttrs.authorID;
1301
- if (wasInsertedBySameUser) {
1302
- deleteNode(c.node, mapping.map(c.pos), newTr);
1303
- const newestStep = newTr.steps[newTr.steps.length - 1];
1304
- if (step !== newestStep) {
1305
- mapping.appendMap(newestStep.getMap());
1306
- step = newestStep;
1307
- }
1308
- mergeTrackedMarks(mapping.map(c.pos), newTr.doc, newTr, schema);
1309
- }
1310
- else {
1311
- const attrs = {
1312
- ...c.node.attrs,
1313
- dataTracked: addTrackIdIfDoesntExist(deleteAttrs),
1314
- };
1315
- newTr.setNodeMarkup(mapping.map(c.pos), undefined, attrs, c.node.marks);
1363
+ deleteOrSetNodeDeleted(c.node, mapping.map(c.pos), newTr, deleteAttrs);
1364
+ const newestStep = newTr.steps[newTr.steps.length - 1];
1365
+ if (step !== newestStep) {
1366
+ mapping.appendMap(newestStep.getMap());
1367
+ step = newestStep;
1316
1368
  }
1369
+ mergeTrackedMarks(mapping.map(c.pos), newTr.doc, newTr, schema);
1317
1370
  }
1318
1371
  else if (c.type === 'delete-text') {
1319
- const from = mapping.map(c.from, -1);
1320
- const to = mapping.map(c.to, 1);
1321
1372
  const node = newTr.doc.nodeAt(mapping.map(c.pos));
1322
- if (node === null || node === void 0 ? void 0 : node.marks.find((m) => m.type === schema.marks.tracked_insert)) {
1323
- newTr.replaceWith(from, to, Fragment.empty);
1324
- mergeTrackedMarks(from, newTr.doc, newTr, schema);
1325
- }
1326
- else {
1327
- const leftNode = newTr.doc.resolve(from).nodeBefore;
1328
- const leftMarks = getMergeableMarkTrackedAttrs(leftNode, deleteAttrs, schema);
1329
- const rightNode = newTr.doc.resolve(to).nodeAfter;
1330
- const rightMarks = getMergeableMarkTrackedAttrs(rightNode, deleteAttrs, schema);
1331
- const fromStartOfMark = from - (leftNode && leftMarks ? leftNode.nodeSize : 0);
1332
- const toEndOfMark = to + (rightNode && rightMarks ? rightNode.nodeSize : 0);
1333
- const dataTracked = addTrackIdIfDoesntExist({
1334
- ...leftMarks,
1335
- ...rightMarks,
1336
- ...deleteAttrs,
1337
- });
1338
- newTr.addMark(fromStartOfMark, toEndOfMark, schema.marks.tracked_delete.create({
1339
- dataTracked,
1340
- }));
1373
+ if (!node) {
1374
+ log.error(`processChangeSteps: no text node found for text-change`, c);
1375
+ return;
1341
1376
  }
1377
+ const where = deleteTextIfInserted(node, mapping.map(c.pos), newTr, schema, deleteAttrs, mapping.map(c.from), mapping.map(c.to));
1378
+ mergeTrackedMarks(where, newTr.doc, newTr, schema);
1342
1379
  }
1343
1380
  else if (c.type === 'merge-fragment') {
1344
1381
  let insertPos = mapping.map(c.mergePos);
@@ -1364,7 +1401,7 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1364
1401
  const newStep = new ReplaceStep(mapping.map(c.from), mapping.map(c.to), c.slice, false);
1365
1402
  const stepResult = newTr.maybeStep(newStep);
1366
1403
  if (stepResult.failed) {
1367
- log.error(`insert ReplaceStep failed: "${stepResult.failed}"`, newStep);
1404
+ log.error(`processChangeSteps: insert-slice ReplaceStep failed "${stepResult.failed}"`, newStep);
1368
1405
  return;
1369
1406
  }
1370
1407
  mergeTrackedMarks(mapping.map(c.from), newTr.doc, newTr, schema);
@@ -1372,17 +1409,29 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1372
1409
  selectionPos = mapping.map(c.to) + c.slice.size;
1373
1410
  }
1374
1411
  else if (c.type === 'update-node-attrs') {
1375
- const oldDataTracked = c.oldAttrs.dataTracked;
1376
- const oldAttrs = (oldDataTracked === null || oldDataTracked === void 0 ? void 0 : oldDataTracked.operation) === CHANGE_OPERATION.set_node_attributes
1377
- ? oldDataTracked.oldAttrs
1378
- : c.oldAttrs;
1379
- const dataTracked = addTrackIdIfDoesntExist({
1380
- ...oldDataTracked,
1381
- oldAttrs,
1382
- ...emptyAttrs,
1383
- operation: CHANGE_OPERATION.set_node_attributes,
1384
- });
1385
- newTr.setNodeMarkup(mapping.map(c.pos), undefined, { ...c.newAttrs, dataTracked });
1412
+ const oldDataTracked = getBlockInlineTrackedData(c.node) || [];
1413
+ const oldUpdate = oldDataTracked.find((d) => d.operation === CHANGE_OPERATION.set_node_attributes);
1414
+ let newDataTracked = oldDataTracked;
1415
+ if (oldUpdate) {
1416
+ newDataTracked = [
1417
+ ...oldDataTracked.filter((d) => d === oldUpdate),
1418
+ {
1419
+ ...oldUpdate,
1420
+ updatedAt: emptyAttrs.updatedAt,
1421
+ },
1422
+ ];
1423
+ }
1424
+ else if (oldDataTracked.length === 0 ||
1425
+ oldDataTracked.find((d) => d.operation === CHANGE_OPERATION.delete)) {
1426
+ newDataTracked = [
1427
+ ...oldDataTracked,
1428
+ addTrackIdIfDoesntExist(createNewUpdateAttrs(emptyAttrs, c.node.attrs)),
1429
+ ];
1430
+ }
1431
+ newTr.setNodeMarkup(mapping.map(c.pos), undefined, {
1432
+ ...c.newAttrs,
1433
+ dataTracked: newDataTracked.length > 0 ? newDataTracked : null,
1434
+ }, c.node.marks);
1386
1435
  }
1387
1436
  const newestStep = newTr.steps[newTr.steps.length - 1];
1388
1437
  if (step !== newestStep) {
@@ -1407,25 +1456,26 @@ function processChangeSteps(changes, startPos, newTr, emptyAttrs, schema) {
1407
1456
  * See the License for the specific language governing permissions and
1408
1457
  * limitations under the License.
1409
1458
  */
1410
- function matchInserted(inDeleted, deleted, inserted, newTr, schema) {
1459
+ function matchInserted(matchedDeleted, deleted, inserted, newTr, schema) {
1411
1460
  var _a;
1461
+ let matched = [matchedDeleted, deleted];
1412
1462
  for (let i = 0;; i += 1) {
1413
1463
  if (inserted.childCount === i)
1414
- return [inDeleted, deleted];
1415
- const child = inserted.child(i);
1464
+ return matched;
1465
+ const insNode = inserted.child(i);
1416
1466
  // @ts-ignore
1417
- let adjDeleted = deleted.find((d) => (d.type === 'delete-text' && d.to === inDeleted) ||
1418
- (d.type === 'delete-node' && d.nodeEnd === inDeleted));
1419
- if (child.type !== ((_a = adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node) === null || _a === void 0 ? void 0 : _a.type)) {
1420
- return [inDeleted, deleted];
1467
+ let adjDeleted = matched[1].find((d) => (d.type === 'delete-text' && Math.max(d.pos, d.from) === matched[0]) ||
1468
+ (d.type === 'delete-node' && d.pos === matched[0]));
1469
+ if (insNode.type !== ((_a = adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node) === null || _a === void 0 ? void 0 : _a.type)) {
1470
+ return matched;
1421
1471
  }
1422
- else if (child.isText && (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node)) {
1472
+ else if (insNode.isText && (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node)) {
1423
1473
  adjDeleted = adjDeleted;
1424
- const { pos, from, to, node } = adjDeleted;
1425
- let j = 0, d = from - pos, maxSteps = Math.max(pos, from) - to;
1474
+ const { pos, from, to, node: delNode } = adjDeleted;
1475
+ let j = 0, d = from - pos, maxSteps = to - Math.max(pos, from);
1426
1476
  // Match text inside the inserted text node to the deleted text node
1427
- for (; maxSteps !== j && child.text[j] === node.text[d]; j += 1, d += 1) {
1428
- inDeleted -= 1;
1477
+ for (; maxSteps !== j && insNode.text[j] !== undefined && insNode.text[j] === delNode.text[d]; j += 1, d += 1) {
1478
+ matched[0] += 1;
1429
1479
  }
1430
1480
  // this is needed incase diffing tr.doc
1431
1481
  // deleted.push({
@@ -1435,46 +1485,108 @@ function matchInserted(inDeleted, deleted, inserted, newTr, schema) {
1435
1485
  // oldAttrs: adjDeleted.node.attrs || {},
1436
1486
  // newAttrs: child.attrs || {},
1437
1487
  // })
1488
+ matched = [matched[0], matched[1].filter((d) => d !== adjDeleted)];
1438
1489
  if (maxSteps !== j) {
1439
- deleted.push({
1490
+ matched[1].push({
1440
1491
  pos,
1441
1492
  from: Math.max(pos, from) + j,
1442
1493
  to,
1443
1494
  type: 'delete-text',
1444
- node,
1495
+ node: delNode,
1445
1496
  });
1497
+ return matched;
1446
1498
  }
1447
- return [inDeleted, deleted.filter((d) => d !== adjDeleted)];
1499
+ continue;
1448
1500
  }
1449
- else if (child.content.size > 0 || (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node.content.size) > 0) {
1450
- // Move the inDeleted inside the block node's boundary
1451
- return matchInserted(inDeleted - 1, deleted.filter((d) => d !== adjDeleted), child.content);
1501
+ else if (insNode.content.size > 0 || (adjDeleted === null || adjDeleted === void 0 ? void 0 : adjDeleted.node.content.size) > 0) {
1502
+ // Move the inDeleted inside the block/inline node's boundary
1503
+ matched = matchInserted(matched[0] + 1, matched[1].filter((d) => d !== adjDeleted), insNode.content);
1452
1504
  }
1453
- deleted.push({
1505
+ else {
1506
+ matched = [matched[0] + insNode.nodeSize, matched[1].filter((d) => d !== adjDeleted)];
1507
+ }
1508
+ matched[1].push({
1454
1509
  pos: adjDeleted.pos,
1455
1510
  type: 'update-node-attrs',
1511
+ node: adjDeleted.node,
1456
1512
  // Should check the attrs for equality in fixInconsistentChanges? to remove dataTracked completely
1457
- oldAttrs: adjDeleted.node.attrs || {},
1458
- newAttrs: child.attrs || {},
1513
+ newAttrs: insNode.attrs || {},
1459
1514
  });
1460
- deleted = deleted.filter((d) => d !== adjDeleted);
1461
- inDeleted -= child.nodeSize;
1462
1515
  }
1463
1516
  }
1517
+ /**
1518
+ * Cuts a fragment similar to Fragment.cut but also removes the parent node.
1519
+ *
1520
+ * @TODO there is however, some silly calculation mistake so that I need to use matched - deleted + 1 > 0
1521
+ * inside it to check whether to actually cut a text node. The offset might be cascading, therefore it should
1522
+ * be fixed at some point.
1523
+ * @param matched
1524
+ * @param deleted
1525
+ * @param content
1526
+ * @returns
1527
+ */
1528
+ function cutFragment(matched, deleted, content) {
1529
+ let newContent = [];
1530
+ for (let i = 0; matched <= deleted && i < content.childCount; i += 1) {
1531
+ const child = content.child(i);
1532
+ if (!child.isText && child.content.size > 0) {
1533
+ const cut = cutFragment(matched + 1, deleted, child.content);
1534
+ matched = cut[0];
1535
+ newContent.push(...cut[1].content);
1536
+ }
1537
+ else if (child.isText && matched + child.nodeSize > deleted) {
1538
+ if (matched - deleted + 1 > 0) {
1539
+ newContent.push(child.cut(0, matched - deleted + 1));
1540
+ }
1541
+ else {
1542
+ newContent.push(child);
1543
+ }
1544
+ matched = deleted + 1;
1545
+ }
1546
+ else {
1547
+ matched += child.nodeSize;
1548
+ }
1549
+ }
1550
+ return [matched, Fragment.fromArray(newContent)];
1551
+ }
1464
1552
  function diffChangeSteps(deleted, inserted, newTr, schema) {
1465
1553
  const updated = [];
1466
1554
  let updatedDeleted = [...deleted];
1467
1555
  inserted.forEach((ins) => {
1468
- const [inDeleted, updatedDel] = matchInserted(ins.from, updatedDeleted, ins.slice.content);
1469
- if (inDeleted === ins.from) {
1556
+ log.info('DIFF ins ', ins);
1557
+ //
1558
+ // @TODO this is a temporary workaround to prevent duplicated diffing between splitSliceIntoMergedParts and
1559
+ // matchInserted.
1560
+ //
1561
+ // As originally authored splitSliceIntoMergedParts splits open slices into their merged parts
1562
+ // leaving out the need to insert the possibly deleted nodes into the doc. However, as matchInserted now
1563
+ // traverses the deleted range checking it against the inserted slice this behaves quite in a same way
1564
+ // where the opened block nodes are traversed but left unmodified. With an openStart > 0 though the
1565
+ // node-attr-updates would additionally have to be filtered out in the processChangeSteps.
1566
+ //
1567
+ // The old logic is still left as it's as refactoring is painful and would probably break something and just
1568
+ // in general, take a lot of time. Therefore, this sliceWasSplit boolean is used to just skip diffing.
1569
+ if (ins.sliceWasSplit) {
1570
+ updated.push(ins);
1571
+ return;
1572
+ }
1573
+ // Start diffing from the start of the deleted range
1574
+ const deleteStart = deleted.reduce((acc, cur) => {
1575
+ if (cur.type === 'delete-node') {
1576
+ return Math.min(acc, cur.pos);
1577
+ }
1578
+ else if (cur.type === 'delete-text') {
1579
+ return Math.min(acc, cur.from);
1580
+ }
1581
+ return acc;
1582
+ }, Number.MAX_SAFE_INTEGER);
1583
+ const [inDeleted, updatedDel] = matchInserted(deleteStart, updatedDeleted, ins.slice.content);
1584
+ if (inDeleted === deleteStart) {
1470
1585
  updated.push(ins);
1471
1586
  return;
1472
1587
  }
1473
1588
  updatedDeleted = updatedDel;
1474
- const newInsertedA = ins.slice.content.cut(ins.from - inDeleted);
1475
- const newInsertedB = ins.slice.content.cut(ins.from - inDeleted + 1);
1476
- // Super hax to cut over block node boundaries in the inserted fragment
1477
- const newInserted = newInsertedA.size === newInsertedB.size + 2 ? newInsertedB : newInsertedA;
1589
+ const newInserted = cutFragment(0, inDeleted, ins.slice.content)[1];
1478
1590
  if (newInserted.size > 0) {
1479
1591
  updated.push({
1480
1592
  ...ins,
@@ -1482,6 +1594,7 @@ function diffChangeSteps(deleted, inserted, newTr, schema) {
1482
1594
  });
1483
1595
  }
1484
1596
  });
1597
+ log.info('FINISH DIFF: ', [...updatedDeleted, ...updated]);
1485
1598
  return [...updatedDeleted, ...updated];
1486
1599
  }
1487
1600
 
@@ -1697,10 +1810,10 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1697
1810
  ids.forEach((changeId) => {
1698
1811
  const change = changeSet === null || changeSet === void 0 ? void 0 : changeSet.get(changeId);
1699
1812
  if (change) {
1700
- createdTr = updateChangeAttrs(createdTr, change, { status, reviewedByID: userID }, oldState.schema);
1701
- setAction(createdTr, TrackChangesAction.updateChanges, [change.id]);
1813
+ createdTr = updateChangeAttrs(createdTr, change, { ...change.dataTracked, status, reviewedByID: userID }, oldState.schema);
1702
1814
  }
1703
1815
  });
1816
+ setAction(createdTr, TrackChangesAction.updateChanges, ids);
1704
1817
  }
1705
1818
  else if (getAction(tr, TrackChangesAction.applyAndRemoveChanges)) {
1706
1819
  const mapping = applyAcceptedRejectedChanges(createdTr, oldState.schema, changeSet.nodeChanges);