Skip to content

Instantly share code, notes, and snippets.

Created April 4, 2021 07:55
Show Gist options
  • Save BrianHung/3b5f9b2fe97a353150324a19250a5257 to your computer and use it in GitHub Desktop.
Save BrianHung/3b5f9b2fe97a353150324a19250a5257 to your computer and use it in GitHub Desktop.
prosemirror changed nodes / part of document by transaction

The general idea is that you keep your decorations in a plugin state field, and have your decorations method just read it from there, rather than rebuild it on every transaction. The field’s apply method maps the old decoration set when there are any changes, and updates the regions touched by the transaction’s steps (which you can get by iterating over their step maps, as found in tr.mapping).

What you do is track the position of the element you are interested in, and on each transaction, map to its new position (possibly tracking both the start and end so that it’s easier to determine whether any of the step maps in the transaction touch it).

You don’t need to use filterTransaction – you can simply react to transactions as they are dispatched. To see the extent of the document ‘touched’ by a given transaction, you can call forEach on each step map in its mapping property, but doing this is kind of subtle as each step will have its positions expressed relative to the document as it was when that step was applied, and later steps might move those positions again.

It is probably preferable to iterate over the mapping of each step (step.getMap().forEach 17), and map those to the current document through any subsequent steps. You could then either just keep a minimum/maximum changed position, or track and merge individual ranges. And then finally iterate over the change range/ranges and send updates.

* Helper for iterating through the nodes in a document that changed compared
* to the given previous document. Useful for avoiding duplicate work on each transaction.
* Source:
import type { Node as PMNode } from "prosemirror-model"
export function changedDescendants(old: PMNode, cur: PMNode, offset: number, f: (node: PMNode, pos: number) => void) {
let oldSize = old.childCount, curSize = cur.childCount
outer: for (let i = 0, j = 0; i < curSize; i++) {
let child = cur.child(i)
for (let scan = j, e = Math.min(oldSize, i + 3); scan < e; scan++) {
if (old.child(scan) == child) {
j = scan + 1
offset += child.nodeSize
continue outer
f(child, offset)
if (j < oldSize && old.child(j).sameMarkup(child))
changedDescendants(old.child(j), child, offset + 1, f);
child.nodesBetween(0, child.content.size, f, offset + 1);
offset += child.nodeSize;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment