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