@manuscripts/track-changes-plugin 0.0.2 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,16 +40,16 @@ Enable or disable the plugin with:
40
40
  import { trackCommands, TrackChangesStatus } from '@manuscripts/track-changes-plugin'
41
41
 
42
42
  // toggle
43
- cmd(view.state, view.dispatch, view)(trackCommands.setTrackingStatus())
43
+ trackCommands.setTrackingStatus())(view.state, view.dispatch, view)
44
44
 
45
45
  // enable
46
- cmd(view.state, view.dispatch, view)(trackCommands.setTrackingStatus(TrackChangesStatus.enabled))
46
+ trackCommands.setTrackingStatus(TrackChangesStatus.enabled))(view.state, view.dispatch, view)
47
47
 
48
48
  // disable
49
- cmd(view.state, view.dispatch, view)(trackCommands.setTrackingStatus(TrackChangesStatus.disabled))
49
+ trackCommands.setTrackingStatus(TrackChangesStatus.disabled))(view.state, view.dispatch, view)
50
50
 
51
51
  // sets editor's 'editable' prop to false, making it ready-only
52
- cmd(view.state, view.dispatch, view)(trackCommands.setTrackingStatus(TrackChangesStatus.viewSnapshots))
52
+ trackCommands.setTrackingStatus(TrackChangesStatus.viewSnapshots))(view.state, view.dispatch, view)
53
53
  ```
54
54
 
55
55
  See an example app at https://github.com/Atypon-OpenSource/manuscripts-quarterback/tree/main/examples-packages/client
package/dist/index.es.js CHANGED
@@ -327,8 +327,9 @@ function deleteNode(node, pos, tr) {
327
327
  var _a;
328
328
  const startPos = tr.doc.resolve(pos + 1);
329
329
  const range = startPos.blockRange(tr.doc.resolve(startPos.pos - 2 + node.nodeSize));
330
- const targetDepth = range ? Number(liftTarget(range)) : NaN;
331
- if (range && !Number.isNaN(targetDepth)) {
330
+ const targetDepth = range && liftTarget(range);
331
+ // Check with typeof since with old prosemirror-transform targetDepth is undefined
332
+ if (range && typeof targetDepth === 'number') {
332
333
  return tr.lift(range, targetDepth);
333
334
  }
334
335
  const resPos = tr.doc.resolve(pos);
@@ -501,9 +502,11 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mappi
501
502
  if (change.attrs.status === CHANGE_STATUS.pending) {
502
503
  return;
503
504
  }
504
- const from = deleteMap.map(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = ChangeSet.shouldNotDelete(change);
505
+ // Map change.from and skip those which dont need to be applied
506
+ // or were already deleted by an applied block delete
507
+ const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || ChangeSet.shouldNotDelete(change);
505
508
  if (!node) {
506
- log.warn('no node found to update for change', change);
509
+ !deleted && log.warn('no node found to update for change', change);
507
510
  return;
508
511
  }
509
512
  if (ChangeSet.isTextChange(change) && noChangeNeeded) {
@@ -927,6 +930,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
927
930
  // But from what I remember what it safeguards against is, when you've already deleted a node
928
931
  // say an inserted blockquote that had all its children deleted, nodesBetween still iterates over those
929
932
  // nodes and therefore we have to make this check to ensure they still exist in the doc.
933
+ //
930
934
  if (nodeEnd > offsetFrom && !nodeWasDeleted && !wasWithinGap) {
931
935
  // |<p>asdf</p>| -> node deleted completely
932
936
  const nodeCompletelyDeleted = offsetPos >= offsetFrom && nodeEnd <= offsetTo;
@@ -1207,11 +1211,9 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1207
1211
  * This skips the direct dependency to prosemirror-state where multiple versions might cause conflicts
1208
1212
  * as the created instances might belong to different prosemirror-state import than one used in the editor.
1209
1213
  * @param sel
1210
- * @param doc
1211
- * @param from
1212
1214
  * @returns
1213
1215
  */
1214
- const getSelectionStaticCreate = (sel, doc, from) => Object.getPrototypeOf(sel).constructor.create(doc, from);
1216
+ const getSelectionStaticConstructor = (sel) => Object.getPrototypeOf(sel).constructor;
1215
1217
  /**
1216
1218
  * Inverts transactions to wrap their contents/operations with track data instead
1217
1219
  *
@@ -1255,7 +1257,12 @@ function trackTransaction(tr, oldState, newTr, userID) {
1255
1257
  else if (step instanceof ReplaceStep) {
1256
1258
  const selectionPos = trackReplaceStep(step, oldState, newTr, emptyAttrs);
1257
1259
  if (!wasNodeSelection) {
1258
- newTr.setSelection(getSelectionStaticCreate(tr.selection, newTr.doc, selectionPos));
1260
+ const sel = getSelectionStaticConstructor(tr.selection);
1261
+ // Use Selection.near to fix selections that point to a block node instead of inline content
1262
+ // eg when inserting a complete new paragraph. -1 finds the first valid position moving backwards
1263
+ // inside the content
1264
+ const near = sel.near(newTr.doc.resolve(selectionPos), -1);
1265
+ newTr.setSelection(near);
1259
1266
  }
1260
1267
  }
1261
1268
  else if (step instanceof ReplaceAroundStep) {
@@ -1266,13 +1273,12 @@ function trackTransaction(tr, oldState, newTr, userID) {
1266
1273
  // TODO: here we could check whether adjacent inserts & deletes cancel each other out.
1267
1274
  // However, this should not be done by diffing and only matching node or char by char instead since
1268
1275
  // it's A easier and B more intuitive to user.
1269
- const { meta } = tr;
1270
- // This is quite non-optimal in some sense but to ensure no information is lost
1271
- // we have to re-add all the old meta keys, such as inputType or uiEvent.
1272
- // This should prevent bugs incase other plugins/widgets rely upon them existing (and they
1273
- // are not able to process the transactions before track-changes).
1274
- // TODO: will this cause race-condition if a meta causes another appendTransaction to fire
1275
- Object.keys(meta).forEach((key) => newTr.setMeta(key, tr.getMeta(key)));
1276
+ // The old meta keys are not copied to the new transaction since this will cause race-conditions
1277
+ // when a single meta-field is expected to having been processed / removed. Generic input meta keys,
1278
+ // inputType and uiEvent, are re-added since some plugins might depend on them and process the transaction
1279
+ // after track-changes plugin.
1280
+ tr.getMeta('inputType') && newTr.setMeta('inputType', tr.getMeta('inputType'));
1281
+ tr.getMeta('uiEvent') && newTr.setMeta('uiEvent', tr.getMeta('uiEvent'));
1276
1282
  });
1277
1283
  // This is kinda hacky solution at the moment to maintain NodeSelections over transactions
1278
1284
  // These are required by at least cross-references that need it to activate the selector pop-up
@@ -1280,7 +1286,8 @@ function trackTransaction(tr, oldState, newTr, userID) {
1280
1286
  const mappedPos = newTr.mapping.map(tr.selection.from);
1281
1287
  const resPos = newTr.doc.resolve(mappedPos);
1282
1288
  const nodePos = mappedPos - (((_a = resPos.nodeBefore) === null || _a === void 0 ? void 0 : _a.nodeSize) || 0);
1283
- newTr.setSelection(getSelectionStaticCreate(tr.selection, newTr.doc, nodePos));
1289
+ const sel = getSelectionStaticConstructor(tr.selection);
1290
+ newTr.setSelection(sel.create(newTr.doc, nodePos));
1284
1291
  }
1285
1292
  log.info('NEW transaction', newTr);
1286
1293
  return newTr;
@@ -1330,7 +1337,8 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1330
1337
  key: trackChangesPluginKey,
1331
1338
  props: {
1332
1339
  editable(state) {
1333
- return this.getState(state).status !== TrackChangesStatus.viewSnapshots;
1340
+ var _a;
1341
+ return ((_a = trackChangesPluginKey.getState(state)) === null || _a === void 0 ? void 0 : _a.status) !== TrackChangesStatus.viewSnapshots;
1334
1342
  },
1335
1343
  },
1336
1344
  state: {
@@ -1400,7 +1408,6 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1400
1408
  (wasAppended && getAction(wasAppended, TrackChangesAction.skipTrack));
1401
1409
  if (tr.docChanged && !skipMetaUsed && !skipTrackUsed && !tr.getMeta('history$')) {
1402
1410
  createdTr = trackTransaction(tr, oldState, createdTr, userID);
1403
- createdTr.setMeta('origin', trackChangesPluginKey);
1404
1411
  infiniteLoopCounter.iters += 1;
1405
1412
  }
1406
1413
  docChanged = docChanged || tr.docChanged;
@@ -1427,6 +1434,7 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1427
1434
  log.warn('had to fix inconsistent changes in', createdTr);
1428
1435
  }
1429
1436
  if (docChanged || createdTr.docChanged || changed) {
1437
+ createdTr.setMeta('origin', trackChangesPluginKey);
1430
1438
  return setAction(createdTr, TrackChangesAction.refreshChanges, true);
1431
1439
  }
1432
1440
  return null;
package/dist/index.js CHANGED
@@ -335,8 +335,9 @@ function deleteNode(node, pos, tr) {
335
335
  var _a;
336
336
  const startPos = tr.doc.resolve(pos + 1);
337
337
  const range = startPos.blockRange(tr.doc.resolve(startPos.pos - 2 + node.nodeSize));
338
- const targetDepth = range ? Number(prosemirrorTransform.liftTarget(range)) : NaN;
339
- if (range && !Number.isNaN(targetDepth)) {
338
+ const targetDepth = range && prosemirrorTransform.liftTarget(range);
339
+ // Check with typeof since with old prosemirror-transform targetDepth is undefined
340
+ if (range && typeof targetDepth === 'number') {
340
341
  return tr.lift(range, targetDepth);
341
342
  }
342
343
  const resPos = tr.doc.resolve(pos);
@@ -509,9 +510,11 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new prose
509
510
  if (change.attrs.status === exports.CHANGE_STATUS.pending) {
510
511
  return;
511
512
  }
512
- const from = deleteMap.map(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = ChangeSet.shouldNotDelete(change);
513
+ // Map change.from and skip those which dont need to be applied
514
+ // or were already deleted by an applied block delete
515
+ const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || ChangeSet.shouldNotDelete(change);
513
516
  if (!node) {
514
- log.warn('no node found to update for change', change);
517
+ !deleted && log.warn('no node found to update for change', change);
515
518
  return;
516
519
  }
517
520
  if (ChangeSet.isTextChange(change) && noChangeNeeded) {
@@ -935,6 +938,7 @@ function deleteAndMergeSplitNodes(from, to, gap, startDoc, newTr, schema, trackA
935
938
  // But from what I remember what it safeguards against is, when you've already deleted a node
936
939
  // say an inserted blockquote that had all its children deleted, nodesBetween still iterates over those
937
940
  // nodes and therefore we have to make this check to ensure they still exist in the doc.
941
+ //
938
942
  if (nodeEnd > offsetFrom && !nodeWasDeleted && !wasWithinGap) {
939
943
  // |<p>asdf</p>| -> node deleted completely
940
944
  const nodeCompletelyDeleted = offsetPos >= offsetFrom && nodeEnd <= offsetTo;
@@ -1215,11 +1219,9 @@ function trackReplaceStep(step, oldState, newTr, attrs) {
1215
1219
  * This skips the direct dependency to prosemirror-state where multiple versions might cause conflicts
1216
1220
  * as the created instances might belong to different prosemirror-state import than one used in the editor.
1217
1221
  * @param sel
1218
- * @param doc
1219
- * @param from
1220
1222
  * @returns
1221
1223
  */
1222
- const getSelectionStaticCreate = (sel, doc, from) => Object.getPrototypeOf(sel).constructor.create(doc, from);
1224
+ const getSelectionStaticConstructor = (sel) => Object.getPrototypeOf(sel).constructor;
1223
1225
  /**
1224
1226
  * Inverts transactions to wrap their contents/operations with track data instead
1225
1227
  *
@@ -1263,7 +1265,12 @@ function trackTransaction(tr, oldState, newTr, userID) {
1263
1265
  else if (step instanceof prosemirrorTransform.ReplaceStep) {
1264
1266
  const selectionPos = trackReplaceStep(step, oldState, newTr, emptyAttrs);
1265
1267
  if (!wasNodeSelection) {
1266
- newTr.setSelection(getSelectionStaticCreate(tr.selection, newTr.doc, selectionPos));
1268
+ const sel = getSelectionStaticConstructor(tr.selection);
1269
+ // Use Selection.near to fix selections that point to a block node instead of inline content
1270
+ // eg when inserting a complete new paragraph. -1 finds the first valid position moving backwards
1271
+ // inside the content
1272
+ const near = sel.near(newTr.doc.resolve(selectionPos), -1);
1273
+ newTr.setSelection(near);
1267
1274
  }
1268
1275
  }
1269
1276
  else if (step instanceof prosemirrorTransform.ReplaceAroundStep) {
@@ -1274,13 +1281,12 @@ function trackTransaction(tr, oldState, newTr, userID) {
1274
1281
  // TODO: here we could check whether adjacent inserts & deletes cancel each other out.
1275
1282
  // However, this should not be done by diffing and only matching node or char by char instead since
1276
1283
  // it's A easier and B more intuitive to user.
1277
- const { meta } = tr;
1278
- // This is quite non-optimal in some sense but to ensure no information is lost
1279
- // we have to re-add all the old meta keys, such as inputType or uiEvent.
1280
- // This should prevent bugs incase other plugins/widgets rely upon them existing (and they
1281
- // are not able to process the transactions before track-changes).
1282
- // TODO: will this cause race-condition if a meta causes another appendTransaction to fire
1283
- Object.keys(meta).forEach((key) => newTr.setMeta(key, tr.getMeta(key)));
1284
+ // The old meta keys are not copied to the new transaction since this will cause race-conditions
1285
+ // when a single meta-field is expected to having been processed / removed. Generic input meta keys,
1286
+ // inputType and uiEvent, are re-added since some plugins might depend on them and process the transaction
1287
+ // after track-changes plugin.
1288
+ tr.getMeta('inputType') && newTr.setMeta('inputType', tr.getMeta('inputType'));
1289
+ tr.getMeta('uiEvent') && newTr.setMeta('uiEvent', tr.getMeta('uiEvent'));
1284
1290
  });
1285
1291
  // This is kinda hacky solution at the moment to maintain NodeSelections over transactions
1286
1292
  // These are required by at least cross-references that need it to activate the selector pop-up
@@ -1288,7 +1294,8 @@ function trackTransaction(tr, oldState, newTr, userID) {
1288
1294
  const mappedPos = newTr.mapping.map(tr.selection.from);
1289
1295
  const resPos = newTr.doc.resolve(mappedPos);
1290
1296
  const nodePos = mappedPos - (((_a = resPos.nodeBefore) === null || _a === void 0 ? void 0 : _a.nodeSize) || 0);
1291
- newTr.setSelection(getSelectionStaticCreate(tr.selection, newTr.doc, nodePos));
1297
+ const sel = getSelectionStaticConstructor(tr.selection);
1298
+ newTr.setSelection(sel.create(newTr.doc, nodePos));
1292
1299
  }
1293
1300
  log.info('NEW transaction', newTr);
1294
1301
  return newTr;
@@ -1338,7 +1345,8 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1338
1345
  key: trackChangesPluginKey,
1339
1346
  props: {
1340
1347
  editable(state) {
1341
- return this.getState(state).status !== exports.TrackChangesStatus.viewSnapshots;
1348
+ var _a;
1349
+ return ((_a = trackChangesPluginKey.getState(state)) === null || _a === void 0 ? void 0 : _a.status) !== exports.TrackChangesStatus.viewSnapshots;
1342
1350
  },
1343
1351
  },
1344
1352
  state: {
@@ -1408,7 +1416,6 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1408
1416
  (wasAppended && getAction(wasAppended, exports.TrackChangesAction.skipTrack));
1409
1417
  if (tr.docChanged && !skipMetaUsed && !skipTrackUsed && !tr.getMeta('history$')) {
1410
1418
  createdTr = trackTransaction(tr, oldState, createdTr, userID);
1411
- createdTr.setMeta('origin', trackChangesPluginKey);
1412
1419
  infiniteLoopCounter.iters += 1;
1413
1420
  }
1414
1421
  docChanged = docChanged || tr.docChanged;
@@ -1435,6 +1442,7 @@ const trackChangesPlugin = (opts = { userID: 'anonymous:Anonymous' }) => {
1435
1442
  log.warn('had to fix inconsistent changes in', createdTr);
1436
1443
  }
1437
1444
  if (docChanged || createdTr.docChanged || changed) {
1445
+ createdTr.setMeta('origin', trackChangesPluginKey);
1438
1446
  return setAction(createdTr, exports.TrackChangesAction.refreshChanges, true);
1439
1447
  }
1440
1448
  return null;
package/dist/plugin.d.ts CHANGED
@@ -15,11 +15,11 @@
15
15
  */
16
16
  import { Plugin, PluginKey } from 'prosemirror-state';
17
17
  import { TrackChangesOptions, TrackChangesState } from './types/track';
18
- export declare const trackChangesPluginKey: PluginKey<TrackChangesState, any>;
18
+ export declare const trackChangesPluginKey: PluginKey<TrackChangesState>;
19
19
  /**
20
20
  * The ProseMirror plugin needed to enable track-changes.
21
21
  *
22
22
  * Accepts an empty options object as an argument but note that this uses 'anonymous:Anonymous' as the default userID.
23
23
  * @param opts
24
24
  */
25
- export declare const trackChangesPlugin: (opts?: TrackChangesOptions) => Plugin<TrackChangesState, any>;
25
+ export declare const trackChangesPlugin: (opts?: TrackChangesOptions) => Plugin<TrackChangesState>;
@@ -24,4 +24,4 @@ import { Transaction } from 'prosemirror-state';
24
24
  * @param tr
25
25
  * @returns
26
26
  */
27
- export declare function deleteNode(node: PMNode, pos: number, tr: Transaction): Transaction<any>;
27
+ export declare function deleteNode(node: PMNode, pos: number, tr: Transaction): Transaction;
@@ -49,5 +49,5 @@ export declare function deleteAndMergeSplitNodes(from: number, to: number, gap:
49
49
  } | undefined, startDoc: PMNode, newTr: Transaction, schema: Schema, trackAttrs: NewEmptyAttrs, insertSlice: ExposedSlice): {
50
50
  deleteMap: Mapping;
51
51
  mergedInsertPos: undefined;
52
- newSliceContent: Fragment<any>;
52
+ newSliceContent: Fragment;
53
53
  };
@@ -15,4 +15,4 @@
15
15
  */
16
16
  import { Fragment, Schema } from 'prosemirror-model';
17
17
  import { NewInsertAttrs } from '../../types/track';
18
- export declare function setFragmentAsInserted(inserted: Fragment, insertAttrs: NewInsertAttrs, schema: Schema): Fragment<any>;
18
+ export declare function setFragmentAsInserted(inserted: Fragment, insertAttrs: NewInsertAttrs, schema: Schema): Fragment;
@@ -14,4 +14,4 @@ import type { EditorState, Transaction } from 'prosemirror-state';
14
14
  * @param userID User id
15
15
  * @returns newTr that inverts the initial tr and applies track attributes/marks
16
16
  */
17
- export declare function trackTransaction(tr: Transaction, oldState: EditorState, newTr: Transaction, userID: string): Transaction<any>;
17
+ export declare function trackTransaction(tr: Transaction, oldState: EditorState, newTr: Transaction, userID: string): Transaction;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manuscripts/track-changes-plugin",
3
- "version": "0.0.2",
3
+ "version": "0.1.1",
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,38 +21,33 @@
21
21
  "access": "public"
22
22
  },
23
23
  "devDependencies": {
24
+ "@manuscripts/manuscript-transform": "^0.49.1",
24
25
  "@rollup/plugin-commonjs": "^22.0.0",
25
26
  "@types/debug": "^4.1.7",
26
27
  "@types/jest": "27.5.1",
27
28
  "@types/node": "^17.0.35",
28
- "@types/prosemirror-commands": "^1.0.4",
29
- "@types/prosemirror-model": "^1.16.2",
30
- "@types/prosemirror-schema-list": "^1.0.3",
31
- "@types/prosemirror-state": "^1.3.0",
32
- "@types/prosemirror-transform": "^1.4.1",
33
- "@types/prosemirror-view": "^1.23.3",
34
- "jest": "28.1.0",
35
- "jest-environment-jsdom": "28.1.0",
29
+ "jest": "27.5.1",
30
+ "jest-environment-jsdom": "27.5.1",
36
31
  "jsdom": "^19.0.0",
37
- "prosemirror-commands": "^1.2.2",
38
- "prosemirror-example-setup": "^1.1.2",
39
- "prosemirror-keymap": "^1.1.5",
40
- "prosemirror-model": "^1.16.1",
41
- "prosemirror-schema-list": "^1.1.6",
42
- "prosemirror-state": "^1.3.4",
43
- "prosemirror-transform": "^1.4.2",
44
- "prosemirror-view": "^1.23.13",
32
+ "prosemirror-commands": "^1.3.0",
33
+ "prosemirror-example-setup": "^1.2.1",
34
+ "prosemirror-keymap": "^1.2.0",
35
+ "prosemirror-model": "^1.18.1",
36
+ "prosemirror-schema-list": "^1.2.0",
37
+ "prosemirror-state": "^1.4.1",
38
+ "prosemirror-transform": "^1.6.0",
39
+ "prosemirror-view": "^1.26.2",
45
40
  "rollup": "^2.74.0",
46
41
  "rollup-plugin-typescript2": "^0.31.2",
47
- "ts-jest": "28.0.2",
42
+ "ts-jest": "27.1.4",
48
43
  "tslib": "^2.4.0",
49
44
  "typescript": "^4.6.4"
50
45
  },
51
46
  "peerDependencies": {
52
- "prosemirror-model": "^1.16.1",
53
- "prosemirror-state": "^1.3.4",
54
- "prosemirror-transform": "^1.3.3",
55
- "prosemirror-view": "^1.23.6"
47
+ "prosemirror-model": ">=1.14.0",
48
+ "prosemirror-state": ">=1.3.0",
49
+ "prosemirror-transform": ">=1.3.0",
50
+ "prosemirror-view": ">=1.18.0"
56
51
  },
57
52
  "dependencies": {
58
53
  "debug": "^4.3.4"
@@ -62,6 +57,7 @@
62
57
  "watch": "rollup -cw",
63
58
  "test": "jest --runInBand",
64
59
  "format": "prettier --write \"*.+(js|json|yml|yaml|ts|md|graphql|mdx)\" src/ test/",
60
+ "typecheck": "tsc --project tsconfig.test.json --noEmit",
65
61
  "lint": "eslint --cache --ext .js,.ts, ./src ./test",
66
62
  "lint:fix": "eslint --fix --ext .js,.ts, ./src ./test"
67
63
  }