UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

126 lines (125 loc) 4.21 kB
import { Anchor } from '../../../../json-crdt-extensions/peritext/rga/constants'; export class UiHandle { txt; api; constructor(txt, api) { this.txt = txt; this.api = api; } point(pos) { return typeof pos === 'number' ? this.txt.pointAt(pos) : pos; } /** * Finds the position of the character at the given point (position between * characters). The first position has index of 0. Have to specify the * direction of the search, forward or backward. * * @param point The index of the character in the text, or a {@link Point}. * @param fwd Whether to find the location of the next character after the * given {@link Point} or before, defaults to `true`. * @returns The bounding rectangle of the character at the given index. */ getPointRect(pos, right = true) { const txt = this.txt; const point = typeof pos === 'number' ? txt.pointAt(pos) : pos.clone(); if (right) point.refBefore(); else point.refAfter(); return this.api.getCharRect?.(point.id); } pointX(pos) { const txt = this.txt; const point = typeof pos === 'number' ? txt.pointAt(pos) : pos; const rect = this.getPointRect(point, point.anchor === Anchor.Before); if (!rect) return; const x = point.anchor === Anchor.Before ? rect.x : rect.x + rect.width; return [x, rect]; } findPointAtX(targetX, line) { let point = line[0][0].clone(); const curr = point; let bestDiff = 1e9; const max = line[1][0].viewPos() - line[0][0].viewPos(); if (!this.api.getCharRect) return point; for (let i = 0; i < max; i++) { const pointX = this.pointX(curr); if (!pointX) break; const [x] = pointX; const diff = Math.abs(x - targetX); if (diff <= bestDiff) { bestDiff = diff; point = curr.clone(); } else break; curr.step(1); } return point; } getLineEnd(pos, right = true) { const startPoint = this.point(pos); if (startPoint.isAbs()) return; const startRect = this.getPointRect(startPoint, right); if (!startRect) return; let curr = startPoint.clone(); let currRect = startRect; const prepareReturn = () => { if (right) { curr.step(1); curr.refAfter(); } else { curr.step(-1); curr.refBefore(); } return [curr, currRect]; }; while (true) { const next = curr.copy((p) => p.step(right ? 1 : -1)); if (!next) return prepareReturn(); if (next.isAbs()) return prepareReturn(); const nextRect = this.getPointRect(next, right); if (!nextRect) return prepareReturn(); if (right ? nextRect.x < currRect.x : nextRect.x > currRect.x) return prepareReturn(); curr = next; currRect = nextRect; } } getLineInfo(pos) { const txt = this.txt; const point = this.point(pos); if (point.isAbs()) return; const isEndOfText = point.viewPos() === txt.strApi().length(); if (isEndOfText) return; const left = this.getLineEnd(point, false); const right = this.getLineEnd(point, true); if (!left || !right) return; return [left, right]; } getNextLineInfo(line, direction = 1) { const edge = line[direction > 0 ? 1 : 0][0]; const txt = this.txt; if (edge.viewPos() >= txt.str.length()) return; const point = edge.copy((p) => p.step(direction)); const success = txt.overlay.skipMarkers(point, direction); if (!success) return; if (point.isAbs()) return; return this.getLineInfo(point); } }