@manuscripts/track-changes-plugin 0.0.1 → 0.0.4

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
@@ -1,20 +1,58 @@
1
- # @manuscripts/track-changes-plugin
1
+ # [@manuscripts/track-changes-plugin](https://github.com/Atypon-OpenSource/manuscripts-quarterback/tree/main/quarterback-packages/track-changes-plugin)
2
2
 
3
- This is a ProseMirror plugin that tracks inserts/deletes to nodes and text.
3
+ ProseMirror plugin to track inserts/deletes to nodes and text.
4
4
 
5
- It uses node attributes inside `dataTracked` object to persist changes for both block and inline nodes and `tracked_insert` & `tracked_delete` marks for text. An example implementation can be found inside the `examples-packages/schema` package. In addition to `dataTracked`, `tracked_insert` and `tracked_delete` code_blocks marks were altered to include track marks: `marks: 'tracked_insert tracked_delete'`. If you have multiple versions of prosemirror packages, ensure that track-changes' dependencies `prosemirror-model` and `prosemirror-transform` are aliased/deduped to same instance. `prosemirror-state` and `prosemirror-view` are only used at type level.
5
+ If you have multiple versions of prosemirror packages, ensure that track-changes' dependencies `prosemirror-model` and `prosemirror-transform` are aliased/deduped to same instance. `prosemirror-state` and `prosemirror-view` are only used at type level. [Example](https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/examples-packages/client/vite.config.js).
6
6
 
7
- This library replaces a previous implementation based on commits as in https://prosemirror.net/examples/track/ which proved unreliable due to non-idempotent nature of transactions when rebased as well as the inability to transition to Yjs syncing. `prosemirror-changeset` was also trialed but it had similar problems and was deemed hard to extend to include tracking formatting changes.
7
+ [More detailed overview](https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/quarterback-packages/track-changes-plugin/OVERVIEW.md)
8
8
 
9
- On a more detailed level, this plugin checks every transaction in an `appendTransaction` hook for any modifications to the document (`tr.docChanged`). If they exist, it prevents that transaction from happening by inverting it and reapplying it with deletions and insertions wrapped in track attributes/marks. It can be bybassed by using `TrackActions.skipTrack` action or by setting `skipTrsWithMetas?: (PluginKey | string)[]` option to skip all transactions based on their meta fields, such as `ySyncPluginKey`. This prevents race conditions where appendTransactions keep firing between 2 or more plugins. `prosemirror-history` transactions are skipped by default.
9
+ ## How to use
10
10
 
11
- On every transaction prevented, ChangeSet is generated which contains all the changes found from the document. `tr.setMeta('origin', trackChangesPluginKey)` is applied to id the transaction. Whole document is currently being iterated which may be somewhat inefficient on larger docs. The changes are stored as a flat list yet due to the hierarchy of the changes, a `changeTree` property is generated which wraps all changes within a node change as its children. This is especially helpful when inserting/deleting nodes that require multiple children that are irrelevant to the user. Currently, they are still shown in the example UI but future enhancements will probably hide them. Granular controls are however at times needed when some of the changes can be considered separate or non-contiguous.
11
+ First install the plugin: `npm i @manuscripts/track-changes-plugin`
12
12
 
13
- Yjs collaboration works without further integration with this attribute & mark based approach. However, due to missing feature-parity with Yjs documents and ProseMirror documents it currently does not behave consistently. Notably, Yjs snapshots do not show node attributes which makes using them for viewing snapshots insufficient. Yjs nodes can't also contain marks but this was circumvented by using node attributes for inline nodes too.
13
+ Then add the plugin to ProseMirror plugins:
14
14
 
