Expanding the selection to the active mark?

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.

1 Like

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

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.

1 Like

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

I am also looking for a solution like this. So if the cursor is on a link or a single link is fully selected, then it is editable. In current example it is not clear which url it is.

I was testing this out on links that had bold/italics etc in them. This I believe handles those cases. The implementations above don’t work for me due to the endIndex first calculation in some placements of the cursor (directly after bold in a link?). Hope this maybe helps someone else … !

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

  const { parent, parentOffset } = $pos;
  const start = parent.childAfter(parentOffset);
  if (!start.node) return null;

  const link = start.node.marks.find((mark) => mark.type === state.schema.marks.link);
  if (!link) return null;

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