UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

166 lines 6.04 kB
import { printTree } from 'tree-dump'; import { AvlMap } from 'sonic-forest/lib/avl/AvlMap'; import { InputController } from './InputController'; import { CursorController } from './CursorController'; import { RichTextController } from './RichTextController'; import { KeyController } from './KeyController'; import { CompositionController } from './CompositionController'; import { AnnalsController } from './annals/AnnalsController'; import { ElementAttr } from '../constants'; import { Anchor } from '../../../json-crdt-extensions/peritext/rga/constants'; import { compare } from '../../../json-crdt-patch'; import { UiHandle } from '../../../json-crdt-extensions/peritext/events/defaults/ui/UiHandle'; export class DomController { events; log; txt; et; keys; comp; input; cursor; richText; annals; /** * Index of block HTML <div> elements keyed by the ID (timestamp) of the split * boundary that starts that block element. */ blocks = new AvlMap(compare); /** * Index of inline HTML <span> elements keyed by the slice start {@link Point}. */ inlines = new AvlMap((a, b) => a.cmpSpatial(b)); constructor(events, log) { this.events = events; this.log = log; const { txt } = events; this.txt = txt; this.et = events.et; this.keys = new KeyController(this); this.comp = new CompositionController(this); this.input = new InputController(this); this.cursor = new CursorController(this); this.richText = new RichTextController(this); this.annals = new AnnalsController(this); const uiHandle = new UiHandle(txt, this); events.ui = uiHandle; events.undo = this.annals; } isEditable(el) { if (!el.isContentEditable) return false; const computed = getComputedStyle(el); return (computed.getPropertyValue('--jsonjoy-peritext-id') === this.et.id + '' && computed.getPropertyValue('--jsonjoy-peritext-editable') === 'yes'); } /** -------------------------------------------------- {@link UiLifeCycles} */ /** * Must be set before calling {@link start}. */ el; start() { const { et, el } = this; el.contentEditable = 'true'; const style = el.style; style.setProperty('--jsonjoy-peritext-id', et.id + ''); style.setProperty('--jsonjoy-peritext-editable', 'yes'); const stopKeys = this.keys.start(); const stopComp = this.comp.start(); const stopInput = this.input.start(); const stopCursor = this.cursor.start(); const stopRichText = this.richText.start(); const stopAnnals = this.annals.start(); return () => { el.contentEditable = 'false'; stopKeys(); stopComp(); stopInput(); stopCursor(); stopRichText(); stopAnnals(); }; } /** ------------------------------------------------- {@link PeritextUiApi} */ focus() { this.el.focus(); } getSpans(blockInnerId) { let el; if (blockInnerId) { const txt = this.txt; const marker = txt.overlay.getOrNextLowerMarker(blockInnerId); const markerId = marker?.id ?? txt.str.id; el = this.blocks.get(markerId); } el ??= this.el; return el.querySelectorAll('.jsonjoy-peritext-inline'); } findSpanContaining(char) { const start = char.start; const overlayPoint = this.txt.overlay.getOrNextLower(start); if (overlayPoint) { const span = this.inlines.get(overlayPoint); if (span) { const inline = span[ElementAttr.InlineOffset]; if (inline) { const contains = inline.contains(char); if (contains) return span; } } } const spans = this.getSpans(start); const length = spans.length; for (let i = 0; i < length; i++) { const span = spans[i]; const inline = span[ElementAttr.InlineOffset]; if (inline) { const contains = inline.contains(char); if (contains) return span; } } return; } getCharRect(char) { const txt = this.events.txt; const id = typeof char === 'number' ? txt.str.find(char) : char; if (!id) return; const start = txt.point(id, Anchor.Before); const end = txt.point(id, Anchor.After); const charRange = txt.range(start, end); const span = this.findSpanContaining(charRange); if (!span) return; const inline = span[ElementAttr.InlineOffset]; if (!inline) return; const textNode = span.firstChild; if (!textNode) return; const range = document.createRange(); range.selectNode(textNode); const offset = Math.max(0, Math.min(textNode.length - 1, charRange.start.viewPos() - inline.start.viewPos())); range.setStart(textNode, offset); range.setEnd(textNode, offset + 1); const rects = range.getClientRects(); return rects[0]; } caretRect() { return document.getElementById(this.cursor.caretId)?.getBoundingClientRect?.(); } /** ----------------------------------------------------- {@link Printable} */ toString(tab) { return ('DOM' + printTree(tab, [ (tab) => 'blocks: ' + this.blocks.size(), (tab) => 'inlines: ' + this.inlines.size(), (tab) => this.cursor.toString(tab), (tab) => this.keys.toString(tab), (tab) => this.comp.toString(tab), (tab) => this.annals.toString(tab), ])); } } //# sourceMappingURL=DomController.js.map