UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

202 lines (201 loc) 6.73 kB
import { printTree } from 'tree-dump/lib/printTree'; import { CONST, updateJson, updateNum } from '../../../json-hash/hash'; import { MarkerOverlayPoint } from '../overlay/MarkerOverlayPoint'; import { UndefEndIter } from '../../../util/iterator'; import { Inline } from './Inline'; import { formatType, getTag } from '../slice/util'; import { Range } from '../rga/Range'; export class Block extends Range { txt; path; marker; start; end; parent = null; children = []; constructor(txt, path, marker, start, end) { super(txt.str, start, end); this.txt = txt; this.path = path; this.marker = marker; this.start = start; this.end = end; } /** * @returns Stable unique identifier within a list of blocks. Used for React * or other rendering library keys. */ key() { if (!this.marker) return this.tag(); const id = this.marker.id; return id.sid.toString(36) + id.time.toString(36); } tag() { return getTag(this.path); } attr() { return this.marker?.data(); } isLeaf() { return false; } /** * Iterate through all overlay points of this block, until the next marker * (regardless if that marker is a child or not). */ points0(withMarker = false) { const txt = this.txt; const overlay = txt.overlay; const iterator = overlay.points0(this.marker); let closed = false; return () => { if (withMarker) { withMarker = false; return this.marker ?? overlay.START; } if (closed) return; const point = iterator(); if (!point) return; if (point instanceof MarkerOverlayPoint) { closed = true; return; } return point; }; } points(withMarker) { return new UndefEndIter(this.points0(withMarker)); } tuples0() { const overlay = this.txt.overlay; const marker = this.marker; const iterator = overlay.tuples0(marker); let closed = false; return () => { if (closed) return; let pair = iterator(); while (!marker && pair && pair[1] && pair[1].cmpSpatial(this.start) < 0) pair = iterator(); if (!pair) return (closed = true), void 0; if (!pair[1] || pair[1] instanceof MarkerOverlayPoint) closed = true; return pair; }; } /** * @todo Consider moving inline-related methods to {@link LeafBlock}. */ texts0() { const txt = this.txt; const overlay = txt.overlay; const iterator = this.tuples0(); const start = this.start; const end = this.end; const startIsMarker = overlay.isMarker(start.id); const endIsMarker = overlay.isMarker(end.id); let isFirst = true; let next = iterator(); let closed = false; const newIterator = () => { if (closed) return; const pair = next; next = iterator(); if (!pair) return; const [overlayPoint1, overlayPoint2] = pair; let point1 = overlayPoint1; let point2 = overlayPoint2; if (isFirst) { isFirst = false; if (start.cmpSpatial(overlayPoint1) > 0) point1 = start; if (startIsMarker) { point1 = point1.clone(); point1.step(1); // Skip condition when inline annotations tarts immediately at th // beginning of the block. if (point1.cmp(point2) === 0) return newIterator(); } } if (!endIsMarker && end.cmpSpatial(overlayPoint2) < 0) { closed = true; point2 = end; } return new Inline(txt, overlayPoint1, overlayPoint2, point1, point2); }; return newIterator; } /** * @todo Consider moving inline-related methods to {@link LeafBlock}. */ texts() { return new UndefEndIter(this.texts0()); } text() { let str = ''; const children = this.children; const length = children.length; for (let i = 0; i < length; i++) str += children[i].text(); return str; } // ------------------------------------------------------------------- export toJson() { const data = this.attr(); const attr = data !== void 0 ? { data } : null; const node = [this.tag(), attr]; const children = this.children; const length = children.length; for (let i = 0; i < length; i++) node.push(children[i].toJson()); return node; } // ----------------------------------------------------------------- Stateful hash = 0; refresh() { const { path, children } = this; let state = CONST.START_STATE; state = updateJson(state, path); const marker = this.marker; if (marker) { state = updateNum(state, marker.marker.refresh()); state = updateNum(state, marker.textHash); } else { state = updateNum(state, this.txt.overlay.leadingTextHash); } for (let i = 0; i < children.length; i++) state = updateNum(state, children[i].refresh()); return (this.hash = state); } // ---------------------------------------------------------------- Printable toStringName() { return 'Block'; } toStringHeader() { const hash = `#${this.hash.toString(36).slice(-4)}`; const tag = this.path.map((step) => formatType(step)).join('.'); const header = `${super.toString('', true)} ${hash} ${tag} `; return header; } toString(tab = '') { const header = this.toStringHeader(); const hasChildren = !!this.children.length; return (header + printTree(tab, [ this.marker ? (tab) => this.marker.toString(tab) : null, this.marker && hasChildren ? () => '' : null, hasChildren ? (tab) => 'children' + printTree(tab, this.children.map((child, i) => (tab) => `${i + 1}. ` + child.toString(tab + ' ' + ' '.repeat(String(i + 1).length)))) : null, ])); } }