How to Get a Position from an Index

I’m trying to implement a command to shift nodes up and down. Figured I’d just do a .delete() then an .insert(). I have the relative index that I want to shift by (-1, -2, 1, 2, etc.), and the index of the current node (from selection.$head.index(0)) but I can’t figure out how to go from currentIndex + relativeIndex to a position for the .insert() command.

I was looking for something like Node.maybeChild() that would return additional info about the node like its offset into the parent.

Am I on the right track?

Sounds like you either need to do some Position Mapping between the delete() and insert() or you need to complete the move in a single ReplaceAroundStep.

The ReplaceAroundStep is a bit more complex to set up properly, but it works cleanly as a single operation.

Position Mapping is also an important skill when creating a sequence of editing actions. I avoided partial-mapping of positions for a long time but after embracing it, it has made my util methods much more composable/reusable.

ReplaceAroundStep looks good, and I’m comfortable enough with mapping positions. My issue is I don’t have the position to map. I have the index.

ResolvedPos methods, such as before and after, might help. You may also need to retrieve the next sibling (using index/indexAfter) and add/subtract its size.

How to Get a Position from an Index ?

I don’t think you can. You will need a ResolvedPos which you can not get from an index. Nodes don’t have a positions. They have sizes though which you can use to calculate the positions you need.

Bellow is a function I use to get the boundaries of a mark. (may not be the correct way I am new here :slight_smile: ) The ResolvedPos would come from your selection ($cursor in my case). This line let from = ResolvedPos.start() + ResolvedPos.parentOffset - ResolvedPos.textOffset;

export function getMarkRange(ResolvedPos, mark) {

    // console.log('ResolvedPos.parentOffset');
    // console.log(ResolvedPos.parentOffset);
    //
    // console.log('ResolvedPos.textOffset');
    // console.log(ResolvedPos.textOffset);
    //
    // console.log('ResolvedPos.start()');
    // console.log(ResolvedPos.start());
    //
    // console.log('ResolvedPos.end()');
    // console.log(ResolvedPos.end());
    //
    // console.log('ResolvedPos.before()');
    // console.log(ResolvedPos.before());
    //
    // console.log('ResolvedPos.after()');
    // console.log(ResolvedPos.after());

    // ResolvedPos.parent.childAfter(ResolvedPos.parentOffset)


    let parent = ResolvedPos.parent;
    let index = ResolvedPos.index();

    // ResolvedPos is at the end of parent there are nodes with this index
    if (parent.content.content.length === index){
        return false;
    }

    const child = parent.content.content[index];

    let from = ResolvedPos.start() + ResolvedPos.parentOffset - ResolvedPos.textOffset;
    let to = from + child.nodeSize;

    // look ahead
    for (let i = index + 1; i < parent.content.content.length; i++) {
        let temp = parent.content.content[i];
        if(mark.isInSet(temp.marks)){
            to += temp.nodeSize;
        }else{
            break;
        }
    }

    // look back
    for (let i = index - 1; i >= 0 ; i--) {
        let temp = parent.content.content[i];
        if(mark.isInSet(temp.marks)){
            from -= temp.nodeSize;
        }else{
            break;
        }
    }

    return { from, to };
}

Here’s what I did to get a position from my index:

let newPosition;
state.doc.forEach((node, offset, index) => {
	if (index === currentIndex + relativeIndex) {
		newPosition = offset;
	}
});

1 Like

@marijn Maybe consider add a Node API that can return the node absolute pos, the parameter is doc and the node? Perhaps the immutable data structure make it difficult?

A given node instance may appear in a document multiple times, hence node object identity is absolutely useless for referring to a specific node in a document.

Thank you for posting your solution !

@marijn Just came back to this because I needed to get a position from an index again.

Do you think a resolveIndex method on Node would make sense to complement the resolve method?

If you already know the parent node and the index, it seems like a ResolvedPos isn’t going to add a lot of information. Also, you’d get a resolved position rooted in a local node instead of the document, which is likely to be confusing. Maybe a method to go from an index to a node-local offset would be more useful?

That’s all I’ve ever used it for I think, so that makes sense to me.

In your use case, do you have a resolved position that goes through the node? Would a posAtIndex(depth, index) method on ResolvedPos cover what you need?

I do have a ResolvedPos, but it doesn’t go through the node I have the index for. (But I don’t think that would matter for your suggestion?)

In the two cases that I need this I have a either a shortcut or UI triggered command which moves the selection backward or forward some number of nodes. In one case it’s a constant amount between 1-3, in another it involves starting at the current node and searching forward or backward for the next node of interest. So I have the ResolvedPos of the cursor, and I take position.index(x) + indexOffset to get the index of the node to move the cursor to.

I meant through its parent.

I’ve opened an RFC for this.

Could you confirm that this would work for your case?

Yes, this would work. Should I comment on the rfc issue or something?

No, I just wanted to be sure that it actually helps. I’ve accepted the RFC and implemented this in prosemirror-model 1.11.0

Awesome, thanks!