A proposed way to visualize a changeset

Is there any proposed way to visualize a changeset? So I would like to visualize a changeset between two versions by having content of both displayed and then additions colored green and removals with red. I am not sure how to arrange this for ProseMirror to render in this way? So both versions at once?

This example does not use changesets.

I’m not aware of there being open-source code to do that. The basic approach would be to insert widget decorations for deletions, containing a rendered form of the deleted slices (with some heuristics to drop leading/trailing open/close tokens to avoid making a mess), and to add range decorations over inserted ranges, coloring them somehow.

1 Like

Thanks. It makes sense.

You mean inline decorations?

Yeah, indeed (we’re calling them range decorations in the new CodeMirror codebase, and I got confused)

1 Like

Thanks! I think I am making progress.

But I have trouble converting a transaction to changeset. It is always computing the whole document as inserted. I have a transaction with all steps in it and I would like to compute changeset for the last diffVersionsCount steps:

        const transactionEndIndex = transaction.docs.length - diffVersionsCount;
        if (transactionEndIndex < transaction.docs.length) {
          changeset = ChangeSet.create(transaction.docs[transactionEndIndex]);
          changeset = changeset.addSteps(transaction.doc, transaction.mapping.slice(transactionEndIndex).maps);
        else {
          changeset = ChangeSet.create(transaction.doc);

Would it be better if I call addSteps with every document in the transaction’s diffVersionsCount range instead of just the last one?

This does not work. transaction.mapping.slice(transactionEndIndex).maps is the same as transaction.mapping.maps. This is a surprise. transaction.mapping.maps.slice(transactionEndIndex) seems to work better.

I think I made it (open source). See here. A pretty simple plugin.

I do not get this, or more specifically, I do not seem to be able to imagine what heuristics are necessary. I am also not sure how to even detect invalid open/close tokens. I cannot really imagine how can a diff have partial open/close tokens? Or you removed the whole block, or you removed its content. Can you really remove just one side of the block?

I also use:

const serializer = DOMSerializer.fromSchema(view.state.schema);
return serializer.serializeFragment(span.slice.content);

To render deletion widget content. But it seems there is a slight difference how this is rendered. In editor, <p></p> are always rendered as <p><br/></p> while in the widget it is just <p></p>. This then makes diff content looks differently from the original content. Why is that? How could I make it render it exactly the same? (Both editor and widget have content editable set to false for this “diff view mode”.)