@manuscripts/track-changes-plugin 2.0.13 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +3 -3
  2. package/dist/cjs/ChangeSet.js +27 -8
  3. package/dist/cjs/changes/applyChanges.js +26 -12
  4. package/dist/cjs/changes/findChanges.js +16 -0
  5. package/dist/cjs/changes/revertChange.js +2 -2
  6. package/dist/cjs/changes/updateChangeAttrs.js +27 -0
  7. package/dist/cjs/compute/nodeHelpers.js +11 -0
  8. package/dist/cjs/mutate/deleteAndMergeSplitNodes.js +2 -1
  9. package/dist/cjs/steps/trackMarkSteps.js +140 -0
  10. package/dist/cjs/steps/trackReplaceAroundStep.js +3 -3
  11. package/dist/cjs/steps/trackReplaceStep.js +3 -3
  12. package/dist/cjs/steps/trackTransaction.js +14 -2
  13. package/dist/cjs/steps/utils.js +70 -0
  14. package/dist/cjs/utils/track-utils.js +28 -51
  15. package/dist/cjs/utils/uuidv4.js +3 -0
  16. package/dist/es/ChangeSet.js +27 -8
  17. package/dist/es/changes/applyChanges.js +26 -11
  18. package/dist/es/changes/findChanges.js +17 -1
  19. package/dist/es/changes/revertChange.js +2 -2
  20. package/dist/es/changes/updateChangeAttrs.js +27 -0
  21. package/dist/es/compute/nodeHelpers.js +10 -0
  22. package/dist/es/mutate/deleteAndMergeSplitNodes.js +2 -1
  23. package/dist/es/steps/trackMarkSteps.js +134 -0
  24. package/dist/es/steps/trackReplaceAroundStep.js +1 -1
  25. package/dist/es/steps/trackReplaceStep.js +1 -1
  26. package/dist/es/steps/trackTransaction.js +14 -2
  27. package/dist/es/steps/utils.js +62 -0
  28. package/dist/es/utils/track-utils.js +24 -45
  29. package/dist/es/utils/uuidv4.js +3 -0
  30. package/dist/types/ChangeSet.d.ts +5 -2
  31. package/dist/types/changes/applyChanges.d.ts +1 -2
  32. package/dist/types/compute/nodeHelpers.d.ts +2 -1
  33. package/dist/types/steps/trackMarkSteps.d.ts +8 -0
  34. package/dist/types/steps/utils.d.ts +28 -0
  35. package/dist/types/types/change.d.ts +4 -1
  36. package/dist/types/utils/track-utils.d.ts +7 -14
  37. package/package.json +1 -1
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.updateBlockNodesAttrs = exports.filterMeaninglessMoveSteps = exports.handleDirectPendingMoveDeletions = exports.isDirectPendingMoveDeletion = exports.isDeletingPendingMovedNode = exports.isPendingChange = exports.HasMoveOperations = exports.trFromHistory = exports.isStructureSteps = exports.isLiftStep = exports.isWrapStep = exports.isSplitStep = void 0;
14
+ exports.updateBlockNodesAttrs = exports.filterMeaninglessMoveSteps = exports.handleDirectPendingMoveDeletions = exports.isDirectPendingMoveDeletion = exports.isDeletingPendingMovedNode = exports.isPendingChange = exports.HasMoveOperations = exports.trFromHistory = void 0;
15
15
  exports.createNewInsertAttrs = createNewInsertAttrs;
16
16
  exports.createNewWrapAttrs = createNewWrapAttrs;
17
17
  exports.createNewSplitAttrs = createNewSplitAttrs;
@@ -20,7 +20,9 @@ exports.createNewDeleteAttrs = createNewDeleteAttrs;
20
20
  exports.createNewMoveAttrs = createNewMoveAttrs;
21
21
  exports.createNewUpdateAttrs = createNewUpdateAttrs;
22
22
  exports.createNewStructureAttrs = createNewStructureAttrs;
23
- exports.stepIsLift = stepIsLift;
23
+ exports.isValidTrackableMark = isValidTrackableMark;
24
+ exports.excludeFromTracked = excludeFromTracked;
25
+ exports.isInlineMarkChange = isInlineMarkChange;
24
26
  const prosemirror_model_1 = require("prosemirror-model");
25
27
  const prosemirror_transform_1 = require("prosemirror-transform");
26
28
  const actions_1 = require("../actions");
@@ -51,55 +53,6 @@ function createNewUpdateAttrs(attrs, oldAttrs) {
51
53
  function createNewStructureAttrs(attrs) {
52
54
  return Object.assign(Object.assign({}, attrs), { operation: change_1.CHANGE_OPERATION.structure });
53
55
  }
54
- const isSplitStep = (step, selection, uiEvent) => {
55
- var _a, _b, _c, _d;
56
- const { from, to, slice } = step;
57
- if (from !== to ||
58
- slice.content.childCount < 2 ||
59
- (((_a = slice.content.firstChild) === null || _a === void 0 ? void 0 : _a.isInline) && ((_b = slice.content.lastChild) === null || _b === void 0 ? void 0 : _b.isInline))) {
60
- return false;
61
- }
62
- const { $anchor: { parentOffset: startOffset }, $head: { parentOffset: endOffset }, $from, } = selection;
63
- const parentSize = $from.node().content.size;
64
- if (uiEvent === 'paste') {
65
- return !((startOffset === 0 && endOffset === 0) ||
66
- (startOffset === parentSize && endOffset === parentSize));
67
- }
68
- const { content: { firstChild, lastChild }, openStart, openEnd, } = slice;
69
- if ((((_c = window.event) === null || _c === void 0 ? void 0 : _c.code) === 'Enter' || ((_d = window.event) === null || _d === void 0 ? void 0 : _d.code) === 'NumpadEnter') &&
70
- (firstChild === null || firstChild === void 0 ? void 0 : firstChild.type.name) === 'list_item') {
71
- return !(parentSize === startOffset && parentSize === endOffset) && (lastChild === null || lastChild === void 0 ? void 0 : lastChild.type.name) === 'list_item';
72
- }
73
- return (openStart === openEnd &&
74
- firstChild.type === lastChild.type &&
75
- firstChild.inlineContent &&
76
- lastChild.inlineContent &&
77
- !(startOffset === parentSize && endOffset === parentSize));
78
- };
79
- exports.isSplitStep = isSplitStep;
80
- const isWrapStep = (step) => step.from === step.gapFrom &&
81
- step.to === step.gapTo &&
82
- step.slice.openStart === 0 &&
83
- step.slice.openEnd === 0;
84
- exports.isWrapStep = isWrapStep;
85
- const isLiftStep = (step) => {
86
- if (step.from < step.gapFrom &&
87
- step.to > step.gapTo &&
88
- step.slice.size === 0 &&
89
- step.gapTo - step.gapFrom > 0) {
90
- return true;
91
- }
92
- return false;
93
- };
94
- exports.isLiftStep = isLiftStep;
95
- function stepIsLift(gap, node, to) {
96
- return gap.start < gap.end && gap.insert === 0 && gap.end === to && !node.isText;
97
- }
98
- const isStructureSteps = (tr) => tr.getMeta(actions_1.TrackChangesAction.structuralChangeAction) &&
99
- tr.steps.length === 2 &&
100
- tr.steps[0] instanceof prosemirror_transform_1.ReplaceStep &&
101
- tr.steps[1] instanceof prosemirror_transform_1.ReplaceStep;
102
- exports.isStructureSteps = isStructureSteps;
103
56
  const trFromHistory = (tr) => Object.keys(tr.meta).find((s) => s.startsWith('history$'));
104
57
  exports.trFromHistory = trFromHistory;
105
58
  const HasMoveOperations = (tr) => {
@@ -260,3 +213,27 @@ const updateBlockNodesAttrs = (fragment, predicate) => {
260
213
  return prosemirror_model_1.Fragment.fromArray(updatedNodes);
261
214
  };
262
215
  exports.updateBlockNodesAttrs = updateBlockNodesAttrs;
216
+ function isValidTrackableMark(mark) {
217
+ var _a, _b;
218
+ const spec = mark.type.spec;
219
+ const name = mark.type.name;
220
+ if (!name.startsWith('tracked_') &&
221
+ ((_a = spec.attrs) === null || _a === void 0 ? void 0 : _a.dataTracked) &&
222
+ typeof ((_b = spec.attrs) === null || _b === void 0 ? void 0 : _b.dataTracked) === 'object') {
223
+ return true;
224
+ }
225
+ return false;
226
+ }
227
+ function excludeFromTracked(dataTracked, changeIdToExclude) {
228
+ if (!dataTracked) {
229
+ return null;
230
+ }
231
+ const newDataTracked = dataTracked.filter((c) => c.id !== changeIdToExclude);
232
+ return newDataTracked.length ? newDataTracked : null;
233
+ }
234
+ function isInlineMarkChange(change) {
235
+ if (change.type == 'mark-change') {
236
+ return change.nodeType.isInline || change.nodeType.isText;
237
+ }
238
+ return false;
239
+ }
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.uuidv4 = uuidv4;
4
4
  function uuidv4() {
5
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
6
+ return crypto.randomUUID();
7
+ }
5
8
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
6
9
  const r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
7
10
  return v.toString(16);
@@ -12,6 +12,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
12
12
  var _ChangeSet_instances, _ChangeSet_changes, _ChangeSet_isSameNodeChange, _ChangeSet_isNotPendingOrDeleted;
13
13
  import { CHANGE_OPERATION, CHANGE_STATUS, } from './types/change';
14
14
  import { log } from './utils/logger';
15
+ import { isInlineMarkChange } from './utils/track-utils';
15
16
  export class ChangeSet {
16
17
  constructor(changes = []) {
17
18
  _ChangeSet_instances.add(this);
@@ -147,19 +148,34 @@ export class ChangeSet {
147
148
  }
148
149
  }
149
150
  }
151
+ areMatchingWrapOperations(c1, c2) {
152
+ const op1 = c1.dataTracked.operation;
153
+ const op2 = c2.dataTracked.operation;
154
+ return ((op1 === 'wrap_with_node' && (op2 === 'insert' || op2 === 'set_attrs')) ||
155
+ (op2 === 'wrap_with_node' && (op1 === 'insert' || op1 === 'set_attrs')));
156
+ }
157
+ areMatchingMarkOperations(c1, c2) {
158
+ if (ChangeSet.isMarkChange(c1) && ChangeSet.isMarkChange(c2)) {
159
+ const op1 = c1.dataTracked.operation;
160
+ const op2 = c2.dataTracked.operation;
161
+ if (op1 == op2 && c1.mark.type === c2.mark.type) {
162
+ return true;
163
+ }
164
+ }
165
+ return false;
166
+ }
150
167
  canJoinAdjacentInlineChanges(change, index) {
151
168
  const nextChange = this.changeTree.at(index + 1);
152
- const isInline = (c) => c.type === 'text-change' || (c.type === 'node-change' && c.node.isInline);
153
- const hasMatchingOperation = (c1, c2) => c1.dataTracked.operation === c2.dataTracked.operation ||
154
- (c1.dataTracked.operation === 'wrap_with_node' &&
155
- (c2.dataTracked.operation === 'insert' || c2.dataTracked.operation === 'set_attrs')) ||
156
- (c2.dataTracked.operation === 'wrap_with_node' &&
157
- (c1.dataTracked.operation === 'insert' || c1.dataTracked.operation === 'set_attrs'));
169
+ if (!nextChange) {
170
+ return false;
171
+ }
172
+ const isInline = (c) => c.type === 'text-change' || (c.type === 'node-change' && c.node.isInline) || isInlineMarkChange(c);
158
173
  return (isInline(change) &&
159
- nextChange &&
160
174
  isInline(nextChange) &&
161
175
  change.to === nextChange.from &&
162
- hasMatchingOperation(change, nextChange));
176
+ (change.dataTracked.operation === nextChange.dataTracked.operation ||
177
+ this.areMatchingWrapOperations(change, nextChange) ||
178
+ this.areMatchingMarkOperations(change, nextChange)));
163
179
  }
164
180
  joinRelatedStructuralChanges(rootNodes, change) {
165
181
  if (change.dataTracked.operation !== CHANGE_OPERATION.structure) {
@@ -210,6 +226,9 @@ export class ChangeSet {
210
226
  static isTextChange(change) {
211
227
  return change.type === 'text-change';
212
228
  }
229
+ static isMarkChange(change) {
230
+ return change.type === 'mark-change';
231
+ }
213
232
  static isNodeChange(change) {
214
233
  return change.type === 'node-change';
215
234
  }
@@ -4,6 +4,7 @@ import { deleteNode, keepPairedChanges } from '../mutate/deleteNode';
4
4
  import { mergeNode } from '../mutate/mergeNode';
5
5
  import { CHANGE_OPERATION, CHANGE_STATUS } from '../types/change';
6
6
  import { log } from '../utils/logger';
7
+ import { excludeFromTracked, isInlineMarkChange } from '../utils/track-utils';
7
8
  import { revertSplitNodeChange, revertWrapNodeChange } from './revertChange';
8
9
  import { restoreNode, updateChangeChildrenAttributes } from './updateChangeAttrs';
9
10
  function collectMoveNodeIds(containerNode, primaryMoveNodeId) {
@@ -21,13 +22,6 @@ function collectMoveNodeIds(containerNode, primaryMoveNodeId) {
21
22
  });
22
23
  return moveNodeIds;
23
24
  }
24
- export function getUpdatedDataTracked(dataTracked, changeId) {
25
- if (!dataTracked) {
26
- return null;
27
- }
28
- const newDataTracked = dataTracked.filter((c) => c.id !== changeId);
29
- return newDataTracked.length ? newDataTracked : null;
30
- }
31
25
  export function applyAcceptedRejectedChanges(tr, schema, changes, changeSet, deleteMap = new Mapping()) {
32
26
  changes.sort((c1, c2) => {
33
27
  if ((c1.type === 'node-change' && c1.node.type === schema.nodes.list) ||
@@ -87,13 +81,34 @@ export function applyAcceptedRejectedChanges(tr, schema, changes, changeSet, del
87
81
  deleteMap.appendMap(tr.steps[tr.steps.length - 1].getMap());
88
82
  }
89
83
  else if (ChangeSet.isNodeAttrChange(change) && change.dataTracked.status === CHANGE_STATUS.accepted) {
90
- tr.setNodeMarkup(from, undefined, Object.assign(Object.assign({}, change.newAttrs), { dataTracked: getUpdatedDataTracked(node.attrs.dataTracked, change.id) }), node.marks);
84
+ tr.setNodeMarkup(from, undefined, Object.assign(Object.assign({}, change.newAttrs), { dataTracked: excludeFromTracked(node.attrs.dataTracked, change.id) }), node.marks);
91
85
  }
92
86
  else if (ChangeSet.isNodeAttrChange(change) && change.dataTracked.status === CHANGE_STATUS.rejected) {
93
- tr.setNodeMarkup(from, undefined, Object.assign(Object.assign({}, change.oldAttrs), { dataTracked: getUpdatedDataTracked(node.attrs.dataTracked, change.id) }), node.marks);
87
+ tr.setNodeMarkup(from, undefined, Object.assign(Object.assign({}, change.oldAttrs), { dataTracked: excludeFromTracked(node.attrs.dataTracked, change.id) }), node.marks);
94
88
  }
95
89
  else if (ChangeSet.isReferenceChange(change)) {
96
- tr.setNodeMarkup(from, undefined, Object.assign(Object.assign({}, node.attrs), { dataTracked: getUpdatedDataTracked(node.attrs.dataTracked, change.id) }), node.marks);
90
+ tr.setNodeMarkup(from, undefined, Object.assign(Object.assign({}, node.attrs), { dataTracked: excludeFromTracked(node.attrs.dataTracked, change.id) }), node.marks);
91
+ }
92
+ else if (ChangeSet.isMarkChange(change)) {
93
+ const newMark = change.mark.type.create({
94
+ dataTracked: excludeFromTracked(change.mark.attrs.dataTracked, change.id),
95
+ });
96
+ const isInsert = change.dataTracked.operation === CHANGE_OPERATION.insert;
97
+ const isDelete = change.dataTracked.operation === CHANGE_OPERATION.delete;
98
+ const toBeRestored = (change.dataTracked.status === CHANGE_STATUS.accepted && isInsert) ||
99
+ (change.dataTracked.status === CHANGE_STATUS.rejected && isDelete);
100
+ if (isInlineMarkChange(change)) {
101
+ tr.removeMark(change.from, change.to, change.mark);
102
+ if (toBeRestored) {
103
+ tr.addMark(change.from, change.to, newMark);
104
+ }
105
+ }
106
+ else {
107
+ tr.removeNodeMark(change.from, change.mark);
108
+ if (toBeRestored) {
109
+ tr.addNodeMark(change.from, newMark);
110
+ }
111
+ }
97
112
  }
98
113
  });
99
114
  changes.forEach((change) => {
@@ -110,7 +125,7 @@ export function applyAcceptedRejectedChanges(tr, schema, changes, changeSet, del
110
125
  return;
111
126
  }
112
127
  if (change.dataTracked.status === CHANGE_STATUS.accepted) {
113
- const attrs = Object.assign(Object.assign({}, node.attrs), { dataTracked: getUpdatedDataTracked(node.attrs.dataTracked, change.id) });
128
+ const attrs = Object.assign(Object.assign({}, node.attrs), { dataTracked: excludeFromTracked(node.attrs.dataTracked, change.id) });
114
129
  tr.setNodeMarkup(from, undefined, attrs, node.marks);
115
130
  const originalChanges = changeSet.changes.filter((c) => c.dataTracked.moveNodeId === change.dataTracked.moveNodeId &&
116
131
  c.dataTracked.operation === CHANGE_OPERATION.delete);
@@ -1,11 +1,27 @@
1
1
  import { ChangeSet } from '../ChangeSet';
2
- import { getNodeTrackedData } from '../compute/nodeHelpers';
2
+ import { getMarkTrackedData, getNodeTrackedData } from '../compute/nodeHelpers';
3
3
  import { CHANGE_OPERATION, } from '../types/change';
4
4
  export function findChanges(state) {
5
5
  const changes = [];
6
6
  let current;
7
7
  state.doc.descendants((node, pos) => {
8
8
  const tracked = getNodeTrackedData(node, state.schema) || [];
9
+ const marksWithTrackChanges = getMarkTrackedData(node);
10
+ marksWithTrackChanges === null || marksWithTrackChanges === void 0 ? void 0 : marksWithTrackChanges.forEach((trackAttrs, mark) => {
11
+ trackAttrs.forEach((c) => {
12
+ const ch = {
13
+ id: c.id,
14
+ type: 'mark-change',
15
+ from: pos,
16
+ to: pos + node.nodeSize,
17
+ dataTracked: Object.assign({}, c),
18
+ nodeType: node.type,
19
+ node: node,
20
+ mark: mark,
21
+ };
22
+ changes.push(ch);
23
+ });
24
+ });
9
25
  for (let i = 0; i < tracked.length; i += 1) {
10
26
  const dataTracked = tracked[i];
11
27
  const id = dataTracked.id || '';
@@ -1,7 +1,7 @@
1
1
  import { Slice } from 'prosemirror-model';
2
2
  import { liftTarget, ReplaceAroundStep } from 'prosemirror-transform';
3
3
  import { getBlockInlineTrackedData } from '../compute/nodeHelpers';
4
- import { getUpdatedDataTracked } from './applyChanges';
4
+ import { excludeFromTracked } from '../utils/track-utils';
5
5
  export function revertSplitNodeChange(tr, change, changeSet) {
6
6
  const sourceChange = changeSet.changes.find((c) => c.dataTracked.operation === 'reference' && c.dataTracked.referenceId === change.id);
7
7
  const node = tr.doc.nodeAt(tr.mapping.map(change.from));
@@ -20,7 +20,7 @@ export function revertSplitNodeChange(tr, change, changeSet) {
20
20
  const deleteChange = changeSet.changes.find((c) => c.dataTracked.operation == 'delete' && c.from === sourceChange.from);
21
21
  if (deleteChange) {
22
22
  const node = tr.doc.nodeAt(tr.mapping.map(deleteChange.from));
23
- tr.setNodeMarkup(tr.mapping.map(deleteChange.from), undefined, getUpdatedDataTracked(node.attrs.dataTracked, deleteChange.id));
23
+ tr.setNodeMarkup(tr.mapping.map(deleteChange.from), undefined, excludeFromTracked(node.attrs.dataTracked, deleteChange.id));
24
24
  }
25
25
  }
26
26
  export function revertWrapNodeChange(tr, change, deleteMap) {
@@ -2,6 +2,7 @@ import { ChangeSet } from '../ChangeSet';
2
2
  import { getBlockInlineTrackedData, getTextNodeTrackedMarkData } from '../compute/nodeHelpers';
3
3
  import { CHANGE_OPERATION, CHANGE_STATUS, } from '../types/change';
4
4
  import { log } from '../utils/logger';
5
+ import { isInlineMarkChange } from '../utils/track-utils';
5
6
  export function updateChangeAttrs(tr, change, trackedAttrs, schema) {
6
7
  const node = tr.doc.nodeAt(change.from);
7
8
  if (!node) {
@@ -64,6 +65,32 @@ export function updateChangeAttrs(tr, change, trackedAttrs, schema) {
64
65
  .filter(Boolean);
65
66
  tr.setNodeMarkup(change.from, undefined, Object.assign(Object.assign({}, (restoredAttrs || node.attrs)), { dataTracked: newDataTracked.length === 0 ? null : newDataTracked }), node.marks);
66
67
  }
68
+ else if (change.type === 'mark-change') {
69
+ const markChange = change;
70
+ if (markChange.mark && markChange.from && markChange.to) {
71
+ const markDataTracked = Array.isArray(markChange.mark.attrs.dataTracked)
72
+ ? markChange.mark.attrs.dataTracked
73
+ : [];
74
+ const newDT = markDataTracked === null || markDataTracked === void 0 ? void 0 : markDataTracked.map((dt) => {
75
+ if (dt.createdAt === change.dataTracked.createdAt && dt.operation === change.dataTracked.operation) {
76
+ return Object.assign(Object.assign({}, dt), trackedAttrs);
77
+ }
78
+ return dt;
79
+ });
80
+ const newMark = markChange.mark.type.create(Object.assign(Object.assign({}, markChange.mark.attrs), { dataTracked: newDT }));
81
+ if (isInlineMarkChange(markChange)) {
82
+ tr.removeMark(markChange.from, markChange.to, markChange.mark);
83
+ tr.addMark(markChange.from, markChange.to, newMark);
84
+ }
85
+ else {
86
+ tr.removeNodeMark(markChange.from, markChange.mark);
87
+ tr.addNodeMark(markChange.from, newMark);
88
+ }
89
+ }
90
+ else {
91
+ console.warn('Unable to update a mark change because the mark change data are incomplete');
92
+ }
93
+ }
67
94
  return tr;
68
95
  }
69
96
  export function updateChangeChildrenAttributes(changes, tr, mapping) {
@@ -1,5 +1,6 @@
1
1
  import { CHANGE_OPERATION } from '../types/change';
2
2
  import { log } from '../utils/logger';
3
+ import { isValidTrackableMark } from '../utils/track-utils';
3
4
  import { uuidv4 } from '../utils/uuidv4';
4
5
  export function addTrackIdIfDoesntExist(attrs) {
5
6
  if (!attrs.id) {
@@ -30,6 +31,15 @@ export function getBlockInlineTrackedData(node) {
30
31
  }
31
32
  return dataTracked || [];
32
33
  }
34
+ export function getMarkTrackedData(node) {
35
+ const tracked = node === null || node === void 0 ? void 0 : node.marks.reduce((acc, current) => {
36
+ if (isValidTrackableMark(current) && current.attrs.dataTracked) {
37
+ acc.set(current, current.attrs.dataTracked);
38
+ }
39
+ return acc;
40
+ }, new Map());
41
+ return tracked || new Map();
42
+ }
33
43
  export function getNodeTrackedData(node, schema) {
34
44
  let tracked;
35
45
  if (node && !node.isText) {
@@ -1,6 +1,7 @@
1
1
  import { Fragment } from 'prosemirror-model';
2
2
  import { setFragmentAsInserted } from '../compute/setFragmentAsInserted';
3
3
  import { splitSliceIntoMergedParts } from '../compute/splitSliceIntoMergedParts';
4
+ import { stepIsLift } from '../steps/utils';
4
5
  import * as trackUtils from '../utils/track-utils';
5
6
  export function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackAttrs, insertSlice) {
6
7
  const steps = [];
@@ -33,7 +34,7 @@ export function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema,
33
34
  const mergeEndNode = startTokenDeleted && openEnd > 0 && depth === openEnd && mergeContent;
34
35
  const mergeEndNodeNotEmpty = mergeEndNode && mergeContent.size;
35
36
  if (mergeEndNode && !mergeEndNodeNotEmpty && gap) {
36
- if (trackUtils.stepIsLift(gap, node, to)) {
37
+ if (stepIsLift(gap, node, to)) {
37
38
  gap.slice.content.forEach((node, offset) => {
38
39
  steps.push({
39
40
  type: 'delete-node',
@@ -0,0 +1,134 @@
1
+ import { AddMarkStep, AddNodeMarkStep, RemoveMarkStep } from 'prosemirror-transform';
2
+ import { CHANGE_OPERATION } from '../types/change';
3
+ import { createNewDeleteAttrs, createNewInsertAttrs, isValidTrackableMark } from '../utils/track-utils';
4
+ import { uuidv4 } from '../utils/uuidv4';
5
+ function markHasOp(mark, operation) {
6
+ if (mark.attrs.dataTracked && Array.isArray(mark.attrs.dataTracked)) {
7
+ const dtAttrs = mark.attrs.dataTracked;
8
+ return dtAttrs.some((at) => at.operation === operation);
9
+ }
10
+ }
11
+ export function trackRemoveMarkStep(step, emptyAttrs, newTr, doc) {
12
+ if (isValidTrackableMark(step.mark)) {
13
+ const markName = step.mark.type.name;
14
+ const markSource = step.mark.type.schema.marks[step.mark.type.name];
15
+ let sameMark = null;
16
+ const targetNode = doc.nodeAt(step.from);
17
+ if (targetNode) {
18
+ let targetNodePos = -1;
19
+ doc.descendants((node, pos) => {
20
+ if (node === targetNode) {
21
+ targetNodePos = pos;
22
+ }
23
+ if (targetNodePos >= 0) {
24
+ return false;
25
+ }
26
+ });
27
+ const parentsSameMark = targetNode.marks.find((mark) => {
28
+ var _a;
29
+ if (mark.type.name === markName && ((_a = mark.attrs.dataTracked) === null || _a === void 0 ? void 0 : _a.length)) {
30
+ return mark;
31
+ }
32
+ });
33
+ const nodeEnd = targetNodePos + targetNode.nodeSize;
34
+ if (parentsSameMark && step.from <= nodeEnd && step.to <= nodeEnd) {
35
+ sameMark = parentsSameMark;
36
+ }
37
+ }
38
+ const newDataTracked = createNewDeleteAttrs(emptyAttrs);
39
+ const newMark = markSource.create({
40
+ dataTracked: [Object.assign(Object.assign({}, newDataTracked), { id: uuidv4() })],
41
+ });
42
+ let newStep = new AddMarkStep(step.from, step.to, newMark);
43
+ if (sameMark) {
44
+ if (markHasOp(step.mark, CHANGE_OPERATION.delete)) {
45
+ newStep = new AddMarkStep(step.from, step.to, markSource.create({
46
+ dataTracked: [],
47
+ }));
48
+ }
49
+ if (markHasOp(step.mark, CHANGE_OPERATION.insert)) {
50
+ newStep = new RemoveMarkStep(step.from, step.to, step.mark);
51
+ }
52
+ }
53
+ try {
54
+ newTr.step(newStep);
55
+ }
56
+ catch (e) {
57
+ console.error('Unable to record a RemoveMarkStep with error: ' + e);
58
+ }
59
+ }
60
+ }
61
+ export function trackRemoveNodeMarkStep(step, emptyAttrs, newTr, doc) {
62
+ if (isValidTrackableMark(step.mark)) {
63
+ const markName = step.mark.type.name;
64
+ const markSource = step.mark.type.schema.marks[markName];
65
+ let sameMark = null;
66
+ const targetNode = doc.nodeAt(step.pos);
67
+ if (targetNode) {
68
+ targetNode.marks.find((mark) => {
69
+ var _a;
70
+ if (mark.type.name === markName && ((_a = mark.attrs.dataTracked) === null || _a === void 0 ? void 0 : _a.length)) {
71
+ sameMark = mark;
72
+ }
73
+ });
74
+ }
75
+ const newDataTracked = createNewDeleteAttrs(emptyAttrs);
76
+ const newMark = markSource.create({
77
+ dataTracked: [Object.assign(Object.assign({}, newDataTracked), { id: uuidv4() })],
78
+ });
79
+ let newStep = new AddNodeMarkStep(step.pos, newMark);
80
+ if (sameMark) {
81
+ if (markHasOp(step.mark, CHANGE_OPERATION.delete)) {
82
+ newStep = new AddNodeMarkStep(step.pos, markSource.create({
83
+ dataTracked: [],
84
+ }));
85
+ }
86
+ if (markHasOp(step.mark, CHANGE_OPERATION.insert)) {
87
+ newStep = new AddNodeMarkStep(step.pos, step.mark);
88
+ }
89
+ }
90
+ try {
91
+ newTr.step(newStep);
92
+ }
93
+ catch (e) {
94
+ console.error('Unable to record a RemoveNodeMarkStep with error: ' + e);
95
+ }
96
+ }
97
+ }
98
+ export function trackAddMarkStep(step, emptyAttrs, newTr, doc) {
99
+ if (isValidTrackableMark(step.mark)) {
100
+ const markName = step.mark.type.name;
101
+ const markSource = step.mark.type.schema.marks[markName];
102
+ const newDataTracked = createNewInsertAttrs(emptyAttrs);
103
+ const newMark = markSource.create({
104
+ dataTracked: [Object.assign(Object.assign({}, newDataTracked), { id: uuidv4() })],
105
+ });
106
+ const newStep = new AddMarkStep(step.from, step.to, newMark);
107
+ try {
108
+ const inverted = step.invert();
109
+ newTr.step(inverted);
110
+ newTr.step(newStep);
111
+ }
112
+ catch (e) {
113
+ console.error('Unable to record a remove node mark step: ' + e);
114
+ }
115
+ }
116
+ }
117
+ export function trackAddNodeMarkStep(step, emptyAttrs, newTr, stepDoc) {
118
+ if (isValidTrackableMark(step.mark)) {
119
+ const newDataTracked = createNewInsertAttrs(emptyAttrs);
120
+ const markSource = step.mark.type.schema.marks[step.mark.type.name];
121
+ const newMark = markSource.create({
122
+ dataTracked: [Object.assign(Object.assign({}, newDataTracked), { id: uuidv4() })],
123
+ });
124
+ const newStep = new AddNodeMarkStep(step.pos, newMark);
125
+ try {
126
+ const inverted = step.invert(stepDoc);
127
+ newTr.step(inverted);
128
+ newTr.step(newStep);
129
+ }
130
+ catch (e) {
131
+ console.error('Unable to record an AddNodeMarkStep with error: ' + e);
132
+ }
133
+ }
134
+ }
@@ -4,7 +4,7 @@ import { setFragmentAsInserted, setFragmentAsWrapChange } from '../compute/setFr
4
4
  import { deleteAndMergeSplitNodes } from '../mutate/deleteAndMergeSplitNodes';
5
5
  import { log } from '../utils/logger';
6
6
  import * as trackUtils from '../utils/track-utils';
7
- import { isLiftStep, isWrapStep } from '../utils/track-utils';
7
+ import { isLiftStep, isWrapStep } from './utils';
8
8
  function preserveDataTrackedFromPreviousStep(newTr, step, newStep) {
9
9
  const prevDoc = newTr.docs[newTr.docs.length - 2];
10
10
  if (prevDoc && (step.slice.openEnd || step.slice.openStart)) {
@@ -5,7 +5,7 @@ import { deleteAndMergeSplitNodes } from '../mutate/deleteAndMergeSplitNodes';
5
5
  import { joinStructureChanges } from '../mutate/dropStructureChange';
6
6
  import { log } from '../utils/logger';
7
7
  import * as trackUtils from '../utils/track-utils';
8
- import { isSplitStep, isStructureSteps } from '../utils/track-utils';
8
+ import { isSplitStep, isStructureSteps } from './utils';
9
9
  export function trackReplaceStep(step, oldState, newTr, attrsTemplate, stepResult, currentStepDoc, tr, moveID) {
10
10
  log.info('###### ReplaceStep ######');
11
11
  let selectionPos = 0;
@@ -1,5 +1,5 @@
1
1
  import { NodeSelection as NodeSelectionClass, TextSelection, } from 'prosemirror-state';
2
- import { AddMarkStep, AttrStep, Mapping, ReplaceAroundStep, ReplaceStep, } from 'prosemirror-transform';
2
+ import { AddMarkStep, AddNodeMarkStep, AttrStep, Mapping, RemoveMarkStep, RemoveNodeMarkStep, ReplaceAroundStep, ReplaceStep, } from 'prosemirror-transform';
3
3
  import { getAction, TrackChangesAction } from '../actions';
4
4
  import { diffChangeSteps } from '../change-steps/diffChangeSteps';
5
5
  import { processChangeSteps } from '../change-steps/processChangeSteps';
@@ -8,11 +8,13 @@ import { getNodeTrackedData } from '../compute/nodeHelpers';
8
8
  import { CHANGE_STATUS } from '../types/change';
9
9
  import { log } from '../utils/logger';
10
10
  import { mapChangeSteps } from '../utils/mapChangeStep';
11
- import { filterMeaninglessMoveSteps, handleDirectPendingMoveDeletions, HasMoveOperations, isStructureSteps, } from '../utils/track-utils';
11
+ import { filterMeaninglessMoveSteps, handleDirectPendingMoveDeletions, HasMoveOperations, } from '../utils/track-utils';
12
12
  import { uuidv4 } from '../utils/uuidv4';
13
13
  import trackAttrsChange from './trackAttrsChange';
14
+ import { trackAddMarkStep, trackAddNodeMarkStep, trackRemoveMarkStep, trackRemoveNodeMarkStep, } from './trackMarkSteps';
14
15
  import { trackReplaceAroundStep } from './trackReplaceAroundStep';
15
16
  import { trackReplaceStep } from './trackReplaceStep';
17
+ import { isStructureSteps } from './utils';
16
18
  const getSelectionStaticConstructor = (sel) => Object.getPrototypeOf(sel).constructor;
17
19
  const isHighlightMarkerNode = (node) => node && node.type === node.type.schema.nodes.highlight_marker;
18
20
  export function trackTransaction(tr, oldState, newTr, authorID, changeSet) {
@@ -109,11 +111,21 @@ export function trackTransaction(tr, oldState, newTr, authorID, changeSet) {
109
111
  const [mapping, selectionPos] = processChangeSteps(changeSteps, tr.selection.from, newTr, emptyAttrs, oldState.schema, deletedNodeMapping);
110
112
  }
111
113
  else if (step instanceof AddMarkStep) {
114
+ trackAddMarkStep(step, emptyAttrs, newTr, tr.docs[i]);
112
115
  const dataTracked = (_f = getNodeTrackedData(newTr.doc.nodeAt(step.from), oldState.schema)) === null || _f === void 0 ? void 0 : _f.pop();
113
116
  if (dataTracked) {
114
117
  updateChangeAttrs(newTr, { id: dataTracked.id, from: step.from, to: step.to, type: 'text-change', dataTracked }, Object.assign(Object.assign({}, dataTracked), { id: uuidv4() }), oldState.schema);
115
118
  }
116
119
  }
120
+ else if (step instanceof RemoveMarkStep) {
121
+ trackRemoveMarkStep(step, emptyAttrs, newTr, tr.docs[i]);
122
+ }
123
+ else if (step instanceof RemoveNodeMarkStep) {
124
+ trackRemoveNodeMarkStep(step, emptyAttrs, newTr, tr.docs[i]);
125
+ }
126
+ else if (step instanceof AddNodeMarkStep) {
127
+ trackAddNodeMarkStep(step, emptyAttrs, newTr, tr.docs[i]);
128
+ }
117
129
  tr.getMeta('inputType') && newTr.setMeta('inputType', tr.getMeta('inputType'));
118
130
  tr.getMeta('uiEvent') && newTr.setMeta('uiEvent', tr.getMeta('uiEvent'));
119
131
  }
@@ -0,0 +1,62 @@
1
+ /*!
2
+ * © 2023 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { ReplaceStep } from 'prosemirror-transform';
17
+ import { TrackChangesAction } from '../actions';
18
+ export const isSplitStep = (step, selection, uiEvent) => {
19
+ var _a, _b, _c, _d;
20
+ const { from, to, slice } = step;
21
+ if (from !== to ||
22
+ slice.content.childCount < 2 ||
23
+ (((_a = slice.content.firstChild) === null || _a === void 0 ? void 0 : _a.isInline) && ((_b = slice.content.lastChild) === null || _b === void 0 ? void 0 : _b.isInline))) {
24
+ return false;
25
+ }
26
+ const { $anchor: { parentOffset: startOffset }, $head: { parentOffset: endOffset }, $from, } = selection;
27
+ const parentSize = $from.node().content.size;
28
+ if (uiEvent === 'paste') {
29
+ return !((startOffset === 0 && endOffset === 0) ||
30
+ (startOffset === parentSize && endOffset === parentSize));
31
+ }
32
+ const { content: { firstChild, lastChild }, openStart, openEnd, } = slice;
33
+ if ((((_c = window.event) === null || _c === void 0 ? void 0 : _c.code) === 'Enter' || ((_d = window.event) === null || _d === void 0 ? void 0 : _d.code) === 'NumpadEnter') &&
34
+ (firstChild === null || firstChild === void 0 ? void 0 : firstChild.type.name) === 'list_item') {
35
+ return !(parentSize === startOffset && parentSize === endOffset) && (lastChild === null || lastChild === void 0 ? void 0 : lastChild.type.name) === 'list_item';
36
+ }
37
+ return (openStart === openEnd &&
38
+ firstChild.type === lastChild.type &&
39
+ firstChild.inlineContent &&
40
+ lastChild.inlineContent &&
41
+ !(startOffset === parentSize && endOffset === parentSize));
42
+ };
43
+ export const isWrapStep = (step) => step.from === step.gapFrom &&
44
+ step.to === step.gapTo &&
45
+ step.slice.openStart === 0 &&
46
+ step.slice.openEnd === 0;
47
+ export const isLiftStep = (step) => {
48
+ if (step.from < step.gapFrom &&
49
+ step.to > step.gapTo &&
50
+ step.slice.size === 0 &&
51
+ step.gapTo - step.gapFrom > 0) {
52
+ return true;
53
+ }
54
+ return false;
55
+ };
56
+ export function stepIsLift(gap, node, to) {
57
+ return gap.start < gap.end && gap.insert === 0 && gap.end === to && !node.isText;
58
+ }
59
+ export const isStructureSteps = (tr) => tr.getMeta(TrackChangesAction.structuralChangeAction) &&
60
+ tr.steps.length === 2 &&
61
+ tr.steps[0] instanceof ReplaceStep &&
62
+ tr.steps[1] instanceof ReplaceStep;