@liveblocks/core 3.19.2 → 3.19.4-test1
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 +742 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -10
- package/dist/index.d.ts +122 -10
- package/dist/index.js +672 -7
- 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.19.
|
|
9
|
+
var PKG_VERSION = "3.19.4-test1";
|
|
10
10
|
var PKG_FORMAT = "esm";
|
|
11
11
|
|
|
12
12
|
// src/dupe-detection.ts
|
|
@@ -5473,7 +5473,9 @@ var OpCode = Object.freeze({
|
|
|
5473
5473
|
DELETE_CRDT: 5,
|
|
5474
5474
|
DELETE_OBJECT_KEY: 6,
|
|
5475
5475
|
CREATE_MAP: 7,
|
|
5476
|
-
CREATE_REGISTER: 8
|
|
5476
|
+
CREATE_REGISTER: 8,
|
|
5477
|
+
CREATE_TEXT: 9,
|
|
5478
|
+
UPDATE_TEXT: 10
|
|
5477
5479
|
});
|
|
5478
5480
|
function isIgnoredOp(op) {
|
|
5479
5481
|
return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
|
|
@@ -5484,7 +5486,8 @@ var CrdtType = Object.freeze({
|
|
|
5484
5486
|
OBJECT: 0,
|
|
5485
5487
|
LIST: 1,
|
|
5486
5488
|
MAP: 2,
|
|
5487
|
-
REGISTER: 3
|
|
5489
|
+
REGISTER: 3,
|
|
5490
|
+
TEXT: 4
|
|
5488
5491
|
});
|
|
5489
5492
|
function isRootStorageNode(node) {
|
|
5490
5493
|
return node[0] === "root";
|
|
@@ -5501,6 +5504,9 @@ function isMapStorageNode(node) {
|
|
|
5501
5504
|
function isRegisterStorageNode(node) {
|
|
5502
5505
|
return node[1].type === CrdtType.REGISTER;
|
|
5503
5506
|
}
|
|
5507
|
+
function isTextStorageNode(node) {
|
|
5508
|
+
return node[1].type === CrdtType.TEXT;
|
|
5509
|
+
}
|
|
5504
5510
|
function isCompactRootNode(node) {
|
|
5505
5511
|
return node[0] === "root";
|
|
5506
5512
|
}
|
|
@@ -5523,6 +5529,9 @@ function* compactNodesToNodeStream(compactNodes) {
|
|
|
5523
5529
|
case CrdtType.REGISTER:
|
|
5524
5530
|
yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
|
|
5525
5531
|
break;
|
|
5532
|
+
case CrdtType.TEXT:
|
|
5533
|
+
yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
|
|
5534
|
+
break;
|
|
5526
5535
|
default:
|
|
5527
5536
|
}
|
|
5528
5537
|
}
|
|
@@ -5551,6 +5560,17 @@ function* nodeStreamToCompactNodes(nodes) {
|
|
|
5551
5560
|
const id = node[0];
|
|
5552
5561
|
const crdt = node[1];
|
|
5553
5562
|
yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
|
|
5563
|
+
} else if (isTextStorageNode(node)) {
|
|
5564
|
+
const id = node[0];
|
|
5565
|
+
const crdt = node[1];
|
|
5566
|
+
yield [
|
|
5567
|
+
id,
|
|
5568
|
+
CrdtType.TEXT,
|
|
5569
|
+
crdt.parentId,
|
|
5570
|
+
crdt.parentKey,
|
|
5571
|
+
crdt.data,
|
|
5572
|
+
crdt.version
|
|
5573
|
+
];
|
|
5554
5574
|
} else {
|
|
5555
5575
|
}
|
|
5556
5576
|
}
|
|
@@ -8207,6 +8227,581 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8207
8227
|
}
|
|
8208
8228
|
};
|
|
8209
8229
|
|
|
8230
|
+
// src/crdts/liveTextOps.ts
|
|
8231
|
+
function attributesEqual(left, right) {
|
|
8232
|
+
if (left === right) {
|
|
8233
|
+
return true;
|
|
8234
|
+
}
|
|
8235
|
+
if (left === void 0 || right === void 0) {
|
|
8236
|
+
return false;
|
|
8237
|
+
}
|
|
8238
|
+
const leftKeys = Object.keys(left);
|
|
8239
|
+
const rightKeys = Object.keys(right);
|
|
8240
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
8241
|
+
return false;
|
|
8242
|
+
}
|
|
8243
|
+
for (const key of leftKeys) {
|
|
8244
|
+
if (left[key] !== right[key]) {
|
|
8245
|
+
return false;
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
return true;
|
|
8249
|
+
}
|
|
8250
|
+
function cloneAttributes(attributes) {
|
|
8251
|
+
return attributes === void 0 ? void 0 : freeze({ ...attributes });
|
|
8252
|
+
}
|
|
8253
|
+
function normalizeSegments(segments) {
|
|
8254
|
+
const normalized = [];
|
|
8255
|
+
for (const segment of segments) {
|
|
8256
|
+
if (segment.text.length === 0) {
|
|
8257
|
+
continue;
|
|
8258
|
+
}
|
|
8259
|
+
const last = normalized.at(-1);
|
|
8260
|
+
const attributes = cloneAttributes(segment.attributes);
|
|
8261
|
+
if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
|
|
8262
|
+
last.text += segment.text;
|
|
8263
|
+
} else {
|
|
8264
|
+
normalized.push({ text: segment.text, attributes });
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
return normalized;
|
|
8268
|
+
}
|
|
8269
|
+
function deltaToSegments(delta) {
|
|
8270
|
+
return normalizeSegments(
|
|
8271
|
+
delta.map((item) => ({
|
|
8272
|
+
text: item.text,
|
|
8273
|
+
attributes: item.attributes
|
|
8274
|
+
}))
|
|
8275
|
+
);
|
|
8276
|
+
}
|
|
8277
|
+
function segmentsToDelta(segments) {
|
|
8278
|
+
return segments.map(
|
|
8279
|
+
(segment) => segment.attributes === void 0 ? { text: segment.text } : { text: segment.text, attributes: { ...segment.attributes } }
|
|
8280
|
+
);
|
|
8281
|
+
}
|
|
8282
|
+
function textLength(segments) {
|
|
8283
|
+
return segments.reduce((sum, segment) => sum + segment.text.length, 0);
|
|
8284
|
+
}
|
|
8285
|
+
function splitSegmentsAt(segments, index) {
|
|
8286
|
+
const result = [];
|
|
8287
|
+
let offset = 0;
|
|
8288
|
+
for (const segment of segments) {
|
|
8289
|
+
const end = offset + segment.text.length;
|
|
8290
|
+
if (index > offset && index < end) {
|
|
8291
|
+
const before2 = segment.text.slice(0, index - offset);
|
|
8292
|
+
const after2 = segment.text.slice(index - offset);
|
|
8293
|
+
result.push({ text: before2, attributes: segment.attributes });
|
|
8294
|
+
result.push({ text: after2, attributes: segment.attributes });
|
|
8295
|
+
} else {
|
|
8296
|
+
result.push({ text: segment.text, attributes: segment.attributes });
|
|
8297
|
+
}
|
|
8298
|
+
offset = end;
|
|
8299
|
+
}
|
|
8300
|
+
return result;
|
|
8301
|
+
}
|
|
8302
|
+
function clipRange(index, length, contentLength) {
|
|
8303
|
+
const clippedIndex = Math.max(0, Math.min(index, contentLength));
|
|
8304
|
+
const clippedEnd = Math.max(
|
|
8305
|
+
clippedIndex,
|
|
8306
|
+
Math.min(index + length, contentLength)
|
|
8307
|
+
);
|
|
8308
|
+
return { index: clippedIndex, length: clippedEnd - clippedIndex };
|
|
8309
|
+
}
|
|
8310
|
+
function applyInsert(segments, index, text, attributes) {
|
|
8311
|
+
if (text.length === 0) {
|
|
8312
|
+
return normalizeSegments(segments);
|
|
8313
|
+
}
|
|
8314
|
+
const split = splitSegmentsAt(segments, index);
|
|
8315
|
+
const result = [];
|
|
8316
|
+
let offset = 0;
|
|
8317
|
+
let inserted = false;
|
|
8318
|
+
for (const segment of split) {
|
|
8319
|
+
if (!inserted && offset === index) {
|
|
8320
|
+
result.push({ text, attributes });
|
|
8321
|
+
inserted = true;
|
|
8322
|
+
}
|
|
8323
|
+
result.push(segment);
|
|
8324
|
+
offset += segment.text.length;
|
|
8325
|
+
}
|
|
8326
|
+
if (!inserted) {
|
|
8327
|
+
result.push({ text, attributes });
|
|
8328
|
+
}
|
|
8329
|
+
return normalizeSegments(result);
|
|
8330
|
+
}
|
|
8331
|
+
function extractDeletedSegments(segments, index, length) {
|
|
8332
|
+
const split = splitSegmentsAt(
|
|
8333
|
+
splitSegmentsAt(segments, index),
|
|
8334
|
+
index + length
|
|
8335
|
+
);
|
|
8336
|
+
const deleted = [];
|
|
8337
|
+
let offset = 0;
|
|
8338
|
+
for (const segment of split) {
|
|
8339
|
+
const end = offset + segment.text.length;
|
|
8340
|
+
if (offset >= index && end <= index + length) {
|
|
8341
|
+
deleted.push({
|
|
8342
|
+
text: segment.text,
|
|
8343
|
+
attributes: segment.attributes
|
|
8344
|
+
});
|
|
8345
|
+
}
|
|
8346
|
+
offset = end;
|
|
8347
|
+
}
|
|
8348
|
+
return normalizeSegments(deleted);
|
|
8349
|
+
}
|
|
8350
|
+
function applyDelete(segments, index, length) {
|
|
8351
|
+
const deletedSegments = extractDeletedSegments(segments, index, length);
|
|
8352
|
+
const split = splitSegmentsAt(
|
|
8353
|
+
splitSegmentsAt(segments, index),
|
|
8354
|
+
index + length
|
|
8355
|
+
);
|
|
8356
|
+
const result = [];
|
|
8357
|
+
let offset = 0;
|
|
8358
|
+
let deletedText = "";
|
|
8359
|
+
for (const segment of split) {
|
|
8360
|
+
const end = offset + segment.text.length;
|
|
8361
|
+
if (offset >= index && end <= index + length) {
|
|
8362
|
+
deletedText += segment.text;
|
|
8363
|
+
} else {
|
|
8364
|
+
result.push(segment);
|
|
8365
|
+
}
|
|
8366
|
+
offset = end;
|
|
8367
|
+
}
|
|
8368
|
+
return {
|
|
8369
|
+
segments: normalizeSegments(result),
|
|
8370
|
+
deletedText,
|
|
8371
|
+
deletedSegments
|
|
8372
|
+
};
|
|
8373
|
+
}
|
|
8374
|
+
function applyFormat(segments, index, length, attributes) {
|
|
8375
|
+
const split = splitSegmentsAt(
|
|
8376
|
+
splitSegmentsAt(segments, index),
|
|
8377
|
+
index + length
|
|
8378
|
+
);
|
|
8379
|
+
const result = [];
|
|
8380
|
+
let offset = 0;
|
|
8381
|
+
for (const segment of split) {
|
|
8382
|
+
const end = offset + segment.text.length;
|
|
8383
|
+
if (offset >= index && end <= index + length) {
|
|
8384
|
+
const nextAttributes = {
|
|
8385
|
+
...segment.attributes ?? {}
|
|
8386
|
+
};
|
|
8387
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
8388
|
+
if (value === null) {
|
|
8389
|
+
delete nextAttributes[key];
|
|
8390
|
+
} else {
|
|
8391
|
+
nextAttributes[key] = value;
|
|
8392
|
+
}
|
|
8393
|
+
}
|
|
8394
|
+
result.push({
|
|
8395
|
+
text: segment.text,
|
|
8396
|
+
attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
|
|
8397
|
+
});
|
|
8398
|
+
} else {
|
|
8399
|
+
result.push(segment);
|
|
8400
|
+
}
|
|
8401
|
+
offset = end;
|
|
8402
|
+
}
|
|
8403
|
+
return normalizeSegments(result);
|
|
8404
|
+
}
|
|
8405
|
+
function formatReverseOperations(segments, index, length, patch) {
|
|
8406
|
+
const split = splitSegmentsAt(
|
|
8407
|
+
splitSegmentsAt(segments, index),
|
|
8408
|
+
index + length
|
|
8409
|
+
);
|
|
8410
|
+
const result = [];
|
|
8411
|
+
let offset = 0;
|
|
8412
|
+
for (const segment of split) {
|
|
8413
|
+
const end = offset + segment.text.length;
|
|
8414
|
+
if (offset >= index && end <= index + length) {
|
|
8415
|
+
const attributes = {};
|
|
8416
|
+
for (const key of Object.keys(patch)) {
|
|
8417
|
+
attributes[key] = segment.attributes?.[key] ?? null;
|
|
8418
|
+
}
|
|
8419
|
+
result.push({
|
|
8420
|
+
type: "format",
|
|
8421
|
+
index: offset,
|
|
8422
|
+
length: segment.text.length,
|
|
8423
|
+
attributes
|
|
8424
|
+
});
|
|
8425
|
+
}
|
|
8426
|
+
offset = end;
|
|
8427
|
+
}
|
|
8428
|
+
return result;
|
|
8429
|
+
}
|
|
8430
|
+
function mapIndexThroughOperation(index, op) {
|
|
8431
|
+
if (op.type === "insert") {
|
|
8432
|
+
return op.index <= index ? index + op.text.length : index;
|
|
8433
|
+
} else if (op.type === "delete") {
|
|
8434
|
+
if (op.index >= index) {
|
|
8435
|
+
return index;
|
|
8436
|
+
}
|
|
8437
|
+
return Math.max(op.index, index - op.length);
|
|
8438
|
+
} else {
|
|
8439
|
+
return index;
|
|
8440
|
+
}
|
|
8441
|
+
}
|
|
8442
|
+
function mapTextIndexThroughOperations(index, ops) {
|
|
8443
|
+
let mapped = index;
|
|
8444
|
+
for (const op of ops) {
|
|
8445
|
+
mapped = mapIndexThroughOperation(mapped, op);
|
|
8446
|
+
}
|
|
8447
|
+
return mapped;
|
|
8448
|
+
}
|
|
8449
|
+
function rebaseTextOperations(ops, acceptedOps) {
|
|
8450
|
+
return ops.map((op) => {
|
|
8451
|
+
if (op.type === "insert") {
|
|
8452
|
+
return {
|
|
8453
|
+
...op,
|
|
8454
|
+
index: mapTextIndexThroughOperations(op.index, acceptedOps)
|
|
8455
|
+
};
|
|
8456
|
+
} else if (op.type === "delete" || op.type === "format") {
|
|
8457
|
+
const start = mapTextIndexThroughOperations(op.index, acceptedOps);
|
|
8458
|
+
const end = mapTextIndexThroughOperations(
|
|
8459
|
+
op.index + op.length,
|
|
8460
|
+
acceptedOps
|
|
8461
|
+
);
|
|
8462
|
+
return { ...op, index: start, length: Math.max(0, end - start) };
|
|
8463
|
+
} else {
|
|
8464
|
+
return op;
|
|
8465
|
+
}
|
|
8466
|
+
});
|
|
8467
|
+
}
|
|
8468
|
+
function applyTextOperationsToSegments(segments, ops) {
|
|
8469
|
+
let next = [...segments];
|
|
8470
|
+
for (const op of ops) {
|
|
8471
|
+
if (op.type === "insert") {
|
|
8472
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
8473
|
+
next = applyInsert(next, index, op.text, op.attributes);
|
|
8474
|
+
} else if (op.type === "delete") {
|
|
8475
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
8476
|
+
const clipped = clipRange(index, op.length, textLength(next));
|
|
8477
|
+
next = applyDelete(next, clipped.index, clipped.length).segments;
|
|
8478
|
+
} else {
|
|
8479
|
+
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
8480
|
+
const clipped = clipRange(index, op.length, textLength(next));
|
|
8481
|
+
next = applyFormat(next, clipped.index, clipped.length, op.attributes);
|
|
8482
|
+
}
|
|
8483
|
+
}
|
|
8484
|
+
return next;
|
|
8485
|
+
}
|
|
8486
|
+
function applyLiveTextOperations(delta, ops) {
|
|
8487
|
+
return segmentsToDelta(applyTextOperationsToSegments(deltaToSegments(delta), ops));
|
|
8488
|
+
}
|
|
8489
|
+
function invertTextOperations(segments, ops) {
|
|
8490
|
+
let shadow = [...segments];
|
|
8491
|
+
const reverse = [];
|
|
8492
|
+
for (const op of ops) {
|
|
8493
|
+
if (op.type === "insert") {
|
|
8494
|
+
shadow = applyInsert(shadow, op.index, op.text, op.attributes);
|
|
8495
|
+
reverse.unshift({
|
|
8496
|
+
type: "delete",
|
|
8497
|
+
index: op.index,
|
|
8498
|
+
length: op.text.length
|
|
8499
|
+
});
|
|
8500
|
+
} else if (op.type === "delete") {
|
|
8501
|
+
const deletedSegments = extractDeletedSegments(
|
|
8502
|
+
shadow,
|
|
8503
|
+
op.index,
|
|
8504
|
+
op.length
|
|
8505
|
+
);
|
|
8506
|
+
shadow = applyDelete(shadow, op.index, op.length).segments;
|
|
8507
|
+
const inserts = [];
|
|
8508
|
+
let insertIndex = op.index;
|
|
8509
|
+
for (const segment of deletedSegments) {
|
|
8510
|
+
inserts.push({
|
|
8511
|
+
type: "insert",
|
|
8512
|
+
index: insertIndex,
|
|
8513
|
+
text: segment.text,
|
|
8514
|
+
attributes: segment.attributes
|
|
8515
|
+
});
|
|
8516
|
+
insertIndex += segment.text.length;
|
|
8517
|
+
}
|
|
8518
|
+
for (let index = inserts.length - 1; index >= 0; index--) {
|
|
8519
|
+
reverse.unshift(inserts[index]);
|
|
8520
|
+
}
|
|
8521
|
+
} else {
|
|
8522
|
+
const inverse = formatReverseOperations(
|
|
8523
|
+
shadow,
|
|
8524
|
+
op.index,
|
|
8525
|
+
op.length,
|
|
8526
|
+
op.attributes
|
|
8527
|
+
);
|
|
8528
|
+
shadow = applyFormat(shadow, op.index, op.length, op.attributes);
|
|
8529
|
+
reverse.unshift(...inverse.reverse());
|
|
8530
|
+
}
|
|
8531
|
+
}
|
|
8532
|
+
return reverse;
|
|
8533
|
+
}
|
|
8534
|
+
|
|
8535
|
+
// src/crdts/LiveText.ts
|
|
8536
|
+
var LiveText = class _LiveText extends AbstractCrdt {
|
|
8537
|
+
#segments;
|
|
8538
|
+
#version;
|
|
8539
|
+
#pendingOps;
|
|
8540
|
+
constructor(textOrDelta = "", version = 0) {
|
|
8541
|
+
super();
|
|
8542
|
+
this.#segments = typeof textOrDelta === "string" ? textOrDelta.length === 0 ? [] : [{ text: textOrDelta }] : deltaToSegments(textOrDelta);
|
|
8543
|
+
this.#version = version;
|
|
8544
|
+
this.#pendingOps = /* @__PURE__ */ new Map();
|
|
8545
|
+
}
|
|
8546
|
+
get version() {
|
|
8547
|
+
return this.#version;
|
|
8548
|
+
}
|
|
8549
|
+
get length() {
|
|
8550
|
+
return this.toString().length;
|
|
8551
|
+
}
|
|
8552
|
+
/** @internal */
|
|
8553
|
+
static _deserialize([id, item], _parentToChildren, pool) {
|
|
8554
|
+
const text = new _LiveText(item.data, item.version);
|
|
8555
|
+
text._attach(id, pool);
|
|
8556
|
+
return text;
|
|
8557
|
+
}
|
|
8558
|
+
/** @internal */
|
|
8559
|
+
_toOps(parentId, parentKey) {
|
|
8560
|
+
if (this._id === void 0) {
|
|
8561
|
+
throw new Error("Cannot serialize LiveText if it is not attached");
|
|
8562
|
+
}
|
|
8563
|
+
return [
|
|
8564
|
+
{
|
|
8565
|
+
type: OpCode.CREATE_TEXT,
|
|
8566
|
+
id: this._id,
|
|
8567
|
+
parentId,
|
|
8568
|
+
parentKey,
|
|
8569
|
+
data: this.toDelta(),
|
|
8570
|
+
version: this.#version
|
|
8571
|
+
}
|
|
8572
|
+
];
|
|
8573
|
+
}
|
|
8574
|
+
/** @internal */
|
|
8575
|
+
_serialize() {
|
|
8576
|
+
if (this.parent.type !== "HasParent") {
|
|
8577
|
+
throw new Error("Cannot serialize LiveText if parent is missing");
|
|
8578
|
+
}
|
|
8579
|
+
return {
|
|
8580
|
+
type: CrdtType.TEXT,
|
|
8581
|
+
parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
|
|
8582
|
+
parentKey: this.parent.key,
|
|
8583
|
+
data: this.toDelta(),
|
|
8584
|
+
version: this.#version
|
|
8585
|
+
};
|
|
8586
|
+
}
|
|
8587
|
+
/** @internal */
|
|
8588
|
+
_attachChild(_op) {
|
|
8589
|
+
throw new Error("LiveText cannot contain child nodes");
|
|
8590
|
+
}
|
|
8591
|
+
/** @internal */
|
|
8592
|
+
_detachChild(_crdt) {
|
|
8593
|
+
throw new Error("LiveText cannot contain child nodes");
|
|
8594
|
+
}
|
|
8595
|
+
/** @internal */
|
|
8596
|
+
_apply(op, isLocal) {
|
|
8597
|
+
if (op.type !== OpCode.UPDATE_TEXT) {
|
|
8598
|
+
return super._apply(op, isLocal);
|
|
8599
|
+
}
|
|
8600
|
+
if (isLocal) {
|
|
8601
|
+
this.#pendingOps.set(nn(op.opId), op.ops);
|
|
8602
|
+
return this.#applyOperations(op.ops, op.version ?? this.#version);
|
|
8603
|
+
}
|
|
8604
|
+
if (op.opId !== void 0) {
|
|
8605
|
+
const pending2 = this.#pendingOps.get(op.opId);
|
|
8606
|
+
this.#pendingOps.delete(op.opId);
|
|
8607
|
+
const otherPending = Array.from(this.#pendingOps.values()).flat();
|
|
8608
|
+
if (pending2 !== void 0 && otherPending.length > 0) {
|
|
8609
|
+
this.#segments = applyTextOperationsToSegments(
|
|
8610
|
+
this.#segments,
|
|
8611
|
+
invertTextOperations(this.#segments, pending2)
|
|
8612
|
+
);
|
|
8613
|
+
const ops2 = rebaseTextOperations(op.ops, otherPending);
|
|
8614
|
+
return this.#applyOperations(
|
|
8615
|
+
ops2,
|
|
8616
|
+
op.version ?? Math.max(this.#version, op.baseVersion + 1)
|
|
8617
|
+
);
|
|
8618
|
+
}
|
|
8619
|
+
this.#version = op.version ?? Math.max(this.#version, op.baseVersion + 1);
|
|
8620
|
+
return { modified: false };
|
|
8621
|
+
}
|
|
8622
|
+
const pending = Array.from(this.#pendingOps.values()).flat();
|
|
8623
|
+
const ops = pending.length > 0 ? rebaseTextOperations(op.ops, pending) : op.ops;
|
|
8624
|
+
return this.#applyOperations(ops, op.version ?? this.#version + 1);
|
|
8625
|
+
}
|
|
8626
|
+
insert(index, text, attributes) {
|
|
8627
|
+
const clippedIndex = Math.max(0, Math.min(index, this.length));
|
|
8628
|
+
this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
|
|
8629
|
+
}
|
|
8630
|
+
delete(index, length) {
|
|
8631
|
+
const clipped = clipRange(index, length, this.length);
|
|
8632
|
+
if (clipped.length === 0) {
|
|
8633
|
+
return;
|
|
8634
|
+
}
|
|
8635
|
+
this.#dispatch([
|
|
8636
|
+
{ type: "delete", index: clipped.index, length: clipped.length }
|
|
8637
|
+
]);
|
|
8638
|
+
}
|
|
8639
|
+
replace(index, length, text, attributes) {
|
|
8640
|
+
const clipped = clipRange(index, length, this.length);
|
|
8641
|
+
const ops = [];
|
|
8642
|
+
if (clipped.length > 0) {
|
|
8643
|
+
ops.push({
|
|
8644
|
+
type: "delete",
|
|
8645
|
+
index: clipped.index,
|
|
8646
|
+
length: clipped.length
|
|
8647
|
+
});
|
|
8648
|
+
}
|
|
8649
|
+
if (text.length > 0) {
|
|
8650
|
+
ops.push({ type: "insert", index: clipped.index, text, attributes });
|
|
8651
|
+
}
|
|
8652
|
+
this.#dispatch(ops);
|
|
8653
|
+
}
|
|
8654
|
+
format(index, length, attributes) {
|
|
8655
|
+
const clipped = clipRange(index, length, this.length);
|
|
8656
|
+
if (clipped.length === 0) {
|
|
8657
|
+
return;
|
|
8658
|
+
}
|
|
8659
|
+
this.#dispatch([
|
|
8660
|
+
{
|
|
8661
|
+
type: "format",
|
|
8662
|
+
index: clipped.index,
|
|
8663
|
+
length: clipped.length,
|
|
8664
|
+
attributes
|
|
8665
|
+
}
|
|
8666
|
+
]);
|
|
8667
|
+
}
|
|
8668
|
+
#dispatch(ops) {
|
|
8669
|
+
if (ops.length === 0) {
|
|
8670
|
+
return;
|
|
8671
|
+
}
|
|
8672
|
+
this._pool?.assertStorageIsWritable();
|
|
8673
|
+
const baseVersion = this.#version;
|
|
8674
|
+
const reverse = this._pool !== void 0 && this._id !== void 0 ? this.#invertOperations(ops) : [];
|
|
8675
|
+
const changes = this.#applyOperationsLocally(ops);
|
|
8676
|
+
if (this._pool !== void 0 && this._id !== void 0) {
|
|
8677
|
+
const opId = this._pool.generateOpId();
|
|
8678
|
+
this.#pendingOps.set(opId, ops);
|
|
8679
|
+
this._pool.dispatch(
|
|
8680
|
+
[
|
|
8681
|
+
{
|
|
8682
|
+
type: OpCode.UPDATE_TEXT,
|
|
8683
|
+
id: this._id,
|
|
8684
|
+
opId,
|
|
8685
|
+
baseVersion,
|
|
8686
|
+
ops: [...ops]
|
|
8687
|
+
}
|
|
8688
|
+
],
|
|
8689
|
+
reverse,
|
|
8690
|
+
/* @__PURE__ */ new Map([
|
|
8691
|
+
[
|
|
8692
|
+
this._id,
|
|
8693
|
+
{
|
|
8694
|
+
type: "LiveText",
|
|
8695
|
+
node: this,
|
|
8696
|
+
version: this.#version,
|
|
8697
|
+
updates: changes
|
|
8698
|
+
}
|
|
8699
|
+
]
|
|
8700
|
+
])
|
|
8701
|
+
);
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8704
|
+
#applyOperations(ops, version) {
|
|
8705
|
+
const reverse = this.#invertOperations(ops);
|
|
8706
|
+
const changes = this.#applyOperationsLocally(ops);
|
|
8707
|
+
this.#version = Math.max(this.#version, version);
|
|
8708
|
+
return {
|
|
8709
|
+
reverse,
|
|
8710
|
+
modified: {
|
|
8711
|
+
type: "LiveText",
|
|
8712
|
+
node: this,
|
|
8713
|
+
version: this.#version,
|
|
8714
|
+
updates: changes
|
|
8715
|
+
}
|
|
8716
|
+
};
|
|
8717
|
+
}
|
|
8718
|
+
#applyOperationsLocally(ops) {
|
|
8719
|
+
const changes = [];
|
|
8720
|
+
for (const op of ops) {
|
|
8721
|
+
if (op.type === "insert") {
|
|
8722
|
+
this.#segments = applyInsert(
|
|
8723
|
+
this.#segments,
|
|
8724
|
+
op.index,
|
|
8725
|
+
op.text,
|
|
8726
|
+
op.attributes
|
|
8727
|
+
);
|
|
8728
|
+
changes.push({
|
|
8729
|
+
type: "insert",
|
|
8730
|
+
index: op.index,
|
|
8731
|
+
text: op.text,
|
|
8732
|
+
attributes: op.attributes
|
|
8733
|
+
});
|
|
8734
|
+
} else if (op.type === "delete") {
|
|
8735
|
+
const result = applyDelete(this.#segments, op.index, op.length);
|
|
8736
|
+
this.#segments = result.segments;
|
|
8737
|
+
changes.push({
|
|
8738
|
+
type: "delete",
|
|
8739
|
+
index: op.index,
|
|
8740
|
+
length: op.length,
|
|
8741
|
+
deletedText: result.deletedText
|
|
8742
|
+
});
|
|
8743
|
+
} else {
|
|
8744
|
+
this.#segments = applyFormat(
|
|
8745
|
+
this.#segments,
|
|
8746
|
+
op.index,
|
|
8747
|
+
op.length,
|
|
8748
|
+
op.attributes
|
|
8749
|
+
);
|
|
8750
|
+
changes.push({
|
|
8751
|
+
type: "format",
|
|
8752
|
+
index: op.index,
|
|
8753
|
+
length: op.length,
|
|
8754
|
+
attributes: op.attributes
|
|
8755
|
+
});
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8758
|
+
this.invalidate();
|
|
8759
|
+
return changes;
|
|
8760
|
+
}
|
|
8761
|
+
#invertOperations(ops) {
|
|
8762
|
+
return [
|
|
8763
|
+
{
|
|
8764
|
+
type: OpCode.UPDATE_TEXT,
|
|
8765
|
+
id: nn(this._id),
|
|
8766
|
+
baseVersion: this.#version,
|
|
8767
|
+
ops: invertTextOperations(this.#segments, ops)
|
|
8768
|
+
}
|
|
8769
|
+
];
|
|
8770
|
+
}
|
|
8771
|
+
toString() {
|
|
8772
|
+
return this.#segments.map((segment) => segment.text).join("");
|
|
8773
|
+
}
|
|
8774
|
+
toDelta() {
|
|
8775
|
+
return segmentsToDelta(this.#segments);
|
|
8776
|
+
}
|
|
8777
|
+
toJSON() {
|
|
8778
|
+
return super.toJSON();
|
|
8779
|
+
}
|
|
8780
|
+
/** @internal */
|
|
8781
|
+
_toJSON() {
|
|
8782
|
+
return this.toDelta();
|
|
8783
|
+
}
|
|
8784
|
+
/** @internal */
|
|
8785
|
+
_toTreeNode(key) {
|
|
8786
|
+
return {
|
|
8787
|
+
type: "LiveText",
|
|
8788
|
+
id: this._id ?? nanoid(),
|
|
8789
|
+
key,
|
|
8790
|
+
payload: [
|
|
8791
|
+
{
|
|
8792
|
+
type: "Json",
|
|
8793
|
+
id: `${this._id ?? nanoid()}:text`,
|
|
8794
|
+
key: "text",
|
|
8795
|
+
payload: this.toString()
|
|
8796
|
+
}
|
|
8797
|
+
]
|
|
8798
|
+
};
|
|
8799
|
+
}
|
|
8800
|
+
clone() {
|
|
8801
|
+
return new _LiveText(this.toDelta(), this.#version);
|
|
8802
|
+
}
|
|
8803
|
+
};
|
|
8804
|
+
|
|
8210
8805
|
// src/crdts/liveblocks-helpers.ts
|
|
8211
8806
|
function creationOpToLiveNode(op) {
|
|
8212
8807
|
return lsonToLiveNode(creationOpToLson(op));
|
|
@@ -8221,6 +8816,8 @@ function creationOpToLson(op) {
|
|
|
8221
8816
|
return new LiveMap();
|
|
8222
8817
|
case OpCode.CREATE_LIST:
|
|
8223
8818
|
return new LiveList([]);
|
|
8819
|
+
case OpCode.CREATE_TEXT:
|
|
8820
|
+
return new LiveText(op.data, op.version);
|
|
8224
8821
|
default:
|
|
8225
8822
|
return assertNever(op, "Unknown creation Op");
|
|
8226
8823
|
}
|
|
@@ -8243,6 +8840,8 @@ function deserialize(node, parentToChildren, pool) {
|
|
|
8243
8840
|
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
8244
8841
|
} else if (isRegisterStorageNode(node)) {
|
|
8245
8842
|
return LiveRegister._deserialize(node, parentToChildren, pool);
|
|
8843
|
+
} else if (isTextStorageNode(node)) {
|
|
8844
|
+
return LiveText._deserialize(node, parentToChildren, pool);
|
|
8246
8845
|
} else {
|
|
8247
8846
|
throw new Error("Unexpected CRDT type");
|
|
8248
8847
|
}
|
|
@@ -8256,12 +8855,14 @@ function deserializeToLson(node, parentToChildren, pool) {
|
|
|
8256
8855
|
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
8257
8856
|
} else if (isRegisterStorageNode(node)) {
|
|
8258
8857
|
return node[1].data;
|
|
8858
|
+
} else if (isTextStorageNode(node)) {
|
|
8859
|
+
return LiveText._deserialize(node, parentToChildren, pool);
|
|
8259
8860
|
} else {
|
|
8260
8861
|
throw new Error("Unexpected CRDT type");
|
|
8261
8862
|
}
|
|
8262
8863
|
}
|
|
8263
8864
|
function isLiveStructure(value) {
|
|
8264
|
-
return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
|
|
8865
|
+
return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value);
|
|
8265
8866
|
}
|
|
8266
8867
|
function isLiveNode(value) {
|
|
8267
8868
|
return isLiveStructure(value) || isLiveRegister(value);
|
|
@@ -8275,6 +8876,9 @@ function isLiveMap(value) {
|
|
|
8275
8876
|
function isLiveObject(value) {
|
|
8276
8877
|
return value instanceof LiveObject;
|
|
8277
8878
|
}
|
|
8879
|
+
function isLiveText(value) {
|
|
8880
|
+
return value instanceof LiveText;
|
|
8881
|
+
}
|
|
8278
8882
|
function isLiveRegister(value) {
|
|
8279
8883
|
return value instanceof LiveRegister;
|
|
8280
8884
|
}
|
|
@@ -8284,14 +8888,14 @@ function cloneLson(value) {
|
|
|
8284
8888
|
function liveNodeToLson(obj) {
|
|
8285
8889
|
if (obj instanceof LiveRegister) {
|
|
8286
8890
|
return obj.data;
|
|
8287
|
-
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
|
|
8891
|
+
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText) {
|
|
8288
8892
|
return obj;
|
|
8289
8893
|
} else {
|
|
8290
8894
|
return assertNever(obj, "Unknown AbstractCrdt");
|
|
8291
8895
|
}
|
|
8292
8896
|
}
|
|
8293
8897
|
function lsonToLiveNode(value) {
|
|
8294
|
-
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
|
|
8898
|
+
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText) {
|
|
8295
8899
|
return value;
|
|
8296
8900
|
} else {
|
|
8297
8901
|
return new LiveRegister(value);
|
|
@@ -8316,6 +8920,38 @@ function getTreesDiffOperations(currentItems, newItems) {
|
|
|
8316
8920
|
});
|
|
8317
8921
|
}
|
|
8318
8922
|
}
|
|
8923
|
+
if (crdt.type === CrdtType.TEXT) {
|
|
8924
|
+
if (currentCrdt.type !== CrdtType.TEXT || stringifyOrLog(crdt.data) !== stringifyOrLog(currentCrdt.data) || crdt.version !== currentCrdt.version) {
|
|
8925
|
+
ops.push({
|
|
8926
|
+
type: OpCode.UPDATE_TEXT,
|
|
8927
|
+
id,
|
|
8928
|
+
baseVersion: currentCrdt.type === CrdtType.TEXT ? currentCrdt.version : 0,
|
|
8929
|
+
version: crdt.version,
|
|
8930
|
+
ops: [
|
|
8931
|
+
{
|
|
8932
|
+
type: "delete",
|
|
8933
|
+
index: 0,
|
|
8934
|
+
length: currentCrdt.type === CrdtType.TEXT ? currentCrdt.data.reduce(
|
|
8935
|
+
(sum, item) => sum + item.text.length,
|
|
8936
|
+
0
|
|
8937
|
+
) : 0
|
|
8938
|
+
},
|
|
8939
|
+
...crdt.data.map(
|
|
8940
|
+
(item, index, items) => item.attributes === void 0 ? {
|
|
8941
|
+
type: "insert",
|
|
8942
|
+
index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
|
|
8943
|
+
text: item.text
|
|
8944
|
+
} : {
|
|
8945
|
+
type: "insert",
|
|
8946
|
+
index: items.slice(0, index).reduce((sum, item2) => sum + item2.text.length, 0),
|
|
8947
|
+
text: item.text,
|
|
8948
|
+
attributes: item.attributes
|
|
8949
|
+
}
|
|
8950
|
+
)
|
|
8951
|
+
]
|
|
8952
|
+
});
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8319
8955
|
if (crdt.parentKey !== currentCrdt.parentKey) {
|
|
8320
8956
|
ops.push({
|
|
8321
8957
|
type: OpCode.SET_PARENT_KEY,
|
|
@@ -8364,6 +9000,16 @@ function getTreesDiffOperations(currentItems, newItems) {
|
|
|
8364
9000
|
parentKey: crdt.parentKey
|
|
8365
9001
|
});
|
|
8366
9002
|
break;
|
|
9003
|
+
case CrdtType.TEXT:
|
|
9004
|
+
ops.push({
|
|
9005
|
+
type: OpCode.CREATE_TEXT,
|
|
9006
|
+
id,
|
|
9007
|
+
parentId: crdt.parentId,
|
|
9008
|
+
parentKey: crdt.parentKey,
|
|
9009
|
+
data: crdt.data,
|
|
9010
|
+
version: crdt.version
|
|
9011
|
+
});
|
|
9012
|
+
break;
|
|
8367
9013
|
}
|
|
8368
9014
|
}
|
|
8369
9015
|
});
|
|
@@ -8396,6 +9042,12 @@ function mergeListStorageUpdates(first, second) {
|
|
|
8396
9042
|
updates: updates.concat(second.updates)
|
|
8397
9043
|
};
|
|
8398
9044
|
}
|
|
9045
|
+
function mergeTextStorageUpdates(first, second) {
|
|
9046
|
+
return {
|
|
9047
|
+
...second,
|
|
9048
|
+
updates: first.updates.concat(second.updates)
|
|
9049
|
+
};
|
|
9050
|
+
}
|
|
8399
9051
|
function mergeStorageUpdates(first, second) {
|
|
8400
9052
|
if (first === void 0) {
|
|
8401
9053
|
return second;
|
|
@@ -8406,6 +9058,8 @@ function mergeStorageUpdates(first, second) {
|
|
|
8406
9058
|
return mergeMapStorageUpdates(first, second);
|
|
8407
9059
|
} else if (first.type === "LiveList" && second.type === "LiveList") {
|
|
8408
9060
|
return mergeListStorageUpdates(first, second);
|
|
9061
|
+
} else if (first.type === "LiveText" && second.type === "LiveText") {
|
|
9062
|
+
return mergeTextStorageUpdates(first, second);
|
|
8409
9063
|
} else {
|
|
8410
9064
|
}
|
|
8411
9065
|
return second;
|
|
@@ -9687,7 +10341,7 @@ function createRoom(options, config) {
|
|
|
9687
10341
|
);
|
|
9688
10342
|
output.reverse.pushLeft(applyOpResult.reverse);
|
|
9689
10343
|
}
|
|
9690
|
-
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
|
|
10344
|
+
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT) {
|
|
9691
10345
|
createdNodeIds.add(op.id);
|
|
9692
10346
|
}
|
|
9693
10347
|
}
|
|
@@ -9707,6 +10361,7 @@ function createRoom(options, config) {
|
|
|
9707
10361
|
switch (op.type) {
|
|
9708
10362
|
case OpCode.DELETE_OBJECT_KEY:
|
|
9709
10363
|
case OpCode.UPDATE_OBJECT:
|
|
10364
|
+
case OpCode.UPDATE_TEXT:
|
|
9710
10365
|
case OpCode.DELETE_CRDT: {
|
|
9711
10366
|
const node = context.pool.nodes.get(op.id);
|
|
9712
10367
|
if (node === void 0) {
|
|
@@ -9731,6 +10386,7 @@ function createRoom(options, config) {
|
|
|
9731
10386
|
case OpCode.CREATE_OBJECT:
|
|
9732
10387
|
case OpCode.CREATE_LIST:
|
|
9733
10388
|
case OpCode.CREATE_MAP:
|
|
10389
|
+
case OpCode.CREATE_TEXT:
|
|
9734
10390
|
case OpCode.CREATE_REGISTER: {
|
|
9735
10391
|
if (op.parentId === void 0) {
|
|
9736
10392
|
return { modified: false };
|
|
@@ -11885,6 +12541,12 @@ function toPlainLson(lson) {
|
|
|
11885
12541
|
liveblocksType: "LiveList",
|
|
11886
12542
|
data: [...lson].map((item) => toPlainLson(item))
|
|
11887
12543
|
};
|
|
12544
|
+
} else if (lson instanceof LiveText) {
|
|
12545
|
+
return {
|
|
12546
|
+
liveblocksType: "LiveText",
|
|
12547
|
+
data: lson.toDelta(),
|
|
12548
|
+
version: lson.version
|
|
12549
|
+
};
|
|
11888
12550
|
} else {
|
|
11889
12551
|
return lson;
|
|
11890
12552
|
}
|
|
@@ -12066,6 +12728,7 @@ export {
|
|
|
12066
12728
|
LiveList,
|
|
12067
12729
|
LiveMap,
|
|
12068
12730
|
LiveObject,
|
|
12731
|
+
LiveText,
|
|
12069
12732
|
LiveblocksError,
|
|
12070
12733
|
MENTION_CHARACTER,
|
|
12071
12734
|
MutableSignal,
|
|
@@ -12077,6 +12740,7 @@ export {
|
|
|
12077
12740
|
SortedList,
|
|
12078
12741
|
TextEditorType,
|
|
12079
12742
|
WebsocketCloseCodes,
|
|
12743
|
+
applyLiveTextOperations,
|
|
12080
12744
|
asPos,
|
|
12081
12745
|
assert,
|
|
12082
12746
|
assertNever,
|
|
@@ -12132,6 +12796,7 @@ export {
|
|
|
12132
12796
|
isRegisterStorageNode,
|
|
12133
12797
|
isRootStorageNode,
|
|
12134
12798
|
isStartsWithOperator,
|
|
12799
|
+
isTextStorageNode,
|
|
12135
12800
|
isUrl,
|
|
12136
12801
|
kInternal,
|
|
12137
12802
|
keys,
|