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 !