I’m tracking the relative position of decorations with yjs+prosemirror. Say so if somebody highlighted a paragraph in a document, then flagged it for review. I keep those positions in a YMap separate from the prosemirror fragment. In this discussion from a few years back: Y-prosemirror - Mapping a single relative position when doc changes - #3 by dmonad dmonad/Kevin mentions:
But it is important that you recompute the absolute position on every change (local and remote ones).
Does that mean on each prosemirror transaction, I should be updating the relative position to account for the changes in the transaction? If so what does that actually mean? Something like in the my prosemirror plugin apply() stepping through the transaction’s incremental changes and updating the positions somehow?
I mostly have this working, but there are cases where the positions get really out of place. Like selecting a large block of text before the position and then deleting it. But editing characters one by one, the positions seem to work fine. Why might that be?
The relative position is a stable value. You can safely store them and don’t have to worry about updating them. What you need to in reaction to every transaction is to recompute the absolute positions — the ones you use to create your decorations.
I have implemented this for a “comments” functionality and the way I do it is by reacting to changes inside of the apply function: if the change originated remotely, I recompute every decoration from the list of relative positions; if the change originates locally, I just map the decoration set over the transaction.
Ok, thanks for the explanation. I actually am doing the mapping I think. I just wasn’t sure what that meant exactly. So it looks like my bug is elsewhere. My decorations are positioning correctly after edits, but digging deeper it’s actually an overlay that’s not rendered by Prosemirror that’s getting positioned incorrectly.
my plugin has something like:
apply(tr, prevState, _oldState, newState) {
// I could add && ystate.binding here, not sure
// what the effect of not having it is
if (ystate && ystate.isChangeOrigin) {
//recalculate all decorations
return { decorations: getDecorations() };
}
return { decorations: prevState.decorations.map(tr.mapping, tr.doc) };
},
props: {
decorations(state) {
return myPluginKey.getState(state)?.decorations ?? DecorationSet.empty;
}
}
Yep, that was it! Since I was tracking the highlight positions in a separate YMap, I just needed to re-map those positions in response to local changes. I was missing that step in one code path. Thanks again!