json-joy
Version:
Collection of libraries for building collaborative editing apps.
200 lines (199 loc) • 6.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Block = void 0;
const printTree_1 = require("tree-dump/lib/printTree");
const json_hash_1 = require("../../../json-hash");
const MarkerOverlayPoint_1 = require("../overlay/MarkerOverlayPoint");
const iterator_1 = require("../../../util/iterator");
const Inline_1 = require("./Inline");
const util_1 = require("../slice/util");
const Range_1 = require("../rga/Range");
class Block extends Range_1.Range {
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;
this.parent = null;
this.children = [];
// ----------------------------------------------------------------- Stateful
this.hash = 0;
}
/**
* @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() {
const path = this.path;
const length = path.length;
if (!length)
return '';
const step = path[length - 1];
return Array.isArray(step) ? step[0] : step;
}
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_1.MarkerOverlayPoint) {
closed = true;
return;
}
return point;
};
}
points(withMarker) {
return new iterator_1.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_1.MarkerOverlayPoint)
closed = true;
return pair;
};
}
/**
* @todo Consider moving inline-related methods to {@link LeafBlock}.
*/
texts0() {
const txt = this.txt;
const iterator = this.tuples0();
const start = this.start;
const end = this.end;
const startIsMarker = txt.overlay.isMarker(start.id);
const endIsMarker = txt.overlay.isMarker(end.id);
let isFirst = true;
let next = iterator();
let closed = false;
return () => {
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);
}
}
if (!endIsMarker && end.cmpSpatial(overlayPoint2) < 0) {
closed = true;
point2 = end;
}
return new Inline_1.Inline(txt, overlayPoint1, overlayPoint2, point1, point2);
};
}
/**
* @todo Consider moving inline-related methods to {@link LeafBlock}.
*/
texts() {
return new iterator_1.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;
}
refresh() {
const { path, children } = this;
let state = json_hash_1.CONST.START_STATE;
state = (0, json_hash_1.updateJson)(state, path);
const marker = this.marker;
if (marker) {
state = (0, json_hash_1.updateNum)(state, marker.marker.refresh());
state = (0, json_hash_1.updateNum)(state, marker.textHash);
}
else {
state = (0, json_hash_1.updateNum)(state, this.txt.overlay.leadingTextHash);
}
for (let i = 0; i < children.length; i++)
state = (0, json_hash_1.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) => (0, util_1.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 +
(0, printTree_1.printTree)(tab, [
this.marker ? (tab) => this.marker.toString(tab) : null,
this.marker && hasChildren ? () => '' : null,
hasChildren
? (tab) => 'children' +
(0, printTree_1.printTree)(tab, this.children.map((child, i) => (tab) => `${i + 1}. ` + child.toString(tab + ' ' + ' '.repeat(String(i + 1).length))))
: null,
]));
}
}
exports.Block = Block;