Combining transactions in the History plugin?

I have a plugin that appends transactions closely related to the content e.g. marking some text conditionally based on what was entered.

In this case, I want to combine that transaction with the one being appended in the history, such that when the user does an undo, the mark is removed and the text. Right now there’s no way I can tell the history plugin that I want to merge these two and it leads to janky undo behavior.

One possible approach is to allow a transaction to have a flag that makes it automatically pass the isAdjanceToLastStep function (https://github.com/ProseMirror/prosemirror-history/blob/e9e5caf70506f742258d46dfb0b86ac4c1a7721d/src/history.js#L283).

Is your plugin using https://prosemirror.net/docs/ref/#state.PluginSpec.appendTransaction ? I would have thought that would yield a single undo. Your use case may be better dealt with via an input rule which may yield the desired undo behavior.

Anyway, to actually answer your question, you may be able to mess with the undo stack and remove one of them?

Are you using the current version? Because there is code in the library doing just that (using the "appendedTransaction" meta property).

Ohh you’re right, there totally is. I think I’m a few versions out of date and maybe this code was added relatively recent.

To extend this point, while appendedTransactions don’t cause there own undo step, they still separate every initial transaction into a separate history event. So undoing deletes one character at a time. It sill may be useful to flag certain edits as irrelevent when it comes to comparing the proximity of edits for the following undo event. Is this something you’d consider a PR for

That’s not a trivial fix, because ideally you’d want to use the old prevMap value to compare against, but since the appended transaction may have made changes, that no longer points into the current document, and there’s currently no functionality to map step maps in the library.

Is that the logic for isAdjacentToLastStep? Would it be difficult to add the ability to map a StepMap?

Not terribly difficult. Since this map is being used in a limited way, only to check for adjacency, it might even make sense to store a simplified representation that’s easier to use/map instead.

I’ll try and have a look at this this afternoon, it’s innovation week at work so I’m willing to expand that definition slightly and have a poke around with that … not sure how far I’ll get but it’ll be worth understanding the history plugin a bit more as I always seem to butt heads with it!

I won’t have chance to look at this properly but it looks like the general plan would be to store an appendMap on the HistoryState that is a map that describes the transactions appended by appendTransaction. Then, when checking adjacency, map the current tr back through the inverse of the appendMap and check on that?

If that’s right then it looks like mapping a StepMap through another StepMap would largely involve appending a steps ranges to another StepMap with some optimisations accounting for overlaps (although ranges aren’t part of the public API of a StepMap)? It seems like it would be easier if it kept the whole mapping in state but this isn’t serialisable …

Does that sound about right or am I missing something?

I think proactively mapping the stored adjacency info forward is nicer than storing transactions.

I hadn’t planned to store the transactions themselves, just a mapping that represented them. But mapping proactively still seems like the better solution.