15
- The plugin also includes logging using `debug` library that can be toggled with either passing `debug?: boolean` option or executing `enableDebug(enabled: boolean)`.
15
+ ```ts
16
+ import { EditorState } from 'prosemirror-state'
17
+ import { EditorView } from 'prosemirror-view'
18
+ import { exampleSetup } from 'prosemirror-example-setup'
19
+ import { trackChangesPlugin } from '@manuscripts/track-changes-plugin'
20
+
21
+ import { schema } from './schema'
22
+
23
+ const plugins = exampleSetup({ schema }).concat(trackChangesPlugin({
24
+ debug: true
25
+ }))
26
+ const state = EditorState.create({
27
+ schema,
28
+ plugins,
29
+ })
30
+ const view = new EditorView(document.querySelector('#editor') as HTMLElement, {
31
+ state,
32
+ })
33
+ ```
34
+
35
+ where `schema` is https://github.com/Atypon-OpenSource/manuscripts-quarterback/blob/main/quarterback-packages/track-changes-plugin/test/utils/schema.ts
36
+
37
+ Enable or disable the plugin with:
38
+
39
+ ```ts
40
+ import { trackCommands, TrackChangesStatus } from '@manuscripts/track-changes-plugin'
16
41
 
17
- CKEditor and Fiduswriter served as its inspiration but everything was written from scratch. Also, this library is open-sourced under the Apache 2 license.
42
+ // toggle
43
+ trackCommands.setTrackingStatus())(view.state, view.dispatch, view)
44
+
45
+ // enable
46
+ trackCommands.setTrackingStatus(TrackChangesStatus.enabled))(view.state, view.dispatch, view)
47
+
48
+ // disable
49
+ trackCommands.setTrackingStatus(TrackChangesStatus.disabled))(view.state, view.dispatch, view)
50
+
51
+ // sets editor's 'editable' prop to false, making it ready-only
52
+ trackCommands.setTrackingStatus(TrackChangesStatus.viewSnapshots))(view.state, view.dispatch, view)
53
+ ```
54
+
55
+ See an example app at https://github.com/Atypon-OpenSource/manuscripts-quarterback/tree/main/examples-packages/client
18
56
 
19
57
  ## API
20
58
 
