Expanding the selection to the active mark?



I’m trying to implement buttons for link, typically remove link and change link destination. Using/patching toggleMark I was able to make it work if the user selects exactly the text that has the link.

However I want to be able to do that when the user when the cursor is in the middle of the link. I think it could be done by modifying the selection to make it match the whole mark before applying the command.

I figured out how to get the active marks, but they come without their positions so I can’t find how to do that. Is there a simple way to do it? Basically get the from/to positions corresponding at the start and end positions of the mark.


You’ll need to inspect the parent node of the cursor, and find the range that the current link covers in that. You could do something like this (untested):

function linkAround(doc, pos) {
  let $pos = doc.resolve(pos), parent = $pos.parent
  let start = parent.childAfter($pos.parentOffset)
  if (!start.node) return null
  let link = start.node.marks.find(mark => mark.type.name == "link")
  if (!link) return null

  let startIndex = $pos.index(), startPos = $pos.start() + start.offset
  while (startIndex > 0 && link.isInSet($pos.child(startIndex - 1).marks))
    startPos -= $pos.child(--startIndex).nodeSize
  let endIndex = $pos.indexAfter(), endPos = startPos + start.node.nodeSize
  while (end < parent.childCount && link.isInSet($pos.child(endIndex).marks))
    endPos += $pos.child(endIndex++).nodeSize
  return {from: startPos, to: endPos}


Thank you!

I was able to make it work after a few adaptations (for example it’s not ResolvedPos.child, it’s ResolvedPos.node).


Oh, those should be parent.child, not $pos.child (and also not $pos.node!)


I see! I think it was working because in the case I was testing, it wasn’t going in the while anyway so the first values were the correct ones.


I have found this and Find extents of a mark given a selection. Could this maybe be made into a function in ProseMirror. I am afraid to miss some edge case doing this myself.


So based on comments above, this is fixed code. Is this right?

function linkAround(doc, pos) {
  let $pos = doc.resolve(pos);

  let start = $pos.parent.childAfter($pos.parentOffset);
  if (!start.node) {

  let link = start.node.marks.find((mark) => mark.type === this.$editorView.state.schema.marks.link);
  if (!link) {

  let startIndex = $pos.index();
  let startPos = $pos.start() + start.offset;
  while (startIndex > 0 && link.isInSet($pos.parent.child(startIndex - 1).marks)) {
    startIndex -= 1;
    startPos -= $pos.parent.child(startIndex).nodeSize;

  let endIndex = $pos.indexAfter();
  let endPos = startPos + start.node.nodeSize;
  while (endIndex < $pos.parent.childCount && link.isInSet($pos.parent.child(endIndex).marks)) {
    endPos += $pos.parent.child(endIndex).nodeSize;
    endIndex += 1;

  return {from: startPos, to: endPos};