What’s the best way to, given a selection, a) find out if a given mark applies to all inline content in the selection, and b) if it does, expand the selection to the beginning and end of the content contiguously having that mark?
And I put my cursor between the e’s in “sleep”, how could I programmatically get the whole link?
I have a custom mark that’s semantically similar to a hyperlink, and I want to provide UI for changing the target. It doesn’t make sense to update the target for a substring in the middle of a link. I want to be able to change the target of the whole link.
If you’re not interested in marks outside of the current textblock, you could just iterate over the child nodes of the textblock in both directions, and keep going until you find one that doesn’t have the mark.
Here’s some (untested) code to that effect:
// :: (ResolvedPos, Mark)
function markExtend($start, mark) {
let startIndex = $start.index(), endIndex = $end.indexAfter()
while (startIndex > 0 && mark.isInSet($start.parent.child(startIndex - 1).marks)) startIndex--
while (startIndex < $start.parent.childCount && mark.isInSet($start.parent.child(endIndex).marks)) endIndex++
// (This will be easier in the next release with Fragment.offsetAt)
let startPos = $start.start(), endPos = startPos
for (let i = 0; i < endIndex; i++) {
let size = $start.parent.child(i).nodeSize
if (i < startPos) startPos += size
endPos += size
}
return {from: startPos, to: endPos}
}
@marijn good premonition as my implementation just started breaking and throwing RangeError: Resolving a position in an outdated DOM structure
For my purposes, this seems to work start = pm.selection.head - pm.selection.$head.nodeBefore.nodeSize. As I’ll always be “within” the node in question.
One oddity to me is that a resolved path splits an actual node. E.g within “foo ^bar” and cursor at ^, nodeBefore = "foo " and nodeAfter = “bar”. I wish the resolved position gave me currentNode and offset within that node as appose to the grandparent as parent.
The current node is the textblock node. Text nodes don’t have content and you can’t logically have a position inside of them. That’s a bit awkward at times, but it’s preferable to the alternative, which would mean we can no longer treat textblock content as flat.
(If you really need the full text nod in this case, pos.parent.child(pos.index()) gets it.)
Just a small nitpick: actually it should be if (i < $start.start()) startPos += size instead of if (i < startPos) startPos += size. Otherwise it doesn’t work since startPos is updated at every iteration.