json-joy
Version:
Collection of libraries for building collaborative editing apps.
143 lines (142 loc) • 5.86 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DomController = void 0;
const tree_dump_1 = require("tree-dump");
const AvlMap_1 = require("sonic-forest/lib/avl/AvlMap");
const InputController_1 = require("./InputController");
const CursorController_1 = require("./CursorController");
const RichTextController_1 = require("./RichTextController");
const KeyController_1 = require("./KeyController");
const CompositionController_1 = require("./CompositionController");
const AnnalsController_1 = require("./annals/AnnalsController");
const constants_1 = require("../constants");
const constants_2 = require("../../../json-crdt-extensions/peritext/rga/constants");
const UiHandle_1 = require("../../events/defaults/ui/UiHandle");
const json_crdt_patch_1 = require("../../../json-crdt-patch");
class DomController {
constructor(opts) {
this.opts = opts;
/**
* Index of block HTML <div> elements keyed by the ID (timestamp) of the split
* boundary that starts that block element.
*/
this.blocks = new AvlMap_1.AvlMap(json_crdt_patch_1.compare);
/**
* Index of inline HTML <span> elements keyed by the slice start {@link Point}.
*/
this.inlines = new AvlMap_1.AvlMap((a, b) => a.cmpSpatial(b));
const { source, events, log } = opts;
const { txt } = events;
this.txt = txt;
const et = (this.et = opts.events.et);
const keys = (this.keys = new KeyController_1.KeyController({ source }));
const comp = (this.comp = new CompositionController_1.CompositionController({ et, source, txt }));
this.input = new InputController_1.InputController({ et, source, txt, comp });
this.cursor = new CursorController_1.CursorController({ et, source, txt, keys });
this.richText = new RichTextController_1.RichTextController({ et, source, txt });
this.annals = new AnnalsController_1.AnnalsController({ et, txt, log });
const uiHandle = new UiHandle_1.UiHandle(txt, this);
events.ui = uiHandle;
events.undo = this.annals;
}
/** -------------------------------------------------- {@link UiLifeCycles} */
start() {
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 () => {
stopKeys();
stopComp();
stopInput();
stopCursor();
stopRichText();
stopAnnals();
};
}
/** ------------------------------------------------- {@link PeritextUiApi} */
focus() {
this.opts.source.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 ?? (el = this.opts.source);
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[constants_1.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[constants_1.ElementAttr.InlineOffset];
if (inline) {
const contains = inline.contains(char);
if (contains)
return span;
}
}
return;
}
getCharRect(char) {
const txt = this.opts.events.txt;
const id = typeof char === 'number' ? txt.str.find(char) : char;
if (!id)
return;
const start = txt.point(id, constants_2.Anchor.Before);
const end = txt.point(id, constants_2.Anchor.After);
const charRange = txt.range(start, end);
const span = this.findSpanContaining(charRange);
if (!span)
return;
const inline = span[constants_1.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' +
(0, tree_dump_1.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),
]));
}
}
exports.DomController = DomController;