@@ -133,36 +171,3 @@ export function setAction<K extends keyof TrackChangesActionParams>(
133
171
  ### Types
134
172
 
135
173
  Can be found in `./src/types` and `./src/ChangeSet.ts`
136
-
137
- ## Feature summary
138
-
139
- - tracks block, inline, atom node inserts & deletes as `dataTracked` attribute objects
140
- - tracks text insert & delete as `tracked_insert` and `tracked_delete` marks
141
- - joins track marks based on `userID`, `operation` and `status`, uses the oldest `createdAt` value as timestamp
142
- - allows deletes of block nodes & text if operation is `inserted`
143
- - does not diff operations next to each other eg `(ins aasdf)(del asdf)` is not reduced to `ins a`
144
- - does not track block node attribute updates
145
- - does not track mark inserts & deletes
146
- - does not track ReplaceAroundSteps
147
- - has probably bugs regarding the edge cases around copy-pasting complicated slices
148
-
149
- ## Roadmap
150
-
151
- - track block node attribute updates, they currently go undetected
152
- - test copy-pasting works (slices with varying open endedness)
153
- - test for race conditions
154
- - refactor unused code, add better comments
155
- - more thorough tests
156
- - track ReplaceAroundSteps
157
- - track formatting changes, basically handle AddMarkStep and RemoveMarkSteps
158
-
159
- ## Related reading
160
-
161
- - https://ckeditor.com/docs/ckeditor5/latest/features/collaboration/track-changes/track-changes.html
162
- - https://ckeditor.com/blog/ckeditor-5-comparing-revision-history-with-track-changes/
163
- - https://github.com/fiduswriter/fiduswriter
164
- - https://www.ncbi.nlm.nih.gov/books/NBK159965/
165
- - https://teemukoivisto.github.io/prosemirror-track-changes-example/
166
- - https://demos.yjs.dev/prosemirror-versions/prosemirror-versions.html
167
- - https://slab.com/blog/announcing-delta-for-elixir/
168
- - https://www.inkandswitch.com/peritext/
package/dist/index.es.js CHANGED
@@ -501,7 +501,9 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new Mappi
501
501
  if (change.attrs.status === CHANGE_STATUS.pending) {
502
502
  return;
503
503
  }
504
- const from = deleteMap.map(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = ChangeSet.shouldNotDelete(change);
504
+ // Map change.from and skip those which dont need to be applied
505
+ // or were already deleted by an applied block delete
506
+ const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || ChangeSet.shouldNotDelete(change);
505
507
  if (!node) {
506
508
  log.warn('no node found to update for change', change);
507
509
  return;
@@ -1263,16 +1265,10 @@ function trackTransaction(tr, oldState, newTr, userID) {
1263
1265
  // } else if (step instanceof AddMarkStep) {
1264
1266
  // } else if (step instanceof RemoveMarkStep) {
1265
1267
  }
1266
- // TODO: here we could check whether adjacent inserts & deletes cancel each other out.
1267
- // However, this should not be done by diffing and only matching node or char by char instead since
1268
- // 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)));
1268
+ // The old meta keys are not copied to the new transaction since this will cause race-conditions
1269
+ // when a single meta-field is thought to be processed. MAYBE only the generic meta keys, such as
1270
+ // inputType or uiEvent, could be copied over but it remains to be seen if it's necessary.
1271
+ // Object.keys(meta).forEach((key) => newTr.setMeta(key, tr.getMeta(key)))
1276
1272
  });
1277
1273
  // This is kinda hacky solution at the moment to maintain NodeSelections over transactions
1278
1274
  // These are required by at least cross-references that need it to activate the selector pop-up
package/dist/index.js CHANGED
@@ -509,7 +509,9 @@ function applyAcceptedRejectedChanges(tr, schema, changes, deleteMap = new prose
509
509
  if (change.attrs.status === exports.CHANGE_STATUS.pending) {
510
510
  return;
511
511
  }
512
- const from = deleteMap.map(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = ChangeSet.shouldNotDelete(change);
512
+ // Map change.from and skip those which dont need to be applied
513
+ // or were already deleted by an applied block delete
514
+ const { pos: from, deleted } = deleteMap.mapResult(change.from), node = tr.doc.nodeAt(from), noChangeNeeded = deleted || ChangeSet.shouldNotDelete(change);
513
515
  if (!node) {
514
516
  log.warn('no node found to update for change', change);
515
517
  return;
@@ -1271,16 +1273,10 @@ function trackTransaction(tr, oldState, newTr, userID) {
1271
1273
  // } else if (step instanceof AddMarkStep) {
1272
1274
  // } else if (step instanceof RemoveMarkStep) {
1273
1275
  }
1274
- // TODO: here we could check whether adjacent inserts & deletes cancel each other out.
1275
- // However, this should not be done by diffing and only matching node or char by char instead since
1276
- // 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)));
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 thought to be processed. MAYBE only the generic meta keys, such as
1278
+ // inputType or uiEvent, could be copied over but it remains to be seen if it's necessary.
1279
+ // Object.keys(meta).forEach((key) => newTr.setMeta(key, tr.getMeta(key)))
1284
1280
  });
1285
1281
  // This is kinda hacky solution at the moment to maintain NodeSelections over transactions
1286
1282
  // These are required by at least cross-references that need it to activate the selector pop-up
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@manuscripts/track-changes-plugin",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
4
  "author": "Atypon Systems LLC",
5
5
  "license": "Apache-2.0",
6
- "homepage": "https://github.com/Atypon-OpenSource/manuscripts-quarterback",
6
+ "homepage": "https://github.com/Atypon-OpenSource/manuscripts-quarterback/tree/main/quarterback-packages/track-changes-plugin",
7
7
  "main": "dist/index.js",
8
8
  "module": "dist/index.es.js",
9
9
  "type": "module",
@@ -49,10 +49,10 @@
49
49
  "typescript": "^4.6.4"
50
50
  },
51
51
  "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"
52
+ "prosemirror-model": ">=1.14.0",
53
+ "prosemirror-state": ">=1.3.0",
54
+ "prosemirror-transform": ">=1.3.0",
55
+ "prosemirror-view": ">=1.18.0"
56
56
  },
57
57
  "dependencies": {
58
58
  "debug": "^4.3.4"