Socalled "Tracked changes" using ProseMirror

“Tracked changes” can mean many things to many people. There is the kind of scientific understanding that one actually tracks every single change and presents to the user what has actually changed in the document over time.

Another thing is what users have become used to from popular word processors with that name: a mechanism which hinders the user in deleting any content (content is only added) and which adds marks throughout the document, documenting what has been added by whom and what the user attempted to remove or merge.

The title “tracked changes” seems technically incorrect for this second type, because accepting and reject any of these changes can be done independently and in a different order than what the changes were attempted to be done in initially, thereby creating entirely versions of the document.

Anyway, this second type is what many end users expect and here is a first version that tracks block creation/deletion, inline additions/deletions, format changes and block type changes (feature complete but likely not bugfree) built using ProseMirror to be released with the next version of Fidus Writer.

To try it out, go to https://staging.fiduswriter.org (id/pw: testtest/testtest). I’d be interested in feedback!

5 Likes

545545

This looks really interesting - great work! I see you’re having the same issue here as I am, using appendTransaction all of the undo / redo events get closed after every input so that undoing ends up being undo one character at a time. Have you found a fix for this?

@rich I rewrote/refactored the way tracked changes are applied. Instead of appending a transaction through a plugin, I now directly replace the original transaction with a modified one as described here. Advantages so far are:

  • about 20% fewer lines of code
  • basic insertions (the majority of operations) are now just one condensed ReplaceStep rather than a ReplaceStep and an AddMarkStep.
  • deletions are directly replaced with AddMarkSteps instead of first deleted, then readded and then marked
  • undo/redo events work as before tracked changes
  • one odd bug seems to either show less frequently or have gone away entirely.

Disadvantages are likely that:

  • Doing a replacement of the transaction can only be done in the editor’s dispatchTransaction method, so it’s not as well connected to the rest of ProseMirror and it probably only works because I only do this for one component.
  • I cannot really see how I should be copying the meta object from the original tr to the replacement one or whether this could lead to any issues. Currently I just do newTr.meta = tr.meta which likely is completely forbidden. The test seem to run though.
  • There are some oddities about the selection. I have to use the setSelection method if the new tr’s selection is not moving in the same direction is it is in the original tr. I seem to have covered most/all normal cases, but very likely there will be edge cases where the selection still behaves strangely.

PS: I have been wondering why assigning the meta like described does not lead to errors. It’s probably because I use the original tr in just about all cases where prosemirror-internal meta information is being used: trs without steps are not being replaced. Same goes for trs that come out of the collab or history module and a bunch of others for which I have set specific meta-data. The only ones that I am aware of that do have meta data that are being exchanged are those for whom I added meta data like 'inputType'=== 'deleteBackward' and similar that do not contain any position or step data.

2 Likes