json-joy
Version:
Collection of libraries for building collaborative editing apps.
294 lines • 10.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OverlayPoint = void 0;
const Point_1 = require("../rga/Point");
const clock_1 = require("../../../json-crdt-patch/clock");
const refs_1 = require("./refs");
const printTree_1 = require("tree-dump/lib/printTree");
const util_1 = require("../slice/util");
/**
* A {@link Point} which is indexed in the {@link Overlay} tree. Represents
* sparse locations in the string of the places where annotation slices start,
* end, or are broken down by other intersecting slices.
*/
class OverlayPoint extends Point_1.Point {
constructor() {
super(...arguments);
/**
* Hash of text contents until the next {@link OverlayPoint}. This field is
* modified by the {@link Overlay} tree.
*/
this.hash = 0;
/** -------------------------------------------------- {@link HeadlessNode} */
this.p = undefined;
this.l = undefined;
this.r = undefined;
/** ------------------------------------------------- {@link HeadlessNode2} */
this.p2 = undefined;
this.l2 = undefined;
this.r2 = undefined;
// ------------------------------------------------------------------- layers
/**
* Sorted list of layers, contains the interval from this point to the next
* one. A *layer* is a part of a slice from the current point to the next one.
* This interval can contain many layers, as the slices can be overlapped.
*/
this.layers = [];
// --------------------------------------------------------------------- refs
/**
* Sorted list of all references to rich-text constructs.
*/
this.refs = [];
// ------------------------------------------------------------------ markers
/**
* Hash value of the following text contents, up until the next marker.
*/
this.textHash = 0;
/**
* Collapsed slices - markers (block splits), which represent a single point
* in the text, even if the start and end of the slice are different.
*
* @todo This normally should never be a list, but a single item. Enforce?
*/
this.markers = [];
}
/**
* Inserts a slice to the list of layers which contains the area from this
* point until the next one. The operation is idempotent, so inserting the
* same slice twice will not change the state of the point. The layers are
* sorted by the slice ID.
*
* @param slice Slice to add to the layer list.
*/
addLayer(slice) {
const layers = this.layers;
const length = layers.length;
if (!length) {
layers.push(slice);
return;
}
// We attempt to insert from the end of the list, as it is the most likely
// scenario. And `.push()` is more efficient than `.unshift()`.
const lastSlice = layers[length - 1];
const sliceId = slice.id;
const cmp = (0, clock_1.compare)(lastSlice.id, sliceId);
if (cmp < 0) {
layers.push(slice);
return;
}
else if (!cmp)
return;
for (let i = length - 2; i >= 0; i--) {
const currSlice = layers[i];
const cmp = (0, clock_1.compare)(currSlice.id, sliceId);
if (cmp < 0) {
layers.splice(i + 1, 0, slice);
return;
}
else if (!cmp)
return;
}
layers.unshift(slice);
}
/**
* Removes a slice from the list of layers, which start from this overlay
* point.
*
* @param slice Slice to remove from the layer list.
*/
removeLayer(slice) {
const layers = this.layers;
const length = layers.length;
for (let i = 0; i < length; i++) {
if (layers[i] === slice) {
layers.splice(i, 1);
return;
}
}
}
/**
* Insert a reference to a marker.
*
* @param slice A marker (split slice).
*/
addMarkerRef(slice) {
const markers = this.markers;
const length = markers.length;
for (let i = 0; i < length; i++)
if (markers[i] === slice)
return;
this.refs.push(slice);
this.addMarker(slice);
}
upsertStartRef(slice) {
const refs = this.refs;
const length = refs.length;
for (let i = 0; i < length; i++) {
const ref = refs[i];
if (ref instanceof refs_1.OverlayRefSliceStart && ref.slice === slice)
return ref;
}
const ref = new refs_1.OverlayRefSliceStart(slice);
this.refs.push(ref);
return ref;
}
upsertEndRef(slice) {
const refs = this.refs;
const length = refs.length;
for (let i = 0; i < length; i++) {
const ref = refs[i];
if (ref instanceof refs_1.OverlayRefSliceEnd && ref.slice === slice)
return ref;
}
const ref = new refs_1.OverlayRefSliceEnd(slice);
this.refs.push(ref);
return ref;
}
/**
* Removes a reference to a marker or a slice, and remove the corresponding
* layer or marker.
*
* @param slice A slice to remove the reference to.
*/
removeRef(slice) {
const refs = this.refs;
const length = refs.length;
for (let i = 0; i < length; i++) {
const ref = refs[i];
if (ref === slice) {
refs.splice(i, 1);
this.removeMarker(slice);
return;
}
if ((ref instanceof refs_1.OverlayRefSliceStart && ref.slice === slice) ||
(ref instanceof refs_1.OverlayRefSliceEnd && ref.slice === slice)) {
refs.splice(i, 1);
this.removeLayer(slice);
return;
}
}
}
/**
* @deprecated Use `this.marker().type()` instead.
*/
type() {
return this.markers[0] && this.markers[0].type();
}
/**
* @deprecated Use `this.marker().data()` instead.
*/
data() {
return this.markers[0] && this.markers[0].data();
}
/**
* @deprecated Make this a method.
*/
get marker() {
const marker = this.markers[0];
if (!marker)
throw new Error('NO_MARKER');
return marker;
}
isMarker() {
const markers = this.markers;
const length = markers.length;
for (let i = 0; i < length; i++)
if (markers[i].isMarker())
return true;
return false;
}
/**
* Inserts a slice to the list of markers which represent a single point in
* the text, even if the start and end of the slice are different. The
* operation is idempotent, so inserting the same slice twice will not change
* the state of the point. The markers are sorted by the slice ID.
*
* @param slice Slice to add to the marker list.
*
* @todo Make this method private.
*/
addMarker(slice) {
const markers = this.markers;
const length = markers.length;
if (!length) {
markers.push(slice);
return;
}
// We attempt to insert from the end of the list, as it is the most likely
// scenario. And `.push()` is more efficient than `.unshift()`.
const lastSlice = markers[length - 1];
const sliceId = slice.id;
const cmp = (0, clock_1.compare)(lastSlice.id, sliceId);
if (cmp < 0) {
markers.push(slice);
return;
}
else if (!cmp)
return;
for (let i = length - 2; i >= 0; i--) {
const currSlice = markers[i];
const cmp = (0, clock_1.compare)(currSlice.id, sliceId);
if (cmp < 0) {
markers.splice(i + 1, 0, slice);
return;
}
else if (!cmp)
return;
}
markers.unshift(slice);
}
/**
* Removes a slice from the list of markers, which represent a single point in
* the text, even if the start and end of the slice are different.
*
* @param slice Slice to remove from the marker list.
*/
removeMarker(slice) {
const markers = this.markers;
const length = markers.length;
for (let i = 0; i < length; i++) {
if (markers[i] === slice) {
markers.splice(i, 1);
return;
}
}
}
/** ----------------------------------------------------- {@link Printable} */
toStringName() {
return 'OverlayPoint' + (this.isMarker() ? '::Marker' : '');
}
toStringHeader(tab = '', lite) {
const header = super.toString(tab, lite);
if (this.isMarker()) {
const hash = lite ? '' : `#${this.textHash.toString(36).slice(-4)}`;
const typeFormatted = (0, util_1.formatType)(this.type());
const typeFormatted2 = lite ? '' : ' ' + typeFormatted;
return `${header}${lite ? '' : ' '}${hash}${typeFormatted2}`;
}
return header;
}
toString(tab = '', lite) {
if (this.isMarker()) {
return (this.toStringHeader(tab, lite) +
(lite
? ''
: (0, printTree_1.printTree)(tab, this.markers.map((slice) => (tab) => slice.toString(tab)))));
}
const refs = lite ? '' : `, refs = ${this.refs.length}`;
const header = this.toStringHeader(tab, lite) + refs;
if (lite)
return header;
const children = [];
const layers = this.layers;
const layerLength = layers.length;
for (let i = 0; i < layerLength; i++)
children.push((tab) => layers[i].toString(tab));
const markers = this.markers;
const markerLength = markers.length;
for (let i = 0; i < markerLength; i++)
children.push((tab) => markers[i].toString(tab));
return header + (0, printTree_1.printTree)(tab, children);
}
}
exports.OverlayPoint = OverlayPoint;
//# sourceMappingURL=OverlayPoint.js.map