UNPKG

@tiptap/core

Version:

headless rich text editor

91 lines (77 loc) 2.66 kB
import type { Mark as ProseMirrorMark, MarkType, ResolvedPos } from '@tiptap/pm/model' import type { Range } from '../types.js' import { objectIncludes } from '../utilities/objectIncludes.js' function findMarkInSet( marks: ProseMirrorMark[], type: MarkType, attributes: Record<string, any> = {}, ): ProseMirrorMark | undefined { return marks.find(item => { return ( item.type === type && objectIncludes( // Only check equality for the attributes that are provided Object.fromEntries(Object.keys(attributes).map(k => [k, item.attrs[k]])), attributes, ) ) }) } function isMarkInSet(marks: ProseMirrorMark[], type: MarkType, attributes: Record<string, any> = {}): boolean { return !!findMarkInSet(marks, type, attributes) } /** * Get the range of a mark at a resolved position. */ export function getMarkRange( /** * The position to get the mark range for. */ $pos: ResolvedPos, /** * The mark type to get the range for. */ type: MarkType, /** * The attributes to match against. * If not provided, only the first mark at the position will be matched. */ attributes?: Record<string, any>, ): Range | void { if (!$pos || !type) { return } let start = $pos.parent.childAfter($pos.parentOffset) // If the cursor is at the start of a text node that does not have the mark, look backward if (!start.node || !start.node.marks.some(mark => mark.type === type)) { start = $pos.parent.childBefore($pos.parentOffset) } // If there is no text node with the mark even backward, return undefined if (!start.node || !start.node.marks.some(mark => mark.type === type)) { return } // Default to only matching against the first mark's attributes attributes = attributes || start.node.marks[0]?.attrs // We now know that the cursor is either at the start, middle or end of a text node with the specified mark // so we can look it up on the targeted mark const mark = findMarkInSet([...start.node.marks], type, attributes) if (!mark) { return } let startIndex = start.index let startPos = $pos.start() + start.offset let endIndex = startIndex + 1 let endPos = startPos + start.node.nodeSize while (startIndex > 0 && isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)) { startIndex -= 1 startPos -= $pos.parent.child(startIndex).nodeSize } while (endIndex < $pos.parent.childCount && isMarkInSet([...$pos.parent.child(endIndex).marks], type, attributes)) { endPos += $pos.parent.child(endIndex).nodeSize endIndex += 1 } return { from: startPos, to: endPos, } }