i’m currently looking for a method to traverse my entire doc and update the nodes contained within. my specific use case is to randomize the order of the children of each node contained within the doc (doc included). i have a working implementation, but i’m not sure if this is really the “prosemirror way” to solve such a problem. my solution is a custom version of the descendants and nodesBetween functions, which also allows the callback to return a new node instance, instead of just the usual boolean
function nodesBetweenUpdate(this: Node, from: number, to: number,
f: (node: Node, start: number, parent: Node | null, index: number) => boolean | Node | void,
nodeStart = 0,
parent?: Node,
) {
for (let i = 0, pos = 0; pos < to; i++) {
let child = this.content.content[i], end = pos + child.nodeSize
let res = f(child, nodeStart + pos, parent || null, i);
if (end > from && res !== false && child.content.size) {
child = (res instanceof Node ? res : child);
let start = pos + 1
child.nodesBetweenUpdate(Math.max(0, from - start),
Math.min(child.content.size, to - start),
f, nodeStart + start)
}
pos = end
}
}
function descendantsUpdate(this: Node, f: (node: Node, pos: number, parent: Node | null, index: number) => void | boolean, root = true) {
if (root) {
let res = f(this, 0, null, 0);
this.content = (res instanceof Node ? res : this).content
}
this.nodesBetweenUpdate(0, this.content.size, f)
}
so by extending the Node prototype you can call like:
// shuffle all nodes
const { tr } = VIEW.state
tr.doc.descendantsUpdate((node, pos, parent) => {
if (node.childCount <= 1) {
return false
}
let indexMap: number[] = []
node.forEach((_, __, i) => indexMap.push(i))
shuffle(indexMap)
let content = node.content
indexMap.forEach((i, ii) => { content = content.replaceChild(ii, node.content.child(i)) })
const newNode = node.copy(content)
tr.replaceWith(pos, pos + node.content.size, newNode)
return newNode
})
VIEW.dispatch(tr)
if anyone is interested, heres my full repo with an example setup to test with