Approaches for finding the position of nodes

I’d like to initiate a thread for discussing the best way(s) of finding the position of a node.

For empty selections what I’ve been doing is iterating the children of a parent node (or the document) and using node.eq() to determine if the current children is the node I’m looking for. Something like this:

function getNodePositions (node, parentNode) {
    let start, end;
    parentNode.forEach((childNode, pos) => {
        if (childNode.eq(node)) {
            start = pos;
            end = pos + node.nodeSize;
        }
    });

    return {start, end};
}

Usually the parent node is the document, or I just find the parent at the depth I’m interested in.

This seems to work so far, but then I remember that @marijn has insisted frequently that nodes are value types and could be duplicated in the document. I wonder if using node.eq() is the right approach?

For regular selections I simply use state.doc.nodesBetween() which doesn’t have the equality issue I mentioned earlier.

I also found this thread which discusses using HTML attributes to tag DOM elements and then using posFromDOM but when searching the docs the method doesn’t seem to exist anymore.

So how do you do it?

The recommended approach is to not use nodes like this. If you want to remember something about something in the document, remember its position. If you need to track that across changes, map the position through the changes.

1 Like

Thanks for your answer Marijn.

This makes sense, but the fundamental problem I face is finding the position of something in the first place to be able to use it for transforms or track it across changes.

For example, say I want to delete $cursor.parent with tr.deleteRange or tr.delete. I can find the start position for the transform by using $cursor.parentOffset but would it be ok to use node.nodeSize to find the end point?

but would it be ok to use node.nodeSize to find the end point?

Absolutely.

1 Like

After numerous attempts I think I’m finally getting the hang of this.

For example, to get the start/end positions of the previous node in the document:

// first get the index of the current node at the root level
const parentIndex = $cursor.index(0);

// iterate the doc to find the offset of the previous index
let startPos, endPos;

doc.forEach((node, offset, index) => {
    if (index === parentIndex - 1) {
        startPos = offset;
        endPos = offset + node.nodeSize;
    }
});

Or to get the positions of the last child of the previous block:

doc.forEach((node, offset, index) => {
    if (index === parentIndex - 1) {
        node.forEach((childNode, childOffset, childIndex) => {
            if (childIndex === node.childCount - 1) {
                startPos = offset + childOffset;
                endPos = startPos + node.nodeSize;
            }
        })
    }
});

I tried many attempts which all ended up using node.eq(anotherNode) to determine if I had “found” the node I was interested in, but as @marijn has stated this is a bad idea. When iterating it’s possible you could get a different node if these are identical.

I also tried resolving positions inside a node (other than the document) but this was useless. All transforms require absolute global positions and I couldn’t find a way to “translate” a local position to a global one.

You don’t need forEach to get at a child at a given index, you can just use the child or maybeChild methods.

But then how do you get the offset/pos?

AFAIK the only way to get the offset/pos is by using forEach(), nodesBetween(), or descendants().

1 Like

Ah, that’s a good point. There’s a posAtIndex helper on resolved positions, but that only helps if you have one of those going through the node.

2 Likes