Prosemirror-changeset for tracking document changes including deletions

Hey ProseMirror users,

I’ve been working on a plugin to handle track changes. Unfortunately, the example in the official docs is not suitable for my needs as I couldn’t see a way to use it to track and render deletions e.g. with a strikethrough for deleted content .

I did find the chainset helper module (https://github.com/prosemirror/prosemirror-changeset). I believe this can be used to turn a sequence of document changes into sets of insertions and deletions, and then the insertion/deletion sets used to render changes with in-line decorations. Which is pretty perfect for my needs however I’m really struggling to get this working and couldn’t find many examples.

What I am trying to do (at least at first) is keep track of changes with a plugin state field and then add changes with addSteps as new transactions occur. Is that a sensible approach?

export class TrackChangesConfig extends Common.Config {
  constructor(options) {
    super();
    const trackChangesPluginKey = new PluginKey('trackchanges');

    this.trackChangesPlugin = new Plugin({
      key: trackChangesPluginKey,
      props: {
        decorations(state) {
          // TODO: Add decoration for additions and deletions.
        }
      },
      state: {
        init(config, instance) {
          return {
            changeset: ChangeSet.create(instance)
          }
        },
        apply: function (transaction, currentValue, oldState, newState) {
          let changes = currentValue.changeset
          changes.addSteps(transaction.doc, transaction.mapping.maps)
          currentValue.changeset = changes
          return currentValue
        }
      }
    })
  }

But I’m not seeing the changes stored in the changeset. Wondering if I am misunderstanding how to use prosemirror-changeset? I had a look for some examples but couldn’t find anyone using the changeset helper module like I am. Do any examples exist?

image

Any help or feedback much appreciated. Thank you

You’re making two different mistakes related to immutable values—addSteps returns a new change set, which you are just ignoring, and you shouldn’t mutate a state field value (currentValue) but create a new one instead.

See also this thread.

1 Like

Thank so much! This is making a lot more sense now.

I have my plugin rendering additions with decorations using prosemirror-changeset but one thing I don’t quite follow is the handling of deletions might work.

Based on the other thread I think this would be the approach.

  1. Include deleted slices as metadata when generating a new changeset using addSteps. So something like:
const newChangeSet = currentValue.addSteps(transaction.doc, transaction.mapping.maps, transaction.steps)
  1. Render slice content as widget decoration but drop leading/trailing tokens
change.deleted.forEach((span) => {
    const serializer = DOMSerializer.fromSchema(state.schema);
    const element = serializer.serializeFragment(span.data.slice.content);
    Decoration.widget(change.fromA, element)
})

Does that sound about right?

Thank you