Expanding the selection to the active mark?


#1

Hi,

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.


#2

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}
}

#3

Thank you!

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


#4

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


#5

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.


#6

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.


#7

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) {
    return;
  }

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

  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};
}