@manuscripts/track-changes-plugin 2.3.7 → 2.3.9
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/README.md +1 -1
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/plugin.js +4 -0
- package/dist/cjs/trackChanges.js +3 -2
- package/dist/cjs/tracking/normalizeShadowIds.js +50 -0
- package/dist/cjs/tracking/steps-trackers/qualifiers.js +16 -0
- package/dist/cjs/tracking/transactionProcessing.js +61 -0
- package/dist/cjs/utils/shadow-utils.js +66 -0
- package/dist/cjs/utils/shared-utils.js +129 -0
- package/dist/es/index.js +2 -0
- package/dist/es/plugin.js +4 -0
- package/dist/es/trackChanges.js +4 -3
- package/dist/es/tracking/normalizeShadowIds.js +46 -0
- package/dist/es/tracking/steps-trackers/qualifiers.js +14 -0
- package/dist/es/tracking/transactionProcessing.js +61 -1
- package/dist/es/utils/shadow-utils.js +61 -0
- package/dist/es/utils/shared-utils.js +115 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/trackChanges.d.ts +1 -1
- package/dist/types/tracking/normalizeShadowIds.d.ts +18 -0
- package/dist/types/tracking/steps-trackers/qualifiers.d.ts +2 -0
- package/dist/types/tracking/transactionProcessing.d.ts +2 -1
- package/dist/types/utils/shadow-utils.d.ts +30 -0
- package/dist/types/utils/shared-utils.d.ts +13 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ ProseMirror plugin designed to track changes within a document, similar to the t
|
|
|
13
13
|
|
|
14
14
|
### Main design points
|
|
15
15
|
|
|
16
|
-
- Intercept transactions (insert/delete).
|
|
16
|
+
- Intercept transactions (insert/delete).
|
|
17
17
|
Transactions are intercepted and reverted using default plugins lifecycle, unless transaction has meta commanding to skip tracking.
|
|
18
18
|
|
|
19
19
|
- Annotate changes with metadata using node attributes and marks.
|
package/dist/cjs/index.js
CHANGED
|
@@ -51,3 +51,5 @@ var ChangeSet_1 = require("./ChangeSet");
|
|
|
51
51
|
Object.defineProperty(exports, "ChangeSet", { enumerable: true, get: function () { return ChangeSet_1.ChangeSet; } });
|
|
52
52
|
__exportStar(require("./types/change"), exports);
|
|
53
53
|
__exportStar(require("./types/track"), exports);
|
|
54
|
+
__exportStar(require("./utils/shared-utils"), exports);
|
|
55
|
+
__exportStar(require("./utils/shadow-utils"), exports);
|
package/dist/cjs/plugin.js
CHANGED
|
@@ -22,6 +22,7 @@ const trackChanges_1 = require("./trackChanges");
|
|
|
22
22
|
const transactionProcessing_1 = require("./tracking/transactionProcessing");
|
|
23
23
|
const track_1 = require("./types/track");
|
|
24
24
|
const logger_1 = require("./utils/logger");
|
|
25
|
+
const normalizeShadowIds_1 = require("./tracking/normalizeShadowIds");
|
|
25
26
|
exports.trackChangesPluginKey = new prosemirror_state_1.PluginKey('track-changes');
|
|
26
27
|
const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous', initialStatus: track_1.TrackChangesStatus.enabled }) => {
|
|
27
28
|
const { userID, debug, skipTrsWithMetas = [] } = opts;
|
|
@@ -105,6 +106,9 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous', initialStatu
|
|
|
105
106
|
if (changed) {
|
|
106
107
|
logger_1.log.warn('had to fix inconsistent changes in', createdTr);
|
|
107
108
|
}
|
|
109
|
+
if (createdTr.docChanged) {
|
|
110
|
+
createdTr = (0, normalizeShadowIds_1.normalizeShadowIds)(createdTr);
|
|
111
|
+
}
|
|
108
112
|
if (docChanged || createdTr.docChanged || changed) {
|
|
109
113
|
createdTr.setMeta('origin', exports.trackChangesPluginKey);
|
|
110
114
|
return (0, actions_1.setAction)(createdTr, actions_1.TrackChangesAction.refreshChanges, true);
|
package/dist/cjs/trackChanges.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*!
|
|
3
|
-
* ©
|
|
3
|
+
* © 2026 Atypon Systems LLC
|
|
4
4
|
*
|
|
5
5
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
* you may not use this file except in compliance with the License.
|
|
@@ -42,6 +42,7 @@ function trackChanges(tr, createdTr, oldState, userID, skipTrsWithMetas) {
|
|
|
42
42
|
transactionProcessing_1.filterMeaninglessMoveSteps,
|
|
43
43
|
]);
|
|
44
44
|
(0, transactionProcessing_1.changeMovedToInsertsOnSourceDeletion)(tr, createdTr, trContext);
|
|
45
|
-
|
|
45
|
+
const clearTr = (0, transactionProcessing_1.clearShadowsFromNewlyInserted)(tr, oldState);
|
|
46
|
+
return (0, trackTransaction_1.trackTransaction)(clearTr, oldState, createdTr, userID, clearedSteps, trContext);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* © 2026 Atypon Systems LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.SHADOW_ID_PREFIX = void 0;
|
|
19
|
+
exports.normalizeShadowIds = normalizeShadowIds;
|
|
20
|
+
const qualifiers_1 = require("./steps-trackers/qualifiers");
|
|
21
|
+
exports.SHADOW_ID_PREFIX = '__track__changes_shadow__';
|
|
22
|
+
function normalizeShadowIds(tr) {
|
|
23
|
+
const changes = [];
|
|
24
|
+
const shadowRanges = [];
|
|
25
|
+
function isInShadowRange(pos) {
|
|
26
|
+
return shadowRanges.some((range) => pos >= range.from && pos < range.to);
|
|
27
|
+
}
|
|
28
|
+
tr.doc.descendants((node, pos) => {
|
|
29
|
+
if ((0, qualifiers_1.isShadowDelete)(node) || (0, qualifiers_1.isMoved)(node)) {
|
|
30
|
+
shadowRanges.push({ from: pos, to: pos + node.nodeSize });
|
|
31
|
+
}
|
|
32
|
+
if (typeof node.attrs.id === 'string' && node.attrs.id) {
|
|
33
|
+
const currentId = node.attrs.id;
|
|
34
|
+
const hasShadowPrefix = currentId.startsWith(exports.SHADOW_ID_PREFIX);
|
|
35
|
+
const inShadow = isInShadowRange(pos);
|
|
36
|
+
if (inShadow && !hasShadowPrefix) {
|
|
37
|
+
changes.push({ pos, node, newId: exports.SHADOW_ID_PREFIX + currentId });
|
|
38
|
+
}
|
|
39
|
+
else if (!inShadow && hasShadowPrefix) {
|
|
40
|
+
changes.push({ pos, node, newId: currentId.slice(exports.SHADOW_ID_PREFIX.length) });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
for (let i = changes.length - 1; i >= 0; i--) {
|
|
46
|
+
const { pos, node, newId } = changes[i];
|
|
47
|
+
tr.setNodeMarkup(pos, null, Object.assign(Object.assign({}, node.attrs), { id: newId }));
|
|
48
|
+
}
|
|
49
|
+
return tr;
|
|
50
|
+
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.isDeletingPendingMovedNode = exports.isDirectPendingMoveDeletion = exports.isLiftStep = exports.isWrapStep = exports.isSplitStep = exports.isDeleteStep = void 0;
|
|
19
19
|
exports.isLiftStepForGap = isLiftStepForGap;
|
|
20
|
+
exports.isShadowDelete = isShadowDelete;
|
|
21
|
+
exports.isMoved = isMoved;
|
|
20
22
|
const ChangeSet_1 = require("../../ChangeSet");
|
|
21
23
|
const change_1 = require("../../types/change");
|
|
22
24
|
const isDeleteStep = (step) => step.from !== step.to && step.slice.content.size < step.to - step.from;
|
|
@@ -97,3 +99,17 @@ const isDeletingPendingMovedNode = (step, doc) => {
|
|
|
97
99
|
return undefined;
|
|
98
100
|
};
|
|
99
101
|
exports.isDeletingPendingMovedNode = isDeletingPendingMovedNode;
|
|
102
|
+
function isShadowDelete(node) {
|
|
103
|
+
if (node.attrs.dataTracked) {
|
|
104
|
+
const changes = node.attrs.dataTracked;
|
|
105
|
+
return changes.some(({ operation, moveNodeId }) => operation === 'delete' && moveNodeId);
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
function isMoved(node) {
|
|
110
|
+
if (node.attrs.dataTracked) {
|
|
111
|
+
const changes = node.attrs.dataTracked;
|
|
112
|
+
return changes.some(({ operation, status, moveNodeId }) => operation === change_1.CHANGE_OPERATION.delete && status === change_1.CHANGE_STATUS.pending && moveNodeId);
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
@@ -4,6 +4,8 @@ exports.trFromHistory = exports.filterMeaninglessMoveSteps = exports.changeMoved
|
|
|
4
4
|
exports.getIndentationOperationSteps = getIndentationOperationSteps;
|
|
5
5
|
exports.passThroughMeta = passThroughMeta;
|
|
6
6
|
exports.iterationIsValid = iterationIsValid;
|
|
7
|
+
exports.clearShadowsFromNewlyInserted = clearShadowsFromNewlyInserted;
|
|
8
|
+
const prosemirror_model_1 = require("prosemirror-model");
|
|
7
9
|
const prosemirror_transform_1 = require("prosemirror-transform");
|
|
8
10
|
const actions_1 = require("../actions");
|
|
9
11
|
const ChangeSet_1 = require("../ChangeSet");
|
|
@@ -162,3 +164,62 @@ const filterMeaninglessMoveSteps = (tr, context) => {
|
|
|
162
164
|
exports.filterMeaninglessMoveSteps = filterMeaninglessMoveSteps;
|
|
163
165
|
const trFromHistory = (tr) => Object.keys(tr.meta).find((s) => s.startsWith('history$'));
|
|
164
166
|
exports.trFromHistory = trFromHistory;
|
|
167
|
+
function clearShadowsFromNewlyInserted(tr, baseState) {
|
|
168
|
+
let hasShadowContent = false;
|
|
169
|
+
for (let i = 0; i < tr.steps.length; i++) {
|
|
170
|
+
const step = tr.steps[i];
|
|
171
|
+
if (step instanceof prosemirror_transform_1.ReplaceStep && step.slice.content.size) {
|
|
172
|
+
step.slice.content.descendants((node) => {
|
|
173
|
+
if ((0, qualifiers_1.isShadowDelete)(node)) {
|
|
174
|
+
hasShadowContent = true;
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
if (hasShadowContent)
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!hasShadowContent) {
|
|
183
|
+
return tr;
|
|
184
|
+
}
|
|
185
|
+
const newTr = baseState.tr;
|
|
186
|
+
for (let i = 0; i < tr.steps.length; i++) {
|
|
187
|
+
const step = tr.steps[i];
|
|
188
|
+
const clearStep = clearShadowContent(step);
|
|
189
|
+
const remapped = clearStep.map(newTr.mapping);
|
|
190
|
+
if (remapped) {
|
|
191
|
+
newTr.step(remapped);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
Object.keys(tr.meta).forEach((k) => {
|
|
195
|
+
newTr.setMeta(k, tr.getMeta(k));
|
|
196
|
+
});
|
|
197
|
+
return newTr;
|
|
198
|
+
}
|
|
199
|
+
function filterNodes(fragment, predicate) {
|
|
200
|
+
if (fragment.childCount === 0) {
|
|
201
|
+
return fragment;
|
|
202
|
+
}
|
|
203
|
+
const checked = [];
|
|
204
|
+
for (let i = 0; i < fragment.childCount; i++) {
|
|
205
|
+
const node = fragment.child(i);
|
|
206
|
+
if (predicate(node)) {
|
|
207
|
+
const newContent = filterNodes(node.content, predicate);
|
|
208
|
+
if (newContent !== node.content) {
|
|
209
|
+
const newNode = node.type.create(node.attrs, newContent, node.marks);
|
|
210
|
+
checked.push(newNode);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
checked.push(node);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return prosemirror_model_1.Fragment.fromArray(checked);
|
|
217
|
+
}
|
|
218
|
+
function clearShadowContent(step) {
|
|
219
|
+
if (step instanceof prosemirror_transform_1.ReplaceStep && step.slice.content.size) {
|
|
220
|
+
const newContent = filterNodes(step.slice.content, (node) => !(0, qualifiers_1.isShadowDelete)(node));
|
|
221
|
+
const newSlice = new prosemirror_model_1.Slice(newContent, step.slice.openStart, step.slice.openEnd);
|
|
222
|
+
return new prosemirror_transform_1.ReplaceStep(step.from, step.to, newSlice);
|
|
223
|
+
}
|
|
224
|
+
return step;
|
|
225
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* © 2025 Atypon Systems LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.Include = void 0;
|
|
19
|
+
exports.isValid = isValid;
|
|
20
|
+
exports.clear = clear;
|
|
21
|
+
const qualifiers_1 = require("../tracking/steps-trackers/qualifiers");
|
|
22
|
+
const shared_utils_1 = require("./shared-utils");
|
|
23
|
+
var Include;
|
|
24
|
+
(function (Include) {
|
|
25
|
+
Include[Include["CLEAN"] = 0] = "CLEAN";
|
|
26
|
+
Include[Include["PENDING"] = 1] = "PENDING";
|
|
27
|
+
Include[Include["ALL"] = 2] = "ALL";
|
|
28
|
+
})(Include || (exports.Include = Include = {}));
|
|
29
|
+
function isValid(node, include = Include.PENDING) {
|
|
30
|
+
if (include == Include.CLEAN &&
|
|
31
|
+
((0, qualifiers_1.isShadowDelete)(node) || (0, qualifiers_1.isMoved)(node) || (0, shared_utils_1.isDeleted)(node) || (0, shared_utils_1.isDeletedText)(node))) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (include == Include.PENDING && ((0, qualifiers_1.isShadowDelete)(node) || (0, qualifiers_1.isMoved)(node))) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
function clear(node, include = Include.PENDING) {
|
|
40
|
+
return {
|
|
41
|
+
descendants: (fn) => {
|
|
42
|
+
node.descendants((node, pos, parent, index) => {
|
|
43
|
+
if (!isValid(node, include)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return fn(node, pos, parent, index);
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
forEach: (fn) => {
|
|
50
|
+
node.forEach((node, offset, index) => {
|
|
51
|
+
if (!isValid(node, include)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
fn(node, offset, index);
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
nodesBetween: (from, to, fn, startPos) => {
|
|
58
|
+
node.nodesBetween(from, to, (node, pos, parent, index) => {
|
|
59
|
+
if (!isValid(node, include)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
fn(node, pos, parent, index);
|
|
63
|
+
}, startPos);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addTrackChangesClassNames = exports.addTrackChangesAttributes = void 0;
|
|
4
|
+
exports.isDeleted = isDeleted;
|
|
5
|
+
exports.isPendingInsert = isPendingInsert;
|
|
6
|
+
exports.isPending = isPending;
|
|
7
|
+
exports.isPendingSetAttrs = isPendingSetAttrs;
|
|
8
|
+
exports.getChangeClasses = getChangeClasses;
|
|
9
|
+
exports.isTracked = isTracked;
|
|
10
|
+
exports.isDeletedText = isDeletedText;
|
|
11
|
+
exports.getActualTextContent = getActualTextContent;
|
|
12
|
+
exports.sanitizeAttrsChange = sanitizeAttrsChange;
|
|
13
|
+
const transform_1 = require("@manuscripts/transform");
|
|
14
|
+
const change_1 = require("../types/change");
|
|
15
|
+
function isDeleted(node) {
|
|
16
|
+
if (node.attrs.dataTracked) {
|
|
17
|
+
const changes = node.attrs.dataTracked;
|
|
18
|
+
return changes.some(({ operation }) => operation === 'delete');
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function isPendingInsert(node) {
|
|
23
|
+
if (node.attrs.dataTracked) {
|
|
24
|
+
const changes = node.attrs.dataTracked;
|
|
25
|
+
return changes.some(({ operation, status }) => operation === 'insert' && status == 'pending');
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
function isPending(node) {
|
|
30
|
+
if (node.attrs.dataTracked) {
|
|
31
|
+
const changes = node.attrs.dataTracked;
|
|
32
|
+
return changes.some(({ status }) => status == 'pending');
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function isPendingSetAttrs(node) {
|
|
37
|
+
if (node.attrs.dataTracked) {
|
|
38
|
+
const changes = node.attrs.dataTracked;
|
|
39
|
+
return changes.some(({ operation, status }) => operation === 'set_attrs' && status == 'pending');
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
function getChangeClasses(dataTracked) {
|
|
44
|
+
const classes = [];
|
|
45
|
+
if (dataTracked) {
|
|
46
|
+
const changes = dataTracked;
|
|
47
|
+
const operationClasses = new Map([
|
|
48
|
+
['insert', 'inserted'],
|
|
49
|
+
['delete', 'deleted'],
|
|
50
|
+
['set_attrs', 'set_attrs'],
|
|
51
|
+
]);
|
|
52
|
+
changes.forEach(({ operation, status }) => classes.push(operationClasses.get(operation) || '', status));
|
|
53
|
+
}
|
|
54
|
+
return classes;
|
|
55
|
+
}
|
|
56
|
+
function isTracked(node) {
|
|
57
|
+
if (node.attrs.dataTracked) {
|
|
58
|
+
const changes = node.attrs.dataTracked;
|
|
59
|
+
return changes.some(({ operation }) => operation === 'insert' || operation === 'delete' || operation === 'set_attrs');
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
function isDeletedText(node) {
|
|
64
|
+
var _a, _b, _c, _d;
|
|
65
|
+
if (node.type === transform_1.schema.nodes.text && node.marks.length) {
|
|
66
|
+
const deleteMark = node.marks.find((mark) => mark.type === transform_1.schema.marks.tracked_delete);
|
|
67
|
+
if (deleteMark &&
|
|
68
|
+
((_b = (_a = deleteMark.attrs) === null || _a === void 0 ? void 0 : _a.dataTracked) === null || _b === void 0 ? void 0 : _b.status) &&
|
|
69
|
+
'pending' === ((_d = (_c = deleteMark.attrs) === null || _c === void 0 ? void 0 : _c.dataTracked) === null || _d === void 0 ? void 0 : _d.status)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
function getActualTextContent(fragment) {
|
|
76
|
+
let finalContent = '';
|
|
77
|
+
function getContent(fragment) {
|
|
78
|
+
fragment.forEach((node) => {
|
|
79
|
+
if (node.type !== transform_1.schema.nodes.text) {
|
|
80
|
+
finalContent += getContent(node.content);
|
|
81
|
+
}
|
|
82
|
+
if (!isDeletedText(node)) {
|
|
83
|
+
finalContent += node.textContent;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
getContent(fragment);
|
|
88
|
+
return finalContent;
|
|
89
|
+
}
|
|
90
|
+
function sanitizeAttrsChange(newAttr, currentAttrs) {
|
|
91
|
+
return Object.keys(newAttr).reduce((acc, attr) => {
|
|
92
|
+
const key = attr;
|
|
93
|
+
if (!currentAttrs[key] && currentAttrs[key] !== 0 && !newAttr[key] && newAttr[key] !== 0) {
|
|
94
|
+
return acc;
|
|
95
|
+
}
|
|
96
|
+
acc[key] = newAttr[key];
|
|
97
|
+
return acc;
|
|
98
|
+
}, {});
|
|
99
|
+
}
|
|
100
|
+
const addTrackChangesAttributes = (attrs, dom) => {
|
|
101
|
+
dom.removeAttribute('data-track-id');
|
|
102
|
+
dom.removeAttribute('data-track-op');
|
|
103
|
+
dom.removeAttribute('data-track-status');
|
|
104
|
+
const changes = attrs.dataTracked;
|
|
105
|
+
if (!changes || !changes.length) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const change = changes[0];
|
|
109
|
+
dom.setAttribute('data-track-id', change.id);
|
|
110
|
+
dom.setAttribute('data-track-op', change.operation);
|
|
111
|
+
dom.setAttribute('data-track-status', change.status);
|
|
112
|
+
};
|
|
113
|
+
exports.addTrackChangesAttributes = addTrackChangesAttributes;
|
|
114
|
+
const classNames = new Map([
|
|
115
|
+
[change_1.CHANGE_OPERATION.insert, 'inserted'],
|
|
116
|
+
[change_1.CHANGE_OPERATION.delete, 'deleted'],
|
|
117
|
+
[change_1.CHANGE_OPERATION.set_node_attributes, 'set_attrs'],
|
|
118
|
+
]);
|
|
119
|
+
const addTrackChangesClassNames = (attrs, dom) => {
|
|
120
|
+
dom.classList.remove(...classNames.values());
|
|
121
|
+
const changes = attrs.dataTracked;
|
|
122
|
+
if (!changes || !changes.length) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const change = changes[0];
|
|
126
|
+
const className = classNames.get(change.operation);
|
|
127
|
+
className && dom.classList.add(className);
|
|
128
|
+
};
|
|
129
|
+
exports.addTrackChangesClassNames = addTrackChangesClassNames;
|
package/dist/es/index.js
CHANGED
package/dist/es/plugin.js
CHANGED
|
@@ -19,6 +19,7 @@ import { trackChanges } from './trackChanges';
|
|
|
19
19
|
import { trFromHistory } from './tracking/transactionProcessing';
|
|
20
20
|
import { TrackChangesStatus } from './types/track';
|
|
21
21
|
import { enableDebug, log } from './utils/logger';
|
|
22
|
+
import { normalizeShadowIds } from './tracking/normalizeShadowIds';
|
|
22
23
|
export const trackChangesPluginKey = new PluginKey('track-changes');
|
|
23
24
|
export const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous', initialStatus: TrackChangesStatus.enabled }) => {
|
|
24
25
|
const { userID, debug, skipTrsWithMetas = [] } = opts;
|
|
@@ -102,6 +103,9 @@ export const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous', initi
|
|
|
102
103
|
if (changed) {
|
|
103
104
|
log.warn('had to fix inconsistent changes in', createdTr);
|
|
104
105
|
}
|
|
106
|
+
if (createdTr.docChanged) {
|
|
107
|
+
createdTr = normalizeShadowIds(createdTr);
|
|
108
|
+
}
|
|
105
109
|
if (docChanged || createdTr.docChanged || changed) {
|
|
106
110
|
createdTr.setMeta('origin', trackChangesPluginKey);
|
|
107
111
|
return setAction(createdTr, TrackChangesAction.refreshChanges, true);
|
package/dist/es/trackChanges.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* ©
|
|
2
|
+
* © 2026 Atypon Systems LLC
|
|
3
3
|
*
|
|
4
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
* you may not use this file except in compliance with the License.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { getAction, TrackChangesAction } from './actions';
|
|
17
17
|
import { processStepsBeforeTracking } from './tracking/lib/processStepsBeforeTracking';
|
|
18
18
|
import { trackTransaction } from './tracking/trackTransaction';
|
|
19
|
-
import { changeMovedToInsertsOnSourceDeletion, filterMeaninglessMoveSteps, getIndentationOperationSteps, getMoveOperationsSteps, trFromHistory, } from './tracking/transactionProcessing';
|
|
19
|
+
import { changeMovedToInsertsOnSourceDeletion, clearShadowsFromNewlyInserted, filterMeaninglessMoveSteps, getIndentationOperationSteps, getMoveOperationsSteps, trFromHistory, } from './tracking/transactionProcessing';
|
|
20
20
|
export function trackChanges(tr, createdTr, oldState, userID, skipTrsWithMetas) {
|
|
21
21
|
var _a;
|
|
22
22
|
const wasAppended = tr.getMeta('appendedTransaction');
|
|
@@ -39,6 +39,7 @@ export function trackChanges(tr, createdTr, oldState, userID, skipTrsWithMetas)
|
|
|
39
39
|
filterMeaninglessMoveSteps,
|
|
40
40
|
]);
|
|
41
41
|
changeMovedToInsertsOnSourceDeletion(tr, createdTr, trContext);
|
|
42
|
-
|
|
42
|
+
const clearTr = clearShadowsFromNewlyInserted(tr, oldState);
|
|
43
|
+
return trackTransaction(clearTr, oldState, createdTr, userID, clearedSteps, trContext);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2026 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 { isMoved, isShadowDelete } from './steps-trackers/qualifiers';
|
|
17
|
+
export const SHADOW_ID_PREFIX = '__track__changes_shadow__';
|
|
18
|
+
export function normalizeShadowIds(tr) {
|
|
19
|
+
const changes = [];
|
|
20
|
+
const shadowRanges = [];
|
|
21
|
+
function isInShadowRange(pos) {
|
|
22
|
+
return shadowRanges.some((range) => pos >= range.from && pos < range.to);
|
|
23
|
+
}
|
|
24
|
+
tr.doc.descendants((node, pos) => {
|
|
25
|
+
if (isShadowDelete(node) || isMoved(node)) {
|
|
26
|
+
shadowRanges.push({ from: pos, to: pos + node.nodeSize });
|
|
27
|
+
}
|
|
28
|
+
if (typeof node.attrs.id === 'string' && node.attrs.id) {
|
|
29
|
+
const currentId = node.attrs.id;
|
|
30
|
+
const hasShadowPrefix = currentId.startsWith(SHADOW_ID_PREFIX);
|
|
31
|
+
const inShadow = isInShadowRange(pos);
|
|
32
|
+
if (inShadow && !hasShadowPrefix) {
|
|
33
|
+
changes.push({ pos, node, newId: SHADOW_ID_PREFIX + currentId });
|
|
34
|
+
}
|
|
35
|
+
else if (!inShadow && hasShadowPrefix) {
|
|
36
|
+
changes.push({ pos, node, newId: currentId.slice(SHADOW_ID_PREFIX.length) });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
for (let i = changes.length - 1; i >= 0; i--) {
|
|
42
|
+
const { pos, node, newId } = changes[i];
|
|
43
|
+
tr.setNodeMarkup(pos, null, Object.assign(Object.assign({}, node.attrs), { id: newId }));
|
|
44
|
+
}
|
|
45
|
+
return tr;
|
|
46
|
+
}
|
|
@@ -87,3 +87,17 @@ export const isDeletingPendingMovedNode = (step, doc) => {
|
|
|
87
87
|
}
|
|
88
88
|
return undefined;
|
|
89
89
|
};
|
|
90
|
+
export function isShadowDelete(node) {
|
|
91
|
+
if (node.attrs.dataTracked) {
|
|
92
|
+
const changes = node.attrs.dataTracked;
|
|
93
|
+
return changes.some(({ operation, moveNodeId }) => operation === 'delete' && moveNodeId);
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
export function isMoved(node) {
|
|
98
|
+
if (node.attrs.dataTracked) {
|
|
99
|
+
const changes = node.attrs.dataTracked;
|
|
100
|
+
return changes.some(({ operation, status, moveNodeId }) => operation === CHANGE_OPERATION.delete && status === CHANGE_STATUS.pending && moveNodeId);
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { Fragment, Slice } from 'prosemirror-model';
|
|
1
2
|
import { ReplaceStep } from 'prosemirror-transform';
|
|
2
3
|
import { isIndentationAction, TrackChangesAction } from '../actions';
|
|
3
4
|
import { ChangeSet } from '../ChangeSet';
|
|
4
5
|
import { CHANGE_OPERATION, CHANGE_STATUS } from '../types/change';
|
|
5
6
|
import { uuidv4 } from '../utils/uuidv4';
|
|
6
|
-
import { isDeletingPendingMovedNode, isDirectPendingMoveDeletion } from './steps-trackers/qualifiers';
|
|
7
|
+
import { isDeletingPendingMovedNode, isDirectPendingMoveDeletion, isShadowDelete, } from './steps-trackers/qualifiers';
|
|
7
8
|
export function getIndentationOperationSteps(tr, trContext) {
|
|
8
9
|
if (isIndentationAction(trContext.action)) {
|
|
9
10
|
const moveId = uuidv4();
|
|
@@ -151,3 +152,62 @@ export const filterMeaninglessMoveSteps = (tr, context) => {
|
|
|
151
152
|
return cleanSteps;
|
|
152
153
|
};
|
|
153
154
|
export const trFromHistory = (tr) => Object.keys(tr.meta).find((s) => s.startsWith('history$'));
|
|
155
|
+
export function clearShadowsFromNewlyInserted(tr, baseState) {
|
|
156
|
+
let hasShadowContent = false;
|
|
157
|
+
for (let i = 0; i < tr.steps.length; i++) {
|
|
158
|
+
const step = tr.steps[i];
|
|
159
|
+
if (step instanceof ReplaceStep && step.slice.content.size) {
|
|
160
|
+
step.slice.content.descendants((node) => {
|
|
161
|
+
if (isShadowDelete(node)) {
|
|
162
|
+
hasShadowContent = true;
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
if (hasShadowContent)
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!hasShadowContent) {
|
|
171
|
+
return tr;
|
|
172
|
+
}
|
|
173
|
+
const newTr = baseState.tr;
|
|
174
|
+
for (let i = 0; i < tr.steps.length; i++) {
|
|
175
|
+
const step = tr.steps[i];
|
|
176
|
+
const clearStep = clearShadowContent(step);
|
|
177
|
+
const remapped = clearStep.map(newTr.mapping);
|
|
178
|
+
if (remapped) {
|
|
179
|
+
newTr.step(remapped);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
Object.keys(tr.meta).forEach((k) => {
|
|
183
|
+
newTr.setMeta(k, tr.getMeta(k));
|
|
184
|
+
});
|
|
185
|
+
return newTr;
|
|
186
|
+
}
|
|
187
|
+
function filterNodes(fragment, predicate) {
|
|
188
|
+
if (fragment.childCount === 0) {
|
|
189
|
+
return fragment;
|
|
190
|
+
}
|
|
191
|
+
const checked = [];
|
|
192
|
+
for (let i = 0; i < fragment.childCount; i++) {
|
|
193
|
+
const node = fragment.child(i);
|
|
194
|
+
if (predicate(node)) {
|
|
195
|
+
const newContent = filterNodes(node.content, predicate);
|
|
196
|
+
if (newContent !== node.content) {
|
|
197
|
+
const newNode = node.type.create(node.attrs, newContent, node.marks);
|
|
198
|
+
checked.push(newNode);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
checked.push(node);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return Fragment.fromArray(checked);
|
|
205
|
+
}
|
|
206
|
+
function clearShadowContent(step) {
|
|
207
|
+
if (step instanceof ReplaceStep && step.slice.content.size) {
|
|
208
|
+
const newContent = filterNodes(step.slice.content, (node) => !isShadowDelete(node));
|
|
209
|
+
const newSlice = new Slice(newContent, step.slice.openStart, step.slice.openEnd);
|
|
210
|
+
return new ReplaceStep(step.from, step.to, newSlice);
|
|
211
|
+
}
|
|
212
|
+
return step;
|
|
213
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2025 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 { isMoved, isShadowDelete } from '../tracking/steps-trackers/qualifiers';
|
|
17
|
+
import { isDeleted, isDeletedText } from './shared-utils';
|
|
18
|
+
export var Include;
|
|
19
|
+
(function (Include) {
|
|
20
|
+
Include[Include["CLEAN"] = 0] = "CLEAN";
|
|
21
|
+
Include[Include["PENDING"] = 1] = "PENDING";
|
|
22
|
+
Include[Include["ALL"] = 2] = "ALL";
|
|
23
|
+
})(Include || (Include = {}));
|
|
24
|
+
export function isValid(node, include = Include.PENDING) {
|
|
25
|
+
if (include == Include.CLEAN &&
|
|
26
|
+
(isShadowDelete(node) || isMoved(node) || isDeleted(node) || isDeletedText(node))) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (include == Include.PENDING && (isShadowDelete(node) || isMoved(node))) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
export function clear(node, include = Include.PENDING) {
|
|
35
|
+
return {
|
|
36
|
+
descendants: (fn) => {
|
|
37
|
+
node.descendants((node, pos, parent, index) => {
|
|
38
|
+
if (!isValid(node, include)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return fn(node, pos, parent, index);
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
forEach: (fn) => {
|
|
45
|
+
node.forEach((node, offset, index) => {
|
|
46
|
+
if (!isValid(node, include)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
fn(node, offset, index);
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
nodesBetween: (from, to, fn, startPos) => {
|
|
53
|
+
node.nodesBetween(from, to, (node, pos, parent, index) => {
|
|
54
|
+
if (!isValid(node, include)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
fn(node, pos, parent, index);
|
|
58
|
+
}, startPos);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { schema } from '@manuscripts/transform';
|
|
2
|
+
import { CHANGE_OPERATION } from '../types/change';
|
|
3
|
+
export function isDeleted(node) {
|
|
4
|
+
if (node.attrs.dataTracked) {
|
|
5
|
+
const changes = node.attrs.dataTracked;
|
|
6
|
+
return changes.some(({ operation }) => operation === 'delete');
|
|
7
|
+
}
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
export function isPendingInsert(node) {
|
|
11
|
+
if (node.attrs.dataTracked) {
|
|
12
|
+
const changes = node.attrs.dataTracked;
|
|
13
|
+
return changes.some(({ operation, status }) => operation === 'insert' && status == 'pending');
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
export function isPending(node) {
|
|
18
|
+
if (node.attrs.dataTracked) {
|
|
19
|
+
const changes = node.attrs.dataTracked;
|
|
20
|
+
return changes.some(({ status }) => status == 'pending');
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
export function isPendingSetAttrs(node) {
|
|
25
|
+
if (node.attrs.dataTracked) {
|
|
26
|
+
const changes = node.attrs.dataTracked;
|
|
27
|
+
return changes.some(({ operation, status }) => operation === 'set_attrs' && status == 'pending');
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
export function getChangeClasses(dataTracked) {
|
|
32
|
+
const classes = [];
|
|
33
|
+
if (dataTracked) {
|
|
34
|
+
const changes = dataTracked;
|
|
35
|
+
const operationClasses = new Map([
|
|
36
|
+
['insert', 'inserted'],
|
|
37
|
+
['delete', 'deleted'],
|
|
38
|
+
['set_attrs', 'set_attrs'],
|
|
39
|
+
]);
|
|
40
|
+
changes.forEach(({ operation, status }) => classes.push(operationClasses.get(operation) || '', status));
|
|
41
|
+
}
|
|
42
|
+
return classes;
|
|
43
|
+
}
|
|
44
|
+
export function isTracked(node) {
|
|
45
|
+
if (node.attrs.dataTracked) {
|
|
46
|
+
const changes = node.attrs.dataTracked;
|
|
47
|
+
return changes.some(({ operation }) => operation === 'insert' || operation === 'delete' || operation === 'set_attrs');
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
export function isDeletedText(node) {
|
|
52
|
+
var _a, _b, _c, _d;
|
|
53
|
+
if (node.type === schema.nodes.text && node.marks.length) {
|
|
54
|
+
const deleteMark = node.marks.find((mark) => mark.type === schema.marks.tracked_delete);
|
|
55
|
+
if (deleteMark &&
|
|
56
|
+
((_b = (_a = deleteMark.attrs) === null || _a === void 0 ? void 0 : _a.dataTracked) === null || _b === void 0 ? void 0 : _b.status) &&
|
|
57
|
+
'pending' === ((_d = (_c = deleteMark.attrs) === null || _c === void 0 ? void 0 : _c.dataTracked) === null || _d === void 0 ? void 0 : _d.status)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
export function getActualTextContent(fragment) {
|
|
64
|
+
let finalContent = '';
|
|
65
|
+
function getContent(fragment) {
|
|
66
|
+
fragment.forEach((node) => {
|
|
67
|
+
if (node.type !== schema.nodes.text) {
|
|
68
|
+
finalContent += getContent(node.content);
|
|
69
|
+
}
|
|
70
|
+
if (!isDeletedText(node)) {
|
|
71
|
+
finalContent += node.textContent;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
getContent(fragment);
|
|
76
|
+
return finalContent;
|
|
77
|
+
}
|
|
78
|
+
export function sanitizeAttrsChange(newAttr, currentAttrs) {
|
|
79
|
+
return Object.keys(newAttr).reduce((acc, attr) => {
|
|
80
|
+
const key = attr;
|
|
81
|
+
if (!currentAttrs[key] && currentAttrs[key] !== 0 && !newAttr[key] && newAttr[key] !== 0) {
|
|
82
|
+
return acc;
|
|
83
|
+
}
|
|
84
|
+
acc[key] = newAttr[key];
|
|
85
|
+
return acc;
|
|
86
|
+
}, {});
|
|
87
|
+
}
|
|
88
|
+
export const addTrackChangesAttributes = (attrs, dom) => {
|
|
89
|
+
dom.removeAttribute('data-track-id');
|
|
90
|
+
dom.removeAttribute('data-track-op');
|
|
91
|
+
dom.removeAttribute('data-track-status');
|
|
92
|
+
const changes = attrs.dataTracked;
|
|
93
|
+
if (!changes || !changes.length) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const change = changes[0];
|
|
97
|
+
dom.setAttribute('data-track-id', change.id);
|
|
98
|
+
dom.setAttribute('data-track-op', change.operation);
|
|
99
|
+
dom.setAttribute('data-track-status', change.status);
|
|
100
|
+
};
|
|
101
|
+
const classNames = new Map([
|
|
102
|
+
[CHANGE_OPERATION.insert, 'inserted'],
|
|
103
|
+
[CHANGE_OPERATION.delete, 'deleted'],
|
|
104
|
+
[CHANGE_OPERATION.set_node_attributes, 'set_attrs'],
|
|
105
|
+
]);
|
|
106
|
+
export const addTrackChangesClassNames = (attrs, dom) => {
|
|
107
|
+
dom.classList.remove(...classNames.values());
|
|
108
|
+
const changes = attrs.dataTracked;
|
|
109
|
+
if (!changes || !changes.length) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const change = changes[0];
|
|
113
|
+
const className = classNames.get(change.operation);
|
|
114
|
+
className && dom.classList.add(className);
|
|
115
|
+
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2026 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 { Transaction } from 'prosemirror-state';
|
|
17
|
+
export declare const SHADOW_ID_PREFIX = "__track__changes_shadow__";
|
|
18
|
+
export declare function normalizeShadowIds(tr: Transaction): Transaction;
|
|
@@ -28,3 +28,5 @@ export declare function isLiftStepForGap(gap: {
|
|
|
28
28
|
}, node: PMNode, to: number): boolean;
|
|
29
29
|
export declare const isDirectPendingMoveDeletion: (step: ReplaceStep, doc: PMNode, movingSteps: Map<ReplaceStep, string>) => boolean;
|
|
30
30
|
export declare const isDeletingPendingMovedNode: (step: ReplaceStep, doc: PMNode) => string | undefined;
|
|
31
|
+
export declare function isShadowDelete(node: PMNode): boolean;
|
|
32
|
+
export declare function isMoved(node: PMNode): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Node as PMNode } from 'prosemirror-model';
|
|
2
|
-
import { Transaction } from 'prosemirror-state';
|
|
2
|
+
import { EditorState, Transaction } from 'prosemirror-state';
|
|
3
3
|
import { ReplaceStep, Step } from 'prosemirror-transform';
|
|
4
4
|
import { TrTrackingContext } from './types';
|
|
5
5
|
export declare function getIndentationOperationSteps(tr: Transaction, trContext: TrTrackingContext): void;
|
|
@@ -10,3 +10,4 @@ export declare const getMoveOperationsSteps: (tr: Transaction, context: TrTracki
|
|
|
10
10
|
export declare const changeMovedToInsertsOnSourceDeletion: (tr: Transaction, newTr: Transaction, trContext: TrTrackingContext) => void;
|
|
11
11
|
export declare const filterMeaninglessMoveSteps: (tr: Transaction, context: TrTrackingContext) => (Step | null)[];
|
|
12
12
|
export declare const trFromHistory: (tr: Transaction) => string | undefined;
|
|
13
|
+
export declare function clearShadowsFromNewlyInserted(tr: Transaction, baseState: EditorState): Transaction;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* © 2025 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 { Node as PMNode } from 'prosemirror-model';
|
|
17
|
+
type callback = (node: PMNode, pos: number, parent: PMNode | null, index: number) => void | boolean;
|
|
18
|
+
type forEachCallback = (node: PMNode, offset: number, index: number) => void;
|
|
19
|
+
export declare enum Include {
|
|
20
|
+
CLEAN = 0,
|
|
21
|
+
PENDING = 1,
|
|
22
|
+
ALL = 2
|
|
23
|
+
}
|
|
24
|
+
export declare function isValid(node: PMNode, include?: Include): boolean;
|
|
25
|
+
export declare function clear(node: PMNode, include?: Include): {
|
|
26
|
+
descendants: (fn: callback) => void;
|
|
27
|
+
forEach: (fn: forEachCallback) => void;
|
|
28
|
+
nodesBetween: (from: number, to: number, fn: (node: PMNode, pos: number, parent: PMNode | null, index: number) => void | boolean, startPos?: number) => void;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Attrs, Fragment, Mark, Node as ProsemirrorNode } from 'prosemirror-model';
|
|
2
|
+
import { TrackedAttrs } from '../types/change';
|
|
3
|
+
export declare function isDeleted(node: ProsemirrorNode | Mark): boolean;
|
|
4
|
+
export declare function isPendingInsert(node: ProsemirrorNode): boolean;
|
|
5
|
+
export declare function isPending(node: ProsemirrorNode): boolean;
|
|
6
|
+
export declare function isPendingSetAttrs(node: ProsemirrorNode): boolean;
|
|
7
|
+
export declare function getChangeClasses(dataTracked?: TrackedAttrs[]): string[];
|
|
8
|
+
export declare function isTracked(node: ProsemirrorNode | Mark): boolean;
|
|
9
|
+
export declare function isDeletedText(node: ProsemirrorNode): boolean;
|
|
10
|
+
export declare function getActualTextContent(fragment: Fragment): string;
|
|
11
|
+
export declare function sanitizeAttrsChange<T extends ProsemirrorNode>(newAttr: T['attrs'], currentAttrs: T['attrs']): T["attrs"];
|
|
12
|
+
export declare const addTrackChangesAttributes: (attrs: Attrs, dom: Element) => void;
|
|
13
|
+
export declare const addTrackChangesClassNames: (attrs: Attrs, dom: Element) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manuscripts/track-changes-plugin",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.9",
|
|
4
4
|
"author": "Atypon Systems LLC",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/Atypon-OpenSource/manuscripts-track-changes-plugin",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"build:es": "tsc --outDir dist/es --declarationDir dist/types --declaration",
|
|
18
18
|
"dev": "npm-run-all --parallel 'build:* --watch'",
|
|
19
19
|
"test": "jest --runInBand",
|
|
20
|
-
"test-rs": "jest -i test/
|
|
20
|
+
"test-rs": "jest -i test/text/text.test.ts",
|
|
21
21
|
"format": "prettier --write \"*.+(js|json|yml|yaml|ts|md|graphql|mdx)\" src/ test/",
|
|
22
22
|
"typecheck": "tsc --project tsconfig.test.json --noEmit",
|
|
23
23
|
"lint": "eslint --cache --ext .js,.ts, ./src ./test",
|