@liveblocks/core 3.19.5-pre1 → 3.20.0-exp1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +741 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -18
- package/dist/index.d.ts +122 -18
- package/dist/index.js +672 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
|
|
7
7
|
// src/version.ts
|
|
8
8
|
var PKG_NAME = "@liveblocks/core";
|
|
9
|
-
var PKG_VERSION = "3.
|
|
9
|
+
var PKG_VERSION = "3.20.0-exp1";
|
|
10
10
|
var PKG_FORMAT = "esm";
|
|
11
11
|
|
|
12
12
|
// src/dupe-detection.ts
|
|
@@ -5511,7 +5511,9 @@ var OpCode = Object.freeze({
|
|
|
5511
5511
|
DELETE_CRDT: 5,
|
|
5512
5512
|
DELETE_OBJECT_KEY: 6,
|
|
5513
5513
|
CREATE_MAP: 7,
|
|
5514
|
-
CREATE_REGISTER: 8
|
|
5514
|
+
CREATE_REGISTER: 8,
|
|
5515
|
+
CREATE_TEXT: 9,
|
|
5516
|
+
UPDATE_TEXT: 10
|
|
5515
5517
|
});
|
|
5516
5518
|
function isIgnoredOp(op) {
|
|
5517
5519
|
return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
|
|
@@ -5525,7 +5527,8 @@ var CrdtType = Object.freeze({
|
|
|
5525
5527
|
OBJECT: 0,
|
|
5526
5528
|
LIST: 1,
|
|
5527
5529
|
MAP: 2,
|
|
5528
|
-
REGISTER: 3
|
|
5530
|
+
REGISTER: 3,
|
|
5531
|
+
TEXT: 4
|
|
5529
5532
|
});
|
|
5530
5533
|
function isRootStorageNode(node) {
|
|
5531
5534
|
return node[0] === "root";
|
|
@@ -5542,6 +5545,9 @@ function isMapStorageNode(node) {
|
|
|
5542
5545
|
function isRegisterStorageNode(node) {
|
|
5543
5546
|
return node[1].type === CrdtType.REGISTER;
|
|
5544
5547
|
}
|
|
5548
|
+
function isTextStorageNode(node) {
|
|
5549
|
+
return node[1].type === CrdtType.TEXT;
|
|
5550
|
+
}
|
|
5545
5551
|
function isCompactRootNode(node) {
|
|
5546
5552
|
return node[0] === "root";
|
|
5547
5553
|
}
|
|
@@ -5564,6 +5570,9 @@ function* compactNodesToNodeStream(compactNodes) {
|
|
|
5564
5570
|
case CrdtType.REGISTER:
|
|
5565
5571
|
yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
|
|
5566
5572
|
break;
|
|
5573
|
+
case CrdtType.TEXT:
|
|
5574
|
+
yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
|
|
5575
|
+
break;
|
|
5567
5576
|
default:
|
|
5568
5577
|
}
|
|
5569
5578
|
}
|
|
@@ -5592,6 +5601,17 @@ function* nodeStreamToCompactNodes(nodes) {
|
|
|
5592
5601
|
const id = node[0];
|
|
5593
5602
|
const crdt = node[1];
|
|
5594
5603
|
yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
|
|
5604
|
+
} else if (isTextStorageNode(node)) {
|
|
5605
|
+
const id = node[0];
|
|
5606
|
+
const crdt = node[1];
|
|
5607
|
+
yield [
|
|
5608
|
+
id,
|
|
5609
|
+
CrdtType.TEXT,
|
|
5610
|
+
crdt.parentId,
|
|
5611
|
+
crdt.parentKey,
|
|
5612
|
+
crdt.data,
|
|
5613
|
+
crdt.version
|
|
5614
|
+
];
|
|
5595
5615
|
} else {
|
|
5596
5616
|
}
|
|
5597
5617
|
}
|
|
@@ -8427,6 +8447,581 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8427
8447
|
}
|
|
8428
8448
|
};
|
|
8429
8449
|
|
|
8450
|
+
// src/crdts/liveTextOps.ts
|
|
8451
|
+
function attributesEqual(left, right) {
|
|
8452
|
+
if (left === right) {
|
|
8453
|
+
return true;
|
|
8454
|
+
}
|
|
8455
|
+
if (left === void 0 || right === void 0) {
|
|
8456
|
+
return false;
|
|
8457
|
+
}
|
|
8458
|
+
const leftKeys = Object.keys(left);
|
|
8459
|
+
const rightKeys = Object.keys(right);
|
|
8460
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
8461
|
+
return false;
|
|
8462
|
+
}
|
|
8463
|
+
for (const key of leftKeys) {
|
|
8464
|
+
if (left[key] !== right[key]) {
|
|
8465
|
+
return false;
|
|
8466
|
+
}
|
|
8467
|
+
}
|
|
8468
|
+
return true;
|
|
8469
|
+
}
|
|
8470
|
+
function cloneAttributes(attributes) {
|
|
8471
|
+
return attributes === void 0 ? void 0 : freeze({ ...attributes });
|
|
8472
|
+
}
|
|
8473
|
+
function normalizeSegments(segments) {
|
|
8474
|
+
const normalized = [];
|
|
8475
|
+
for (const segment of segments) {
|
|
8476
|
+
if (segment.text.length === 0) {
|
|
8477
|
+
continue;
|
|
8478
|
+
}
|
|
8479
|
+
const last = normalized.at(-1);
|
|
8480
|
+
const attributes = cloneAttributes(segment.attributes);
|
|
8481
|
+
if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
|
|
8482
|
+
last.text += segment.text;
|
|
8483
|
+
} else {
|
|
8484
|
+
normalized.push({ text: segment.text, attributes });
|
|
8485
|
+
}
|
|
8486
|
+
}
|
|
8487
|
+
return normalized;
|
|
8488
|
+
}
|
|
8489
|
+
function deltaToSegments(delta) {
|
|
8490
|
+
return normalizeSegments(
|
|
8491
|
+
delta.map((item) => ({
|
|
8492
|
+
text: item.text,
|
|
8493
|
+
attributes: item.attributes
|
|
8494
|
+
}))
|
|
8495
|
+
);
|
|
8496
|
+
}
|
|
8497
|
+
function segmentsToDelta(segments) {
|
|
8498
|
+
return segments.map(
|
|
8499
|
+
(segment) => segment.attributes === void 0 ? { text: segment.text } : { text: segment.text, attributes: { ...segment.attributes } }
|
|
8500
|
+
);
|
|
8501
|
+
}
|
|
8502
|
+
function textLength(segments) {
|
|
8503
|
+
return segments.reduce((sum, segment) => sum + segment.text.length, 0);
|
|
8504
|
+
}
|
|
8505
|
+
function splitSegmentsAt(segments, index) {
|
|
8506
|
+
const result = [];
|
|
8507
|
+
let offset = 0;
|
|
8508
|
+
for (const segment of segments) {
|
|
8509
|
+
const end = offset + segment.text.length;
|
|
8510
|
+
if (index > offset && index < end) {
|
|
8511
|
+
const before2 = segment.text.slice(0, index - offset);
|
|
8512
|
+
const after2 = segment.text.slice(index - offset);
|
|
8513
|
+
result.push({ text: before2, attributes: segment.attributes });
|
|
8514
|
+
result.push({ text: after2, attributes: segment.attributes });
|
|
8515
|
+
} else {
|
|
8516
|
+
result.push({ text: segment.text, attributes: segment.attributes });
|
|
8517
|
+
}
|
|
8518
|
+
offset = end;
|
|
8519
|
+
}
|
|
8520
|
+
return result;
|
|
8521
|
+
}
|
|
8522
|
+
function clipRange(index, length, contentLength) {
|
|
8523
|
+
const clippedIndex = Math.max(0, Math.min(index, contentLength));
|
|
8524
|
+
const clippedEnd = Math.max(
|
|
8525
|
+
clippedIndex,
|
|
8526
|
+
Math.min(index + length, contentLength)
|
|
8527
|
+
);
|
|
8528
|
+
return { index: clippedIndex, length: clippedEnd - clippedIndex };
|
|
8529
|
+
}
|
|
8530
|
+
function applyInsert(segments, index, text, attributes) {
|
|
8531
|
+
if (text.length === 0) {
|
|
8532
|
+
return normalizeSegments(segments);
|
|
8533
|
+
}
|
|
8534
|
+
const split = splitSegmentsAt(segments, index);
|
|
8535
|
+
const result = [];
|
|
8536
|
+
let offset = 0;
|
|
8537
|
+
let inserted = false;
|
|
8538
|
+
for (const segment of split) {
|
|
8539
|
+
if (!inserted && offset === index) {
|
|
8540
|
+
result.push({ text, attributes });
|
|
8541
|
+
inserted = true;
|
|
8542
|
+
}
|
|
8543
|
+
result.push(segment);
|
|
8544
|
+
offset += segment.text.length;
|
|
8545
|
+
}
|
|
8546
|
+
if (!inserted) {
|
|
8547
|
+
result.push({ text, attributes });
|
|
8548
|
+
}
|
|
8549
|
+
return normalizeSegments(result);
|
|
8550
|
+
}
|
|
8551
|
+
function extractDeletedSegments(segments, index, length) {
|
|
8552
|
+
const split = splitSegmentsAt(
|
|
8553
|
+
splitSegmentsAt(segments, index),
|
|
8554
|
+
index + length
|
|
8555
|
+
);
|
|
8556
|
+
const deleted = [];
|
|
8557
|
+
let offset = 0;
|
|
8558
|
+
for (const segment of split) {
|
|
8559
|
+
const end = offset + segment.text.length;
|
|
8560
|
+
if (offset >= index && end <= index + length) {
|
|
8561
|
+
deleted.push({
|
|
8562
|
+
text: segment.text,
|
|
8563
|
+
attributes: segment.attributes
|
|
8564
|
+
});
|
|
8565
|
+
}
|
|
8566
|
+
offset = end;
|
|
8567
|
+
}
|
|
8568
|
+
return normalizeSegments(deleted);
|
|
8569
|
+
}
|
|
8570
|
+
function applyDelete(segments, index, length) {
|
|
8571
|
+
const deletedSegments = extractDeletedSegments(segments, index, length);
|
|
8572
|
+
const split = splitSegmentsAt(
|
|
8573
|
+
splitSegmentsAt(segments, index),
|
|
8574
|
+
index + length
|
|
8575
|
+
);
|
|
8576
|
+
const result = [];
|
|
8577
|
+
let offset = 0;
|
|
8578
|
+
let deletedText = "";
|
|
8579
|
+
for (const segment of split) {
|
|
8580
|
+
const end = offset + segment.text.length;
|
|
8581
|
+
if (offset >= index && end <= index + length) {
|
|
8582
|
+
deletedText += segment.text;
|
|
8583
|
+
} else {
|
|
8584
|
+
result.push(segment);
|
|
8585
|
+
}
|
|
8586
|
+
offset = end;
|
|
8587
|
+
}
|
|
8588
|
+
return {
|
|
8589
|
+
segments: normalizeSegments(result),
|
|
8590
|
+
deletedText,
|
|
8591
|
+
deletedSegments
|
|
8592
|
+
};
|
|
8593
|
+
}
|
|
8594
|
+
function applyFormat(segments, index, length, attributes) {
|
|
8595
|
+
const split = splitSegmentsAt(
|
|
8596
|
+
splitSegmentsAt(segments, index),
|
|
8597
|
+
index + length
|
|
8598
|
+
);
|
|
8599
|
+
const result = [];
|
|
8600
|
+
let offset = 0;
|
|
8601
|
+
for (const segment of split) {
|
|
8602
|
+
const end = offset + segment.text.length;
|
|
8603
|
+
if (offset >= index && end <= index + length) {
|
|
8604
|
+
const nextAttributes = {
|
|
8605
|
+
...segment.attributes ?? {}
|
|
8606
|
+
};
|
|
8607
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
8608
|
+
if (value === null) {
|
|
8609
|
+
delete nextAttributes[key];
|
|
8610
|
+
} else {
|
|
8611
|
+
nextAttributes[key] = value;
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
result.push({
|
|
8615
|
+
text: segment.text,
|
|
8616
|
+
attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
|
|
8617
|
+
});
|
|
8618
|
+
} else {
|
|
8619
|
+
result.push(segment);
|
|
8620
|
+
}
|
|
8621
|
+
offset = end;
|
|
8622
|
+
}
|
|
8623
|
+
return normalizeSegments(result);
|
|
8624
|
+
}
|
|
8625
|
+
function formatReverseOperations(segments, index, length, patch) {
|
|
8626
|
+
const split = splitSegmentsAt(
|
|
8627
|
+
splitSegmentsAt(segments, index),
|
|
8628
|
+
index + length
|
|
8629
|
+
);
|
|
8630
|
+
const result = [];
|
|
8631
|
+
let offset = 0;
|
|
8632
|
+
for (const segment of split) {
|
|
8633
|
+
const end = offset + segment.text.length;
|
|
8634
|
+
if (offset >= index && end <= index + length) {
|
|
8635
|
+
const attributes = {};
|
|
8636
|
+
for (const key of Object.keys(patch)) {
|
|
8637
|
+
attributes[key] = segment.attributes?.[key] ?? null;
|
|
8638
|
+
}
|
|
8639
|
+
result.push({
|
|
8640
|
+
type: "format",
|
|
8641
|
+
index: offset,
|
|
8642
|
+
length: segment.text.length,
|
|
8643
|
+
attributes
|
|
8644
|
+
});
|
|
8645
|
+
}
|
|
8646
|
+
offset = end;
|
|
8647
|
+
}
|
|
8648
|
+
return result;
|
|
8649
|
+
}
|
|
8650
|
+
function mapIndexThroughOperation(index, op) {
|
|
8651
|
+
if (op.type === "insert") {
|
|
8652
|
+
return op.index <= index ? index + op.text.length : index;
|
|
8653
|
+
} else if (op.type === "delete") {
|
|
8654
|
+
if (op.index >= index) {
|
|
8655
|
+
return index;
|
|
8656
|
+
}
|
|
8657
|
+
return Math.max(op.index, index - op.length);
|
|
8658
|
+
} else {
|
|
8659
|
+
return index;
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
function mapTextIndexThroughOperations(index, ops) {
|
|
8663
|
+
let mapped = index;
|
|
8664
|
+
for (const op of ops) {
|
|
8665
|
+
mapped = mapIndexThroughOperation(mapped, op);
|
|
8666
|
+
}
|
|
8667
|
+
return mapped;
|
|
8668
|
+
}
|
|
8669
|
+
function rebaseTextOperations(ops, acceptedOps) {
|
|
8670
|
+
return ops.map((op) => {
|
|
8671
|
+
if (op.type === "insert") {
|
|
8672
|
+
return {
|
|
8673
|
+
...op,
|
|
8674
|
+
index: mapTextIndexThroughOperations(op.index, acceptedOps)
|
|
8675
|
+
};
|
|
8676
|
+
} else if (op.type === "delete" || op.type === "format") {
|
|
8677
|
+
const start = mapTextIndexThroughOperations(op.index, acceptedOps);
|
|
8678
|
+
const end = mapTextIndexThroughOperations(
|
|
8679
|
+
op.index + op.length,
|
|
8680
|
+
acceptedOps
|
|
8681
|
+
);
|
|
8682
|
+
return { ...op, index: start, length: Math.max(0, end - start) };
|
|
8683
|
+
} else {
|
|
8684
|
+
return op;
|
|
8685
|
+
}
|
|
8686
|
+
});
|
|
8687
|
+
}
|
|
8688
|
+
function applyTextOperationsToSegments(segments, ops) {
|
|
8689
|
+
let next = [...segments];
|
|
8690
|
+
for (const op of ops) {
|
|
8691
|
+
if (op.type === "insert") {
|
|
8692
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
8693
|
+
next = applyInsert(next, index, op.text, op.attributes);
|
|
8694
|
+
} else if (op.type === "delete") {
|
|
8695
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
8696
|
+
const clipped = clipRange(index, op.length, textLength(next));
|
|
8697
|
+
next = applyDelete(next, clipped.index, clipped.length).segments;
|
|
8698
|
+
} else {
|
|
8699
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
8700
|
+
const clipped = clipRange(index, op.length, textLength(next));
|
|
8701
|
+
next = applyFormat(next, clipped.index, clipped.length, op.attributes);
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8704
|
+
return next;
|
|
8705
|
+
}
|
|
8706
|
+
function applyLiveTextOperations(delta, ops) {
|
|
8707
|
+
return segmentsToDelta(applyTextOperationsToSegments(deltaToSegments(delta), ops));
|
|
8708
|
+
}
|
|
8709
|
+
function invertTextOperations(segments, ops) {
|
|
8710
|
+
let shadow = [...segments];
|
|
8711
|
+
const reverse = [];
|
|
8712
|
+
for (const op of ops) {
|
|
8713
|
+
if (op.type === "insert") {
|
|
8714
|
+
shadow = applyInsert(shadow, op.index, op.text, op.attributes);
|
|
8715
|
+
reverse.unshift({
|
|
8716
|
+
type: "delete",
|
|
8717
|
+
index: op.index,
|
|
8718
|
+
length: op.text.length
|
|
8719
|
+
});
|
|
8720
|
+
} else if (op.type === "delete") {
|
|
8721
|
+
const deletedSegments = extractDeletedSegments(
|
|
8722
|
+
shadow,
|
|
8723
|
+
op.index,
|
|
8724
|
+
op.length
|
|
8725
|
+
);
|
|
8726
|
+
shadow = applyDelete(shadow, op.index, op.length).segments;
|
|
8727
|
+
const inserts = [];
|
|
8728
|
+
let insertIndex = op.index;
|
|
8729
|
+
for (const segment of deletedSegments) {
|
|
8730
|
+
inserts.push({
|
|
8731
|
+
type: "insert",
|
|
8732
|
+
index: insertIndex,
|
|
8733
|
+
text: segment.text,
|
|
8734
|
+
attributes: segment.attributes
|
|
8735
|
+
});
|
|
8736
|
+
insertIndex += segment.text.length;
|
|
8737
|
+
}
|
|
8738
|
+
for (let index = inserts.length - 1; index >= 0; index--) {
|
|
8739
|
+
reverse.unshift(inserts[index]);
|
|
8740
|
+
}
|
|
8741
|
+
} else {
|
|
8742
|
+
const inverse = formatReverseOperations(
|
|
8743
|
+
shadow,
|
|
8744
|
+
op.index,
|
|
8745
|
+
op.length,
|
|
8746
|
+
op.attributes
|
|
8747
|
+
);
|
|
8748
|
+
shadow = applyFormat(shadow, op.index, op.length, op.attributes);
|
|
8749
|
+
reverse.unshift(...inverse.reverse());
|
|
8750
|
+
}
|
|
8751
|
+
}
|
|
8752
|
+
return reverse;
|
|
8753
|
+
}
|
|
8754
|
+
|
|
8755
|
+
// src/crdts/LiveText.ts
|
|
8756
|
+
var LiveText = class _LiveText extends AbstractCrdt {
|
|
8757
|
+
#segments;
|
|
8758
|
+
#version;
|
|
8759
|
+
#pendingOps;
|
|
8760
|
+
constructor(textOrDelta = "", version = 0) {
|
|
8761
|
+
super();
|
|
8762
|
+
this.#segments = typeof textOrDelta === "string" ? textOrDelta.length === 0 ? [] : [{ text: textOrDelta }] : deltaToSegments(textOrDelta);
|
|
8763
|
+
this.#version = version;
|
|
8764
|
+
this.#pendingOps = /* @__PURE__ */ new Map();
|
|
8765
|
+
}
|
|
8766
|
+
get version() {
|
|
8767
|
+
return this.#version;
|
|
8768
|
+
}
|
|
8769
|
+
get length() {
|
|
8770
|
+
return this.toString().length;
|
|
8771
|
+
}
|
|
8772
|
+
/** @internal */
|
|
8773
|
+
static _deserialize([id, item], _parentToChildren, pool) {
|
|
8774
|
+
const text = new _LiveText(item.data, item.version);
|
|
8775
|
+
text._attach(id, pool);
|
|
8776
|
+
return text;
|
|
8777
|
+
}
|
|
8778
|
+
/** @internal */
|
|
8779
|
+
_toOps(parentId, parentKey) {
|
|
8780
|
+
if (this._id === void 0) {
|
|
8781
|
+
throw new Error("Cannot serialize LiveText if it is not attached");
|
|
8782
|
+
}
|
|
8783
|
+
return [
|
|
8784
|
+
{
|
|
8785
|
+
type: OpCode.CREATE_TEXT,
|
|
8786
|
+
id: this._id,
|
|
8787
|
+
parentId,
|
|
8788
|
+
parentKey,
|
|
8789
|
+
data: this.toDelta(),
|
|
8790
|
+
version: this.#version
|
|
8791
|
+
}
|
|
8792
|
+
];
|
|
8793
|
+
}
|
|
8794
|
+
/** @internal */
|
|
8795
|
+
_serialize() {
|
|
8796
|
+
if (this.parent.type !== "HasParent") {
|
|
8797
|
+
throw new Error("Cannot serialize LiveText if parent is missing");
|
|
8798
|
+
}
|
|
8799
|
+
return {
|
|
8800
|
+
type: CrdtType.TEXT,
|
|
8801
|
+
parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
|
|
8802
|
+
parentKey: this.parent.key,
|
|
8803
|
+
data: this.toDelta(),
|
|
8804
|
+
version: this.#version
|
|
8805
|
+
};
|
|
8806
|
+
}
|
|
8807
|
+
/** @internal */
|
|
8808
|
+
_attachChild(_op) {
|
|
8809
|
+
throw new Error("LiveText cannot contain child nodes");
|
|
8810
|
+
}
|
|
8811
|
+
/** @internal */
|
|
8812
|
+
_detachChild(_crdt) {
|
|
8813
|
+
throw new Error("LiveText cannot contain child nodes");
|
|
8814
|
+
}
|
|
8815
|
+
/** @internal */
|
|
8816
|
+
_apply(op, isLocal) {
|
|
8817
|
+
if (op.type !== OpCode.UPDATE_TEXT) {
|
|
8818
|
+
return super._apply(op, isLocal);
|
|
8819
|
+
}
|
|
8820
|
+
if (isLocal) {
|
|
8821
|
+
this.#pendingOps.set(nn(op.opId), op.ops);
|
|
8822
|
+
return this.#applyOperations(op.ops, op.version ?? this.#version);
|
|
8823
|
+
}
|
|
8824
|
+
if (op.opId !== void 0) {
|
|
8825
|
+
const pending2 = this.#pendingOps.get(op.opId);
|
|
8826
|
+
this.#pendingOps.delete(op.opId);
|
|
8827
|
+
const otherPending = Array.from(this.#pendingOps.values()).flat();
|
|
8828
|
+
if (pending2 !== void 0 && otherPending.length > 0) {
|
|
8829
|
+
this.#segments = applyTextOperationsToSegments(
|
|
8830
|
+
this.#segments,
|
|
8831
|
+
invertTextOperations(this.#segments, pending2)
|
|
8832
|
+
);
|
|
8833
|
+
const ops2 = rebaseTextOperations(op.ops, otherPending);
|
|
8834
|
+
return this.#applyOperations(
|
|
8835
|
+
ops2,
|
|
8836
|
+
op.version ?? Math.max(this.#version, op.baseVersion + 1)
|
|
8837
|
+
);
|
|
8838
|
+
}
|
|
8839
|
+
this.#version = op.version ?? Math.max(this.#version, op.baseVersion + 1);
|
|
8840
|
+
return { modified: false };
|
|
8841
|
+
}
|
|
8842
|
+
const pending = Array.from(this.#pendingOps.values()).flat();
|
|
8843
|
+
const ops = pending.length > 0 ? rebaseTextOperations(op.ops, pending) : op.ops;
|
|
8844
|
+
return this.#applyOperations(ops, op.version ?? this.#version + 1);
|
|
8845
|
+
}
|
|
8846
|
+
insert(index, text, attributes) {
|
|
8847
|
+
const clippedIndex = Math.max(0, Math.min(index, this.length));
|
|
8848
|
+
this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
|
|
8849
|
+
}
|
|
8850
|
+
delete(index, length) {
|
|
8851
|
+
const clipped = clipRange(index, length, this.length);
|
|
8852
|
+
if (clipped.length === 0) {
|
|
8853
|
+
return;
|
|
8854
|
+
}
|
|
8855
|
+
this.#dispatch([
|
|
8856
|
+
{ type: "delete", index: clipped.index, length: clipped.length }
|
|
8857
|
+
]);
|
|
8858
|
+
}
|
|
8859
|
+
replace(index, length, text, attributes) {
|
|
8860
|
+
const clipped = clipRange(index, length, this.length);
|
|
8861
|
+
const ops = [];
|
|
8862
|
+
if (clipped.length > 0) {
|
|
8863
|
+
ops.push({
|
|
8864
|
+
type: "delete",
|
|
8865
|
+
index: clipped.index,
|
|
8866
|
+
length: clipped.length
|
|
8867
|
+
});
|
|
8868
|
+
}
|
|
8869
|
+
if (text.length > 0) {
|
|
8870
|
+
ops.push({ type: "insert", index: clipped.index, text, attributes });
|
|
8871
|
+
}
|
|
8872
|
+
this.#dispatch(ops);
|
|
8873
|
+
}
|
|
8874
|
+
format(index, length, attributes) {
|
|
8875
|
+
const clipped = clipRange(index, length, this.length);
|
|
8876
|
+
if (clipped.length === 0) {
|
|
8877
|
+
return;
|
|
8878
|
+
}
|
|
8879
|
+
this.#dispatch([
|
|
8880
|
+
{
|
|
8881
|
+
type: "format",
|
|
8882
|
+
index: clipped.index,
|
|
8883
|
+
length: clipped.length,
|
|
8884
|
+
attributes
|
|
8885
|
+
}
|
|
8886
|
+
]);
|
|
8887
|
+
}
|
|
8888
|
+
#dispatch(ops) {
|
|
8889
|
+
if (ops.length === 0) {
|
|
8890
|
+
return;
|
|
8891
|
+
}
|
|
8892
|
+
this._pool?.assertStorageIsWritable();
|
|
8893
|
+
const baseVersion = this.#version;
|
|
8894
|
+
const reverse = this._pool !== void 0 && this._id !== void 0 ? this.#invertOperations(ops) : [];
|
|
8895
|
+
const changes = this.#applyOperationsLocally(ops);
|
|
8896
|
+
if (this._pool !== void 0 && this._id !== void 0) {
|
|
8897
|
+
const opId = this._pool.generateOpId();
|
|
8898
|
+
this.#pendingOps.set(opId, ops);
|
|
8899
|
+
this._pool.dispatch(
|
|
8900
|
+
[
|
|
8901
|
+
{
|
|
8902
|
+
type: OpCode.UPDATE_TEXT,
|
|
8903
|
+
id: this._id,
|
|
8904
|
+
opId,
|
|
8905
|
+
baseVersion,
|
|
8906
|
+
ops: [...ops]
|
|
8907
|
+
}
|
|
8908
|
+
],
|
|
8909
|
+
reverse,
|
|
8910
|
+
/* @__PURE__ */ new Map([
|
|
8911
|
+
[
|
|
8912
|
+
this._id,
|
|
8913
|
+
{
|
|
8914
|
+
type: "LiveText",
|
|
8915
|
+
node: this,
|
|
8916
|
+
version: this.#version,
|
|
8917
|
+
updates: changes
|
|
8918
|
+
}
|
|
8919
|
+
]
|
|
8920
|
+
])
|
|
8921
|
+
);
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
#applyOperations(ops, version) {
|
|
8925
|
+
const reverse = this.#invertOperations(ops);
|
|
8926
|
+
const changes = this.#applyOperationsLocally(ops);
|
|
8927
|
+
this.#version = Math.max(this.#version, version);
|
|
8928
|
+
return {
|
|
8929
|
+
reverse,
|
|
8930
|
+
modified: {
|
|
8931
|
+
type: "LiveText",
|
|
8932
|
+
node: this,
|
|
8933
|
+
version: this.#version,
|
|
8934
|
+
updates: changes
|
|
8935
|
+
}
|
|
8936
|
+
};
|
|
8937
|
+
}
|
|
8938
|
+
#applyOperationsLocally(ops) {
|
|
8939
|
+
const changes = [];
|
|
8940
|
+
for (const op of ops) {
|
|
8941
|
+
if (op.type === "insert") {
|
|
8942
|
+
this.#segments = applyInsert(
|
|
8943
|
+
this.#segments,
|
|
8944
|
+
op.index,
|
|
8945
|
+
op.text,
|
|
8946
|
+
op.attributes
|
|
8947
|
+
);
|
|
8948
|
+
changes.push({
|
|
8949
|
+
type: "insert",
|
|
8950
|
+
index: op.index,
|
|
8951
|
+
text: op.text,
|
|
8952
|
+
attributes: op.attributes
|
|
8953
|
+
});
|
|
8954
|
+
} else if (op.type === "delete") {
|
|
8955
|
+
const result = applyDelete(this.#segments, op.index, op.length);
|
|
8956
|
+
this.#segments = result.segments;
|
|
8957
|
+
changes.push({
|
|
8958
|
+
type: "delete",
|
|
8959
|
+
index: op.index,
|
|
8960
|
+
length: op.length,
|
|
8961
|
+
deletedText: result.deletedText
|
|
8962
|
+
});
|
|
8963
|
+
} else {
|
|
8964
|
+
this.#segments = applyFormat(
|
|
8965
|
+
this.#segments,
|
|
8966
|
+
op.index,
|
|
8967
|
+
op.length,
|
|
8968
|
+
op.attributes
|
|
8969
|
+
);
|
|
8970
|
+
changes.push({
|
|
8971
|
+
type: "format",
|
|
8972
|
+
index: op.index,
|
|
8973
|
+
length: op.length,
|
|
8974
|
+
attributes: op.attributes
|
|
8975
|
+
});
|
|
8976
|
+
}
|
|
8977
|
+
}
|
|
8978
|
+
this.invalidate();
|
|
8979
|
+
return changes;
|
|
8980
|
+
}
|
|
8981
|
+
#invertOperations(ops) {
|
|
8982
|
+
return [
|
|
8983
|
+
{
|
|
8984
|
+
type: OpCode.UPDATE_TEXT,
|
|
8985
|
+
id: nn(this._id),
|
|
8986
|
+
baseVersion: this.#version,
|
|
8987
|
+
ops: invertTextOperations(this.#segments, ops)
|
|
8988
|
+
}
|
|
8989
|
+
];
|
|
8990
|
+
}
|
|
8991
|
+
toString() {
|
|
8992
|
+
return this.#segments.map((segment) => segment.text).join("");
|
|
8993
|
+
}
|
|
8994
|
+
toDelta() {
|
|
8995
|
+
return segmentsToDelta(this.#segments);
|
|
8996
|
+
}
|
|
8997
|
+
toJSON() {
|
|
8998
|
+
return super.toJSON();
|
|
8999
|
+
}
|
|
9000
|
+
/** @internal */
|
|
9001
|
+
_toJSON() {
|
|
9002
|
+
return this.toDelta();
|
|
9003
|
+
}
|
|
9004
|
+
/** @internal */
|
|
9005
|
+
_toTreeNode(key) {
|
|
9006
|
+
return {
|
|
9007
|
+
type: "LiveText",
|
|
9008
|
+
id: this._id ?? nanoid(),
|
|
9009
|
+
key,
|
|
9010
|
+
payload: [
|
|
9011
|
+
{
|
|
9012
|
+
type: "Json",
|
|
9013
|
+
id: `${this._id ?? nanoid()}:text`,
|
|
9014
|
+
key: "text",
|
|
9015
|
+
payload: this.toString()
|
|
9016
|
+
}
|
|
9017
|
+
]
|
|
9018
|
+
};
|
|
9019
|
+
}
|
|
9020
|
+
clone() {
|
|
9021
|
+
return new _LiveText(this.toDelta(), this.#version);
|
|
9022
|
+
}
|
|
9023
|
+
};
|
|
9024
|
+
|
|
8430
9025
|
// src/crdts/liveblocks-helpers.ts
|
|
8431
9026
|
function creationOpToLiveNode(op) {
|
|
8432
9027
|
return lsonToLiveNode(creationOpToLson(op));
|
|
@@ -8441,6 +9036,8 @@ function creationOpToLson(op) {
|
|
|
8441
9036
|
return new LiveMap();
|
|
8442
9037
|
case OpCode.CREATE_LIST:
|
|
8443
9038
|
return new LiveList([]);
|
|
9039
|
+
case OpCode.CREATE_TEXT:
|
|
9040
|
+
return new LiveText(op.data, op.version);
|
|
8444
9041
|
default:
|
|
8445
9042
|
return assertNever(op, "Unknown creation Op");
|
|
8446
9043
|
}
|
|
@@ -8463,6 +9060,8 @@ function deserialize(node, parentToChildren, pool) {
|
|
|
8463
9060
|
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
8464
9061
|
} else if (isRegisterStorageNode(node)) {
|
|
8465
9062
|
return LiveRegister._deserialize(node, parentToChildren, pool);
|
|
9063
|
+
} else if (isTextStorageNode(node)) {
|
|
9064
|
+
return LiveText._deserialize(node, parentToChildren, pool);
|
|
8466
9065
|
} else {
|
|
8467
9066
|
throw new Error("Unexpected CRDT type");
|
|
8468
9067
|
}
|
|
@@ -8476,12 +9075,14 @@ function deserializeToLson(node, parentToChildren, pool) {
|
|
|
8476
9075
|
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
8477
9076
|
} else if (isRegisterStorageNode(node)) {
|
|
8478
9077
|
return node[1].data;
|
|
9078
|
+
} else if (isTextStorageNode(node)) {
|
|
9079
|
+
return LiveText._deserialize(node, parentToChildren, pool);
|
|
8479
9080
|
} else {
|
|
8480
9081
|
throw new Error("Unexpected CRDT type");
|
|
8481
9082
|
}
|
|
8482
9083
|
}
|
|
8483
9084
|
function isLiveStructure(value) {
|
|
8484
|
-
return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
|
|
9085
|
+
return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value);
|
|
8485
9086
|
}
|
|
8486
9087
|
function isLiveNode(value) {
|
|
8487
9088
|
return isLiveStructure(value) || isLiveRegister(value);
|
|
@@ -8495,6 +9096,9 @@ function isLiveMap(value) {
|
|
|
8495
9096
|
function isLiveObject(value) {
|
|
8496
9097
|
return value instanceof LiveObject;
|
|
8497
9098
|
}
|
|
9099
|
+
function isLiveText(value) {
|
|
9100
|
+
return value instanceof LiveText;
|
|
9101
|
+
}
|
|
8498
9102
|
function isLiveRegister(value) {
|
|
8499
9103
|
return value instanceof LiveRegister;
|
|
8500
9104
|
}
|
|
@@ -8504,14 +9108,14 @@ function cloneLson(value) {
|
|
|
8504
9108
|
function liveNodeToLson(obj) {
|
|
8505
9109
|
if (obj instanceof LiveRegister) {
|
|
8506
9110
|
return obj.data;
|
|
8507
|
-
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
|
|
9111
|
+
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText) {
|
|
8508
9112
|
return obj;
|
|
8509
9113
|
} else {
|
|
8510
9114
|
return assertNever(obj, "Unknown AbstractCrdt");
|
|
8511
9115
|
}
|
|
8512
9116
|
}
|
|
8513
9117
|
function lsonToLiveNode(value) {
|
|
8514
|
-
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
|
|
9118
|
+
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText) {
|
|
8515
9119
|
return value;
|
|
8516
9120
|
} else {
|
|
8517
9121
|
return new LiveRegister(value);
|
|
@@ -8606,6 +9210,38 @@ function getTreesDiffOperations(currentItems, newItems) {
|
|
|
8606
9210
|
}
|
|
8607
9211
|
}
|
|
8608
9212
|
}
|
|
9213
|
+
if (crdt.type === CrdtType.TEXT) {
|
|
9214
|
+
if (currentCrdt.type !== CrdtType.TEXT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data) || crdt.version !== currentCrdt.version) {
|
|
9215
|
+
ops.push({
|
|
9216
|
+
type: OpCode.UPDATE_TEXT,
|
|
9217
|
+
id,
|
|
9218
|
+
baseVersion: currentCrdt.type === CrdtType.TEXT ? currentCrdt.version : 0,
|
|
9219
|
+
version: crdt.version,
|
|
9220
|
+
ops: [
|
|
9221
|
+
{
|
|
9222
|
+
type: "delete",
|
|
9223
|
+
index: 0,
|
|
9224
|
+
length: currentCrdt.type === CrdtType.TEXT ? currentCrdt.data.reduce(
|
|
9225
|
+
(sum, item) => sum + item.text.length,
|
|
9226
|
+
0
|
|
9227
|
+
) : 0
|
|
9228
|
+
},
|
|
9229
|
+
...crdt.data.map(
|
|
9230
|
+
(item, index, items) => item.attributes === void 0 ? {
|
|
9231
|
+
type: "insert",
|
|
9232
|
+
index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
|
|
9233
|
+
text: item.text
|
|
9234
|
+
} : {
|
|
9235
|
+
type: "insert",
|
|
9236
|
+
index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
|
|
9237
|
+
text: item.text,
|
|
9238
|
+
attributes: item.attributes
|
|
9239
|
+
}
|
|
9240
|
+
)
|
|
9241
|
+
]
|
|
9242
|
+
});
|
|
9243
|
+
}
|
|
9244
|
+
}
|
|
8609
9245
|
if (crdt.parentKey !== currentCrdt.parentKey) {
|
|
8610
9246
|
ops.push({
|
|
8611
9247
|
type: OpCode.SET_PARENT_KEY,
|
|
@@ -8654,6 +9290,16 @@ function getTreesDiffOperations(currentItems, newItems) {
|
|
|
8654
9290
|
parentKey: crdt.parentKey
|
|
8655
9291
|
});
|
|
8656
9292
|
break;
|
|
9293
|
+
case CrdtType.TEXT:
|
|
9294
|
+
ops.push({
|
|
9295
|
+
type: OpCode.CREATE_TEXT,
|
|
9296
|
+
id,
|
|
9297
|
+
parentId: crdt.parentId,
|
|
9298
|
+
parentKey: crdt.parentKey,
|
|
9299
|
+
data: crdt.data,
|
|
9300
|
+
version: crdt.version
|
|
9301
|
+
});
|
|
9302
|
+
break;
|
|
8657
9303
|
}
|
|
8658
9304
|
}
|
|
8659
9305
|
});
|
|
@@ -8686,6 +9332,12 @@ function mergeListStorageUpdates(first, second) {
|
|
|
8686
9332
|
updates: updates.concat(second.updates)
|
|
8687
9333
|
};
|
|
8688
9334
|
}
|
|
9335
|
+
function mergeTextStorageUpdates(first, second) {
|
|
9336
|
+
return {
|
|
9337
|
+
...second,
|
|
9338
|
+
updates: first.updates.concat(second.updates)
|
|
9339
|
+
};
|
|
9340
|
+
}
|
|
8689
9341
|
function mergeStorageUpdates(first, second) {
|
|
8690
9342
|
if (first === void 0) {
|
|
8691
9343
|
return second;
|
|
@@ -8696,6 +9348,8 @@ function mergeStorageUpdates(first, second) {
|
|
|
8696
9348
|
return mergeMapStorageUpdates(first, second);
|
|
8697
9349
|
} else if (first.type === "LiveList" && second.type === "LiveList") {
|
|
8698
9350
|
return mergeListStorageUpdates(first, second);
|
|
9351
|
+
} else if (first.type === "LiveText" && second.type === "LiveText") {
|
|
9352
|
+
return mergeTextStorageUpdates(first, second);
|
|
8699
9353
|
} else {
|
|
8700
9354
|
}
|
|
8701
9355
|
return second;
|
|
@@ -9984,7 +10638,7 @@ function createRoom(options, config) {
|
|
|
9984
10638
|
);
|
|
9985
10639
|
output.reverse.pushLeft(applyOpResult.reverse);
|
|
9986
10640
|
}
|
|
9987
|
-
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
|
|
10641
|
+
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT) {
|
|
9988
10642
|
createdNodeIds.add(op.id);
|
|
9989
10643
|
}
|
|
9990
10644
|
}
|
|
@@ -10004,6 +10658,7 @@ function createRoom(options, config) {
|
|
|
10004
10658
|
switch (op.type) {
|
|
10005
10659
|
case OpCode.DELETE_OBJECT_KEY:
|
|
10006
10660
|
case OpCode.UPDATE_OBJECT:
|
|
10661
|
+
case OpCode.UPDATE_TEXT:
|
|
10007
10662
|
case OpCode.DELETE_CRDT: {
|
|
10008
10663
|
const node = context.pool.nodes.get(op.id);
|
|
10009
10664
|
if (node === void 0) {
|
|
@@ -10028,6 +10683,7 @@ function createRoom(options, config) {
|
|
|
10028
10683
|
case OpCode.CREATE_OBJECT:
|
|
10029
10684
|
case OpCode.CREATE_LIST:
|
|
10030
10685
|
case OpCode.CREATE_MAP:
|
|
10686
|
+
case OpCode.CREATE_TEXT:
|
|
10031
10687
|
case OpCode.CREATE_REGISTER: {
|
|
10032
10688
|
if (op.parentId === void 0) {
|
|
10033
10689
|
return { modified: false };
|
|
@@ -12187,6 +12843,12 @@ function toPlainLson(lson) {
|
|
|
12187
12843
|
liveblocksType: "LiveList",
|
|
12188
12844
|
data: [...lson].map((item) => toPlainLson(item))
|
|
12189
12845
|
};
|
|
12846
|
+
} else if (lson instanceof LiveText) {
|
|
12847
|
+
return {
|
|
12848
|
+
liveblocksType: "LiveText",
|
|
12849
|
+
data: lson.toDelta(),
|
|
12850
|
+
version: lson.version
|
|
12851
|
+
};
|
|
12190
12852
|
} else {
|
|
12191
12853
|
return lson;
|
|
12192
12854
|
}
|
|
@@ -12368,6 +13030,7 @@ export {
|
|
|
12368
13030
|
LiveList,
|
|
12369
13031
|
LiveMap,
|
|
12370
13032
|
LiveObject,
|
|
13033
|
+
LiveText,
|
|
12371
13034
|
LiveblocksError,
|
|
12372
13035
|
MENTION_CHARACTER,
|
|
12373
13036
|
MutableSignal,
|
|
@@ -12379,6 +13042,7 @@ export {
|
|
|
12379
13042
|
SortedList,
|
|
12380
13043
|
TextEditorType,
|
|
12381
13044
|
WebsocketCloseCodes,
|
|
13045
|
+
applyLiveTextOperations,
|
|
12382
13046
|
asPos,
|
|
12383
13047
|
assert,
|
|
12384
13048
|
assertNever,
|
|
@@ -12405,7 +13069,6 @@ export {
|
|
|
12405
13069
|
createManagedPool,
|
|
12406
13070
|
createNotificationSettings,
|
|
12407
13071
|
createThreadId,
|
|
12408
|
-
deepLiveify,
|
|
12409
13072
|
defineAiTool,
|
|
12410
13073
|
deprecate,
|
|
12411
13074
|
deprecateIf,
|
|
@@ -12435,6 +13098,7 @@ export {
|
|
|
12435
13098
|
isRegisterStorageNode,
|
|
12436
13099
|
isRootStorageNode,
|
|
12437
13100
|
isStartsWithOperator,
|
|
13101
|
+
isTextStorageNode,
|
|
12438
13102
|
isUrl,
|
|
12439
13103
|
kInternal,
|
|
12440
13104
|
keys,
|