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