UNPKG

@tiptap/extension-node-range

Version:

node range extension for tiptap

116 lines (86 loc) 3.93 kB
import type { Node as ProseMirrorNode, ResolvedPos } from '@tiptap/pm/model' import { Selection } from '@tiptap/pm/state' import type { Mapping } from '@tiptap/pm/transform' import { getSelectionRanges } from './getSelectionRanges.js' import { NodeRangeBookmark } from './NodeRangeBookmark.js' export class NodeRangeSelection extends Selection { depth: number | undefined constructor($anchor: ResolvedPos, $head: ResolvedPos, depth?: number, bias = 1) { // if there is only a cursor we can’t calculate a direction of the selection // that’s why we adjust the head position by 1 in the desired direction const { doc } = $anchor const isCursor = $anchor === $head const isCursorAtEnd = $anchor.pos === doc.content.size && $head.pos === doc.content.size const $correctedHead = isCursor && !isCursorAtEnd ? doc.resolve($head.pos + (bias > 0 ? 1 : -1)) : $head const $correctedAnchor = isCursor && isCursorAtEnd ? doc.resolve($anchor.pos - (bias > 0 ? 1 : -1)) : $anchor const ranges = getSelectionRanges($correctedAnchor.min($correctedHead), $correctedAnchor.max($correctedHead), depth) // get the smallest range start position // this will become the $anchor const $rangeFrom = $correctedHead.pos >= $anchor.pos ? ranges[0].$from : ranges[ranges.length - 1].$to // get the biggest range end position // this will become the $head const $rangeTo = $correctedHead.pos >= $anchor.pos ? ranges[ranges.length - 1].$to : ranges[0].$from super($rangeFrom, $rangeTo, ranges) this.depth = depth } // we can safely ignore this TypeScript error: https://github.com/Microsoft/TypeScript/issues/338 // @ts-ignore get $to() { return this.ranges[this.ranges.length - 1].$to } eq(other: Selection): boolean { return other instanceof NodeRangeSelection && other.$from.pos === this.$from.pos && other.$to.pos === this.$to.pos } map(doc: ProseMirrorNode, mapping: Mapping): NodeRangeSelection { const $anchor = doc.resolve(mapping.map(this.anchor)) const $head = doc.resolve(mapping.map(this.head)) return new NodeRangeSelection($anchor, $head) } toJSON() { return { type: 'nodeRange', anchor: this.anchor, head: this.head, } } get isForwards(): boolean { return this.head >= this.anchor } get isBackwards(): boolean { return !this.isForwards } extendBackwards(): NodeRangeSelection { const { doc } = this.$from if (this.isForwards && this.ranges.length > 1) { const ranges = this.ranges.slice(0, -1) const $from = ranges[0].$from const $to = ranges[ranges.length - 1].$to return new NodeRangeSelection($from, $to, this.depth) } const firstRange = this.ranges[0] const $from = doc.resolve(Math.max(0, firstRange.$from.pos - 1)) return new NodeRangeSelection(this.$anchor, $from, this.depth) } extendForwards(): NodeRangeSelection { const { doc } = this.$from if (this.isBackwards && this.ranges.length > 1) { const ranges = this.ranges.slice(1) const $from = ranges[0].$from const $to = ranges[ranges.length - 1].$to return new NodeRangeSelection($to, $from, this.depth) } const lastRange = this.ranges[this.ranges.length - 1] const $to = doc.resolve(Math.min(doc.content.size, lastRange.$to.pos + 1)) return new NodeRangeSelection(this.$anchor, $to, this.depth) } static fromJSON(doc: ProseMirrorNode, json: any): NodeRangeSelection { return new NodeRangeSelection(doc.resolve(json.anchor), doc.resolve(json.head)) } static create(doc: ProseMirrorNode, anchor: number, head: number, depth?: number, bias = 1): NodeRangeSelection { return new this(doc.resolve(anchor), doc.resolve(head), depth, bias) } getBookmark(): NodeRangeBookmark { return new NodeRangeBookmark(this.anchor, this.head) } } NodeRangeSelection.prototype.visible = false