@liveblocks/core 3.21.0-exp1 → 3.21.0-private1
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 +508 -1645
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -367
- package/dist/index.d.ts +65 -367
- package/dist/index.js +346 -1483
- 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.21.0-
|
|
9
|
+
var PKG_VERSION = "3.21.0-private1";
|
|
10
10
|
var PKG_FORMAT = "esm";
|
|
11
11
|
|
|
12
12
|
// src/dupe-detection.ts
|
|
@@ -1567,6 +1567,15 @@ function isUrl(string) {
|
|
|
1567
1567
|
}
|
|
1568
1568
|
|
|
1569
1569
|
// src/api-client.ts
|
|
1570
|
+
function commentsResourceForVisibility(visibility) {
|
|
1571
|
+
if (visibility === "private") {
|
|
1572
|
+
return "comments:private";
|
|
1573
|
+
}
|
|
1574
|
+
if (visibility === "public") {
|
|
1575
|
+
return "comments:public";
|
|
1576
|
+
}
|
|
1577
|
+
return "comments";
|
|
1578
|
+
}
|
|
1570
1579
|
function createApiClient({
|
|
1571
1580
|
baseUrl,
|
|
1572
1581
|
authManager,
|
|
@@ -1618,7 +1627,7 @@ function createApiClient({
|
|
|
1618
1627
|
url`/v2/c/rooms/${options.roomId}/threads`,
|
|
1619
1628
|
await authManager.getAuthValue({
|
|
1620
1629
|
roomId: options.roomId,
|
|
1621
|
-
resource:
|
|
1630
|
+
resource: commentsResourceForVisibility(options.query?.visibility),
|
|
1622
1631
|
access: "read"
|
|
1623
1632
|
}),
|
|
1624
1633
|
{
|
|
@@ -1687,11 +1696,12 @@ function createApiClient({
|
|
|
1687
1696
|
url`/v2/c/rooms/${options.roomId}/threads`,
|
|
1688
1697
|
await authManager.getAuthValue({
|
|
1689
1698
|
roomId: options.roomId,
|
|
1690
|
-
resource: "
|
|
1699
|
+
resource: commentsResourceForVisibility(options.visibility ?? "public"),
|
|
1691
1700
|
access: "write"
|
|
1692
1701
|
}),
|
|
1693
1702
|
{
|
|
1694
1703
|
id: threadId,
|
|
1704
|
+
visibility: options.visibility,
|
|
1695
1705
|
comment: {
|
|
1696
1706
|
id: commentId,
|
|
1697
1707
|
body: options.body,
|
|
@@ -3847,7 +3857,6 @@ var ManagedSocket = class {
|
|
|
3847
3857
|
|
|
3848
3858
|
// src/internal.ts
|
|
3849
3859
|
var kInternal = /* @__PURE__ */ Symbol();
|
|
3850
|
-
var kStorageUpdateSource = /* @__PURE__ */ Symbol();
|
|
3851
3860
|
|
|
3852
3861
|
// src/lib/IncrementalJsonParser.ts
|
|
3853
3862
|
var EMPTY_OBJECT = Object.freeze({});
|
|
@@ -5192,6 +5201,15 @@ var Permission = {
|
|
|
5192
5201
|
CommentsWrite: "comments:write",
|
|
5193
5202
|
CommentsRead: "comments:read",
|
|
5194
5203
|
CommentsNone: "comments:none",
|
|
5204
|
+
CommentsPublicWrite: "comments:public:write",
|
|
5205
|
+
CommentsPublicRead: "comments:public:read",
|
|
5206
|
+
CommentsPublicNone: "comments:public:none",
|
|
5207
|
+
CommentsPrivateWrite: "comments:private:write",
|
|
5208
|
+
CommentsPrivateRead: "comments:private:read",
|
|
5209
|
+
CommentsPrivateNone: "comments:private:none",
|
|
5210
|
+
CommentsPersonalWrite: "comments:personal:write",
|
|
5211
|
+
CommentsPersonalRead: "comments:personal:read",
|
|
5212
|
+
CommentsPersonalNone: "comments:personal:none",
|
|
5195
5213
|
/**
|
|
5196
5214
|
* Feeds
|
|
5197
5215
|
*/
|
|
@@ -5204,152 +5222,125 @@ var Permission = {
|
|
|
5204
5222
|
LegacyRoomPresenceWrite: "room:presence:write"
|
|
5205
5223
|
};
|
|
5206
5224
|
var ACCESS_LEVELS = ["none", "read", "write"];
|
|
5207
|
-
var basePermissionScopes = /* @__PURE__ */ new Set([
|
|
5208
|
-
Permission.Read,
|
|
5209
|
-
Permission.Write,
|
|
5210
|
-
Permission.RoomRead,
|
|
5211
|
-
Permission.RoomWrite
|
|
5212
|
-
]);
|
|
5213
5225
|
var ACCESS_LEVEL_RANKS = {
|
|
5214
5226
|
none: 0,
|
|
5215
5227
|
read: 1,
|
|
5216
5228
|
write: 2
|
|
5217
5229
|
};
|
|
5218
|
-
var
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
write: [Permission.Write, Permission.RoomWrite]
|
|
5222
|
-
},
|
|
5223
|
-
personal: {
|
|
5224
|
-
write: []
|
|
5225
|
-
},
|
|
5226
|
-
storage: {
|
|
5227
|
-
write: [Permission.StorageWrite],
|
|
5228
|
-
read: [Permission.StorageRead],
|
|
5229
|
-
none: [Permission.StorageNone]
|
|
5230
|
-
},
|
|
5231
|
-
comments: {
|
|
5232
|
-
write: [Permission.CommentsWrite],
|
|
5233
|
-
read: [Permission.CommentsRead],
|
|
5234
|
-
none: [Permission.CommentsNone]
|
|
5235
|
-
},
|
|
5236
|
-
feeds: {
|
|
5237
|
-
write: [Permission.FeedsWrite],
|
|
5238
|
-
read: [Permission.FeedsRead],
|
|
5239
|
-
none: [Permission.FeedsNone]
|
|
5240
|
-
}
|
|
5230
|
+
var BASE_PERMISSIONS_BY_ACCESS = {
|
|
5231
|
+
read: [Permission.Read, Permission.RoomRead],
|
|
5232
|
+
write: [Permission.Write, Permission.RoomWrite]
|
|
5241
5233
|
};
|
|
5242
5234
|
var NO_PERMISSION_MATRIX = {
|
|
5243
5235
|
room: "none",
|
|
5244
5236
|
storage: "none",
|
|
5245
5237
|
comments: "none",
|
|
5238
|
+
"comments:public": "none",
|
|
5239
|
+
"comments:private": "none",
|
|
5240
|
+
"comments:personal": "none",
|
|
5246
5241
|
feeds: "none",
|
|
5247
5242
|
personal: "none"
|
|
5248
5243
|
};
|
|
5249
5244
|
var BASE_PERMISSION_RESOURCE = "room";
|
|
5250
|
-
var
|
|
5245
|
+
var basePermissionScopes = /* @__PURE__ */ new Set([
|
|
5246
|
+
Permission.Read,
|
|
5247
|
+
Permission.Write,
|
|
5248
|
+
Permission.RoomRead,
|
|
5249
|
+
Permission.RoomWrite
|
|
5250
|
+
]);
|
|
5251
|
+
var ROOM_PERMISSION_RESOURCES = Object.freeze([
|
|
5251
5252
|
"storage",
|
|
5252
5253
|
"comments",
|
|
5254
|
+
"comments:public",
|
|
5255
|
+
"comments:private",
|
|
5256
|
+
"comments:personal",
|
|
5253
5257
|
"feeds"
|
|
5254
|
-
];
|
|
5255
|
-
var
|
|
5256
|
-
|
|
5257
|
-
return VALID_PERMISSIONS.has(permission);
|
|
5258
|
-
}
|
|
5259
|
-
function resolveResourceAccess(scopes, resource) {
|
|
5260
|
-
const permissions = PERMISSIONS_BY_RESOURCE[resource];
|
|
5261
|
-
let resourceAccess;
|
|
5262
|
-
for (const access of ACCESS_LEVELS) {
|
|
5263
|
-
const scopedPermissions = permissions[access];
|
|
5264
|
-
if (scopedPermissions !== void 0 && scopedPermissions.some((permission) => scopes.includes(permission))) {
|
|
5265
|
-
resourceAccess = access;
|
|
5266
|
-
}
|
|
5267
|
-
}
|
|
5268
|
-
return resourceAccess;
|
|
5269
|
-
}
|
|
5270
|
-
function permissionMatrixFromResolvedScopes(resolved) {
|
|
5271
|
-
if (!resolved.hasDefaultPermission) {
|
|
5272
|
-
return { ...NO_PERMISSION_MATRIX };
|
|
5273
|
-
}
|
|
5274
|
-
const matrix = {
|
|
5275
|
-
...NO_PERMISSION_MATRIX,
|
|
5276
|
-
[BASE_PERMISSION_RESOURCE]: resolved.baseAccess,
|
|
5277
|
-
personal: "write"
|
|
5278
|
-
};
|
|
5258
|
+
]);
|
|
5259
|
+
var CHILD_ROOM_PERMISSION_RESOURCES = (() => {
|
|
5260
|
+
const result = /* @__PURE__ */ new Map();
|
|
5279
5261
|
for (const resource of ROOM_PERMISSION_RESOURCES) {
|
|
5280
|
-
|
|
5262
|
+
const childResourcePrefix = `${resource}:`;
|
|
5263
|
+
result.set(
|
|
5264
|
+
resource,
|
|
5265
|
+
Object.freeze(
|
|
5266
|
+
ROOM_PERMISSION_RESOURCES.filter((candidate) => {
|
|
5267
|
+
if (!candidate.startsWith(childResourcePrefix)) {
|
|
5268
|
+
return false;
|
|
5269
|
+
}
|
|
5270
|
+
return !candidate.slice(childResourcePrefix.length).includes(":");
|
|
5271
|
+
})
|
|
5272
|
+
)
|
|
5273
|
+
);
|
|
5281
5274
|
}
|
|
5282
|
-
return
|
|
5283
|
-
}
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
}
|
|
5287
|
-
function resolvePermissionScopes(scopes) {
|
|
5288
|
-
const hasDefaultPermission = scopes.includes(Permission.Write) || scopes.includes(Permission.Read) || scopes.includes(Permission.RoomWrite) || scopes.includes(Permission.RoomRead);
|
|
5289
|
-
const baseAccess = scopes.includes(Permission.Write) || scopes.includes(Permission.RoomWrite) ? "write" : scopes.includes(Permission.Read) || scopes.includes(Permission.RoomRead) ? "read" : "none";
|
|
5290
|
-
const matrix = {};
|
|
5275
|
+
return result;
|
|
5276
|
+
})();
|
|
5277
|
+
var LEAF_ROOM_PERMISSION_RESOURCES_BY_PARENT = (() => {
|
|
5278
|
+
const result = /* @__PURE__ */ new Map();
|
|
5291
5279
|
for (const resource of ROOM_PERMISSION_RESOURCES) {
|
|
5292
|
-
|
|
5293
|
-
if (access !== void 0) {
|
|
5294
|
-
matrix[resource] = access;
|
|
5295
|
-
}
|
|
5296
|
-
}
|
|
5297
|
-
return { hasDefaultPermission, baseAccess, matrix };
|
|
5298
|
-
}
|
|
5299
|
-
function hasPermissionAccess(matrix, resource, requiredAccess) {
|
|
5300
|
-
const access = matrix[resource] ?? "none";
|
|
5301
|
-
return ACCESS_LEVEL_RANKS[access] >= ACCESS_LEVEL_RANKS[requiredAccess];
|
|
5302
|
-
}
|
|
5303
|
-
function resolveRoomPermissionMatrix(permissions, roomId) {
|
|
5304
|
-
const matchedPermissions = permissions.filter(
|
|
5305
|
-
(entry) => roomPatternMatches(entry.pattern, roomId)
|
|
5306
|
-
);
|
|
5307
|
-
if (matchedPermissions.length === 0) {
|
|
5308
|
-
return void 0;
|
|
5280
|
+
result.set(resource, Object.freeze(getLeafResources(resource)));
|
|
5309
5281
|
}
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
const
|
|
5314
|
-
for (const
|
|
5315
|
-
const
|
|
5316
|
-
|
|
5317
|
-
if (resolved.hasDefaultPermission) {
|
|
5318
|
-
hasDefaultPermission = true;
|
|
5319
|
-
baseAccess = strongestAccess(baseAccess, resolved.baseAccess);
|
|
5320
|
-
}
|
|
5321
|
-
for (const resource of ROOM_PERMISSION_RESOURCES) {
|
|
5322
|
-
const access = resolved.matrix[resource];
|
|
5323
|
-
if (access !== void 0) {
|
|
5324
|
-
const currentSpecificity = explicitSpecificity[resource] ?? -1;
|
|
5325
|
-
if (specificity > currentSpecificity) {
|
|
5326
|
-
explicitMatrix[resource] = access;
|
|
5327
|
-
explicitSpecificity[resource] = specificity;
|
|
5328
|
-
} else if (specificity === currentSpecificity) {
|
|
5329
|
-
explicitMatrix[resource] = strongestAccess(
|
|
5330
|
-
explicitMatrix[resource] ?? "none",
|
|
5331
|
-
access
|
|
5332
|
-
);
|
|
5333
|
-
}
|
|
5334
|
-
}
|
|
5282
|
+
return result;
|
|
5283
|
+
})();
|
|
5284
|
+
var PARENT_ROOM_PERMISSION_RESOURCES = (() => {
|
|
5285
|
+
const result = /* @__PURE__ */ new Map();
|
|
5286
|
+
for (const parentResource of ROOM_PERMISSION_RESOURCES) {
|
|
5287
|
+
for (const childResource of childResourcesOf(parentResource)) {
|
|
5288
|
+
result.set(childResource, parentResource);
|
|
5335
5289
|
}
|
|
5336
5290
|
}
|
|
5337
|
-
return
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5291
|
+
return result;
|
|
5292
|
+
})();
|
|
5293
|
+
var LEAF_ROOM_PERMISSION_RESOURCES = Object.freeze(
|
|
5294
|
+
ROOM_PERMISSION_RESOURCES.filter(
|
|
5295
|
+
(resource) => childResourcesOf(resource).length === 0
|
|
5296
|
+
)
|
|
5297
|
+
);
|
|
5298
|
+
var PERMISSIONS_BY_ROOM_RESOURCE = {
|
|
5299
|
+
storage: {
|
|
5300
|
+
write: [Permission.StorageWrite],
|
|
5301
|
+
read: [Permission.StorageRead],
|
|
5302
|
+
none: [Permission.StorageNone]
|
|
5303
|
+
},
|
|
5304
|
+
comments: {
|
|
5305
|
+
write: [Permission.CommentsWrite],
|
|
5306
|
+
read: [Permission.CommentsRead],
|
|
5307
|
+
none: [Permission.CommentsNone]
|
|
5308
|
+
},
|
|
5309
|
+
"comments:public": {
|
|
5310
|
+
write: [Permission.CommentsPublicWrite],
|
|
5311
|
+
read: [Permission.CommentsPublicRead],
|
|
5312
|
+
none: [Permission.CommentsPublicNone]
|
|
5313
|
+
},
|
|
5314
|
+
"comments:private": {
|
|
5315
|
+
write: [Permission.CommentsPrivateWrite],
|
|
5316
|
+
read: [Permission.CommentsPrivateRead],
|
|
5317
|
+
none: [Permission.CommentsPrivateNone]
|
|
5318
|
+
},
|
|
5319
|
+
"comments:personal": {
|
|
5320
|
+
write: [Permission.CommentsPersonalWrite],
|
|
5321
|
+
read: [Permission.CommentsPersonalRead],
|
|
5322
|
+
none: [Permission.CommentsPersonalNone]
|
|
5323
|
+
},
|
|
5324
|
+
feeds: {
|
|
5325
|
+
write: [Permission.FeedsWrite],
|
|
5326
|
+
read: [Permission.FeedsRead],
|
|
5327
|
+
none: [Permission.FeedsNone]
|
|
5328
|
+
}
|
|
5329
|
+
};
|
|
5330
|
+
var VALID_PERMISSIONS = new Set(Object.values(Permission));
|
|
5343
5331
|
function normalizeRoomPermissions(permissions) {
|
|
5344
5332
|
if (!Array.isArray(permissions)) {
|
|
5345
5333
|
throw new Error("Permission list must be an array");
|
|
5346
5334
|
}
|
|
5347
5335
|
const result = [];
|
|
5348
5336
|
for (const permission of permissions) {
|
|
5349
|
-
|
|
5337
|
+
const knownPermission = Object.values(Permission).find(
|
|
5338
|
+
(value) => value === permission
|
|
5339
|
+
);
|
|
5340
|
+
if (knownPermission === void 0) {
|
|
5350
5341
|
throw new Error(`Not a valid permission: ${permission}`);
|
|
5351
5342
|
}
|
|
5352
|
-
result.push(
|
|
5343
|
+
result.push(knownPermission);
|
|
5353
5344
|
}
|
|
5354
5345
|
return result;
|
|
5355
5346
|
}
|
|
@@ -5375,6 +5366,31 @@ function normalizeUpdateRoomAccesses(accesses) {
|
|
|
5375
5366
|
])
|
|
5376
5367
|
);
|
|
5377
5368
|
}
|
|
5369
|
+
function validatePermissionsSet(scopes) {
|
|
5370
|
+
const unknownScopes = scopes.filter((scope) => !VALID_PERMISSIONS.has(scope));
|
|
5371
|
+
if (unknownScopes.length > 0) {
|
|
5372
|
+
return `Unknown permission scope(s): ${unknownScopes.join(", ")}`;
|
|
5373
|
+
}
|
|
5374
|
+
const baseScopes = scopes.filter((scope) => basePermissionScopes.has(scope));
|
|
5375
|
+
if (baseScopes.length !== 1) {
|
|
5376
|
+
return `Permissions must include exactly one of ${Permission.Read}, ${Permission.Write} (or the legacy aliases ${Permission.RoomRead}, ${Permission.RoomWrite}), got ${baseScopes.length === 0 ? "none" : baseScopes.join(", ")}`;
|
|
5377
|
+
}
|
|
5378
|
+
const seenFeatures = /* @__PURE__ */ new Set();
|
|
5379
|
+
for (const scope of scopes) {
|
|
5380
|
+
if (basePermissionScopes.has(scope) || scope === Permission.LegacyRoomPresenceWrite) {
|
|
5381
|
+
continue;
|
|
5382
|
+
}
|
|
5383
|
+
const feature = scope.split(":").slice(0, -1).join(":");
|
|
5384
|
+
if (seenFeatures.has(feature)) {
|
|
5385
|
+
return `Permissions can include at most one scope per feature, got multiple "${feature}" scopes`;
|
|
5386
|
+
}
|
|
5387
|
+
seenFeatures.add(feature);
|
|
5388
|
+
}
|
|
5389
|
+
return true;
|
|
5390
|
+
}
|
|
5391
|
+
function permissionMatrixFromScopes(scopes) {
|
|
5392
|
+
return permissionMatrixFromResolvedScopes(resolvePermissionScopes(scopes));
|
|
5393
|
+
}
|
|
5378
5394
|
function permissionMatrixToScopes(matrix) {
|
|
5379
5395
|
const scopes = [];
|
|
5380
5396
|
const baseAccess = matrix.room;
|
|
@@ -5382,13 +5398,59 @@ function permissionMatrixToScopes(matrix) {
|
|
|
5382
5398
|
scopes.push(permissionForAccessLevel(BASE_PERMISSION_RESOURCE, baseAccess));
|
|
5383
5399
|
}
|
|
5384
5400
|
for (const resource of ROOM_PERMISSION_RESOURCES) {
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
scopes.push(permissionForAccessLevel(resource, access));
|
|
5401
|
+
if (parentResourceOf(resource) === void 0) {
|
|
5402
|
+
pushResourcePermissions(scopes, matrix, resource, baseAccess);
|
|
5388
5403
|
}
|
|
5389
5404
|
}
|
|
5390
5405
|
return scopes;
|
|
5391
5406
|
}
|
|
5407
|
+
function hasPermissionAccess(matrix, resource, requiredAccess) {
|
|
5408
|
+
const access = matrix[resource] ?? "none";
|
|
5409
|
+
return ACCESS_LEVEL_RANKS[access] >= ACCESS_LEVEL_RANKS[requiredAccess];
|
|
5410
|
+
}
|
|
5411
|
+
function resolveRoomPermissionMatrix(permissions, roomId) {
|
|
5412
|
+
const matchedPermissions = permissions.filter((entry) => {
|
|
5413
|
+
if (entry.pattern.includes("*")) {
|
|
5414
|
+
return roomId.startsWith(entry.pattern.replace("*", ""));
|
|
5415
|
+
}
|
|
5416
|
+
return entry.pattern === roomId;
|
|
5417
|
+
});
|
|
5418
|
+
if (matchedPermissions.length === 0) {
|
|
5419
|
+
return void 0;
|
|
5420
|
+
}
|
|
5421
|
+
let hasDefaultPermission = false;
|
|
5422
|
+
let baseAccess = "none";
|
|
5423
|
+
const leafAccesses = {};
|
|
5424
|
+
const leafSpecificity = {};
|
|
5425
|
+
for (const entry of matchedPermissions) {
|
|
5426
|
+
const resolved = resolvePermissionScopes(entry.scopes);
|
|
5427
|
+
const specificity = entry.pattern.replace("*", "").length;
|
|
5428
|
+
if (resolved.baseAccess !== void 0) {
|
|
5429
|
+
hasDefaultPermission = true;
|
|
5430
|
+
baseAccess = strongestAccess(baseAccess, resolved.baseAccess);
|
|
5431
|
+
}
|
|
5432
|
+
for (const resource of LEAF_ROOM_PERMISSION_RESOURCES) {
|
|
5433
|
+
if (resolved.leafAccesses[resource] === void 0) {
|
|
5434
|
+
continue;
|
|
5435
|
+
}
|
|
5436
|
+
const access = resolveLeafAccessFromSource(resolved, resource);
|
|
5437
|
+
const currentSpecificity = leafSpecificity[resource] ?? -1;
|
|
5438
|
+
if (specificity > currentSpecificity) {
|
|
5439
|
+
leafAccesses[resource] = access;
|
|
5440
|
+
leafSpecificity[resource] = specificity;
|
|
5441
|
+
} else if (specificity === currentSpecificity) {
|
|
5442
|
+
leafAccesses[resource] = strongestAccess(
|
|
5443
|
+
leafAccesses[resource] ?? "none",
|
|
5444
|
+
access
|
|
5445
|
+
);
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
return permissionMatrixFromResolvedScopes({
|
|
5450
|
+
baseAccess: hasDefaultPermission ? baseAccess : void 0,
|
|
5451
|
+
leafAccesses
|
|
5452
|
+
});
|
|
5453
|
+
}
|
|
5392
5454
|
function mergeRoomPermissionScopes({
|
|
5393
5455
|
defaultAccesses,
|
|
5394
5456
|
groupsAccesses,
|
|
@@ -5402,90 +5464,158 @@ function mergeRoomPermissionScopes({
|
|
|
5402
5464
|
resolvePermissionScopes(userAccesses)
|
|
5403
5465
|
];
|
|
5404
5466
|
const merged = {
|
|
5405
|
-
|
|
5406
|
-
baseAccess: "none",
|
|
5407
|
-
matrix: {}
|
|
5467
|
+
leafAccesses: {}
|
|
5408
5468
|
};
|
|
5409
5469
|
for (const source of sources) {
|
|
5410
|
-
if (source.
|
|
5411
|
-
merged.hasDefaultPermission = true;
|
|
5470
|
+
if (source.baseAccess !== void 0) {
|
|
5412
5471
|
merged.baseAccess = source.baseAccess;
|
|
5413
5472
|
}
|
|
5414
|
-
for (const resource of
|
|
5415
|
-
const
|
|
5416
|
-
if (
|
|
5417
|
-
|
|
5473
|
+
for (const resource of LEAF_ROOM_PERMISSION_RESOURCES) {
|
|
5474
|
+
const sourceAccess = source.leafAccesses[resource];
|
|
5475
|
+
if (sourceAccess === void 0) {
|
|
5476
|
+
continue;
|
|
5418
5477
|
}
|
|
5478
|
+
merged.leafAccesses[resource] = sourceAccess;
|
|
5419
5479
|
}
|
|
5420
5480
|
}
|
|
5421
5481
|
return permissionMatrixToScopes(permissionMatrixFromResolvedScopes(merged));
|
|
5422
5482
|
}
|
|
5483
|
+
function resolvePermissionScopes(scopes) {
|
|
5484
|
+
const baseAccess = resolveAccess(scopes, BASE_PERMISSIONS_BY_ACCESS);
|
|
5485
|
+
const leafAccesses = {};
|
|
5486
|
+
for (const resource of ROOM_PERMISSION_RESOURCES) {
|
|
5487
|
+
const access = resolveResourceAccess(scopes, resource);
|
|
5488
|
+
if (access !== void 0) {
|
|
5489
|
+
for (const leafResource of leafResourcesOf(resource)) {
|
|
5490
|
+
leafAccesses[leafResource] = access;
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
return { baseAccess, leafAccesses };
|
|
5495
|
+
}
|
|
5496
|
+
function resolveResourceAccess(scopes, resource) {
|
|
5497
|
+
return resolveAccess(scopes, PERMISSIONS_BY_ROOM_RESOURCE[resource]);
|
|
5498
|
+
}
|
|
5499
|
+
function resolveAccess(scopes, permissions) {
|
|
5500
|
+
let resourceAccess;
|
|
5501
|
+
for (const access of ACCESS_LEVELS) {
|
|
5502
|
+
const scopedPermissions = permissions[access];
|
|
5503
|
+
if (scopedPermissions !== void 0 && scopedPermissions.some((permission) => scopes.includes(permission))) {
|
|
5504
|
+
resourceAccess = access;
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
return resourceAccess;
|
|
5508
|
+
}
|
|
5509
|
+
function permissionMatrixFromResolvedScopes(resolved) {
|
|
5510
|
+
if (resolved.baseAccess === void 0) {
|
|
5511
|
+
return { ...NO_PERMISSION_MATRIX };
|
|
5512
|
+
}
|
|
5513
|
+
const matrix = {
|
|
5514
|
+
...NO_PERMISSION_MATRIX,
|
|
5515
|
+
[BASE_PERMISSION_RESOURCE]: resolved.baseAccess,
|
|
5516
|
+
personal: "write"
|
|
5517
|
+
};
|
|
5518
|
+
for (const resource of LEAF_ROOM_PERMISSION_RESOURCES) {
|
|
5519
|
+
matrix[resource] = resolveLeafAccessFromSource(resolved, resource);
|
|
5520
|
+
}
|
|
5521
|
+
for (const resource of ROOM_PERMISSION_RESOURCES) {
|
|
5522
|
+
if (childResourcesOf(resource).length > 0) {
|
|
5523
|
+
let strongest = "none";
|
|
5524
|
+
for (const leafResource of leafResourcesOf(resource)) {
|
|
5525
|
+
strongest = strongestAccess(strongest, matrix[leafResource]);
|
|
5526
|
+
}
|
|
5527
|
+
matrix[resource] = strongest;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
return matrix;
|
|
5531
|
+
}
|
|
5532
|
+
function resolveLeafAccessFromSource(source, resource) {
|
|
5533
|
+
const access = source.leafAccesses[resource];
|
|
5534
|
+
if (access !== void 0) {
|
|
5535
|
+
return access;
|
|
5536
|
+
}
|
|
5537
|
+
return source.baseAccess ?? "none";
|
|
5538
|
+
}
|
|
5539
|
+
function pushResourcePermissions(scopes, matrix, resource, baseAccess) {
|
|
5540
|
+
const childResources = childResourcesOf(resource);
|
|
5541
|
+
if (childResources.length === 0) {
|
|
5542
|
+
const access = matrix[resource];
|
|
5543
|
+
if (access !== baseAccess) {
|
|
5544
|
+
scopes.push(permissionForAccessLevel(resource, access));
|
|
5545
|
+
}
|
|
5546
|
+
return;
|
|
5547
|
+
}
|
|
5548
|
+
const leafResources = leafResourcesOf(resource);
|
|
5549
|
+
let sharedAccess;
|
|
5550
|
+
let allLeavesShareAccess = true;
|
|
5551
|
+
for (const leafResource of leafResources) {
|
|
5552
|
+
const access = matrix[leafResource];
|
|
5553
|
+
sharedAccess ??= access;
|
|
5554
|
+
if (access !== sharedAccess) {
|
|
5555
|
+
allLeavesShareAccess = false;
|
|
5556
|
+
break;
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
if (sharedAccess !== void 0 && sharedAccess !== baseAccess && allLeavesShareAccess) {
|
|
5560
|
+
scopes.push(permissionForAccessLevel(resource, sharedAccess));
|
|
5561
|
+
return;
|
|
5562
|
+
}
|
|
5563
|
+
for (const childResource of childResources) {
|
|
5564
|
+
pushResourcePermissions(scopes, matrix, childResource, baseAccess);
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5423
5567
|
function mergeResolvedScopesByHighestAccess(sources) {
|
|
5424
5568
|
const merged = {
|
|
5425
|
-
|
|
5426
|
-
baseAccess: "none",
|
|
5427
|
-
matrix: {}
|
|
5569
|
+
leafAccesses: {}
|
|
5428
5570
|
};
|
|
5429
5571
|
for (const source of sources) {
|
|
5430
|
-
if (source.
|
|
5431
|
-
merged.
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
);
|
|
5572
|
+
if (source.baseAccess !== void 0) {
|
|
5573
|
+
merged.baseAccess = strongestAccess(
|
|
5574
|
+
merged.baseAccess ?? "none",
|
|
5575
|
+
source.baseAccess
|
|
5576
|
+
);
|
|
5577
|
+
}
|
|
5578
|
+
for (const resource of LEAF_ROOM_PERMISSION_RESOURCES) {
|
|
5579
|
+
const sourceAccess = source.leafAccesses[resource];
|
|
5580
|
+
if (sourceAccess === void 0) {
|
|
5581
|
+
continue;
|
|
5441
5582
|
}
|
|
5583
|
+
merged.leafAccesses[resource] = strongestAccess(
|
|
5584
|
+
merged.leafAccesses[resource] ?? "none",
|
|
5585
|
+
sourceAccess
|
|
5586
|
+
);
|
|
5442
5587
|
}
|
|
5443
5588
|
}
|
|
5444
5589
|
return merged;
|
|
5445
5590
|
}
|
|
5446
5591
|
function permissionForAccessLevel(resource, access, field = resource) {
|
|
5447
|
-
const
|
|
5448
|
-
const
|
|
5449
|
-
if (
|
|
5450
|
-
|
|
5451
|
-
`Invalid permission level for ${field}: ${JSON.stringify(access) ?? String(access)}`
|
|
5452
|
-
);
|
|
5592
|
+
const permissions = resource === "room" ? BASE_PERMISSIONS_BY_ACCESS[access] : resource === "personal" ? void 0 : PERMISSIONS_BY_ROOM_RESOURCE[resource][access];
|
|
5593
|
+
const permission = permissions?.[0];
|
|
5594
|
+
if (permission !== void 0) {
|
|
5595
|
+
return permission;
|
|
5453
5596
|
}
|
|
5454
|
-
|
|
5597
|
+
throw new Error(
|
|
5598
|
+
`Invalid permission level for ${field}: ${JSON.stringify(access) ?? String(access)}`
|
|
5599
|
+
);
|
|
5455
5600
|
}
|
|
5456
5601
|
function strongestAccess(left, right) {
|
|
5457
5602
|
return ACCESS_LEVEL_RANKS[right] > ACCESS_LEVEL_RANKS[left] ? right : left;
|
|
5458
5603
|
}
|
|
5459
|
-
function
|
|
5460
|
-
|
|
5461
|
-
return roomId.startsWith(pattern.replace("*", ""));
|
|
5462
|
-
}
|
|
5463
|
-
return pattern === roomId;
|
|
5604
|
+
function leafResourcesOf(resource) {
|
|
5605
|
+
return LEAF_ROOM_PERMISSION_RESOURCES_BY_PARENT.get(resource) ?? [];
|
|
5464
5606
|
}
|
|
5465
|
-
function
|
|
5466
|
-
return
|
|
5607
|
+
function childResourcesOf(resource) {
|
|
5608
|
+
return CHILD_ROOM_PERMISSION_RESOURCES.get(resource) ?? [];
|
|
5467
5609
|
}
|
|
5468
|
-
function
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
return `Permissions must include exactly one of ${Permission.Read}, ${Permission.Write} (or the legacy aliases ${Permission.RoomRead}, ${Permission.RoomWrite}), got ${baseScopes.length === 0 ? "none" : baseScopes.join(", ")}`;
|
|
5476
|
-
}
|
|
5477
|
-
const seenFeatures = /* @__PURE__ */ new Set();
|
|
5478
|
-
for (const scope of scopes) {
|
|
5479
|
-
if (basePermissionScopes.has(scope) || scope === Permission.LegacyRoomPresenceWrite) {
|
|
5480
|
-
continue;
|
|
5481
|
-
}
|
|
5482
|
-
const feature = scope.slice(0, scope.indexOf(":"));
|
|
5483
|
-
if (seenFeatures.has(feature)) {
|
|
5484
|
-
return `Permissions can include at most one scope per feature, got multiple "${feature}" scopes`;
|
|
5485
|
-
}
|
|
5486
|
-
seenFeatures.add(feature);
|
|
5610
|
+
function parentResourceOf(resource) {
|
|
5611
|
+
return PARENT_ROOM_PERMISSION_RESOURCES.get(resource);
|
|
5612
|
+
}
|
|
5613
|
+
function getLeafResources(resource) {
|
|
5614
|
+
const childResources = childResourcesOf(resource);
|
|
5615
|
+
if (childResources.length === 0) {
|
|
5616
|
+
return [resource];
|
|
5487
5617
|
}
|
|
5488
|
-
return
|
|
5618
|
+
return childResources.flatMap(getLeafResources);
|
|
5489
5619
|
}
|
|
5490
5620
|
|
|
5491
5621
|
// src/protocol/AuthToken.ts
|
|
@@ -5768,15 +5898,13 @@ var OpCode = Object.freeze({
|
|
|
5768
5898
|
DELETE_CRDT: 5,
|
|
5769
5899
|
DELETE_OBJECT_KEY: 6,
|
|
5770
5900
|
CREATE_MAP: 7,
|
|
5771
|
-
CREATE_REGISTER: 8
|
|
5772
|
-
CREATE_TEXT: 9,
|
|
5773
|
-
UPDATE_TEXT: 10
|
|
5901
|
+
CREATE_REGISTER: 8
|
|
5774
5902
|
});
|
|
5775
5903
|
function isIgnoredOp(op) {
|
|
5776
5904
|
return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
|
|
5777
5905
|
}
|
|
5778
5906
|
function isCreateOp(op) {
|
|
5779
|
-
return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST
|
|
5907
|
+
return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
|
|
5780
5908
|
}
|
|
5781
5909
|
|
|
5782
5910
|
// src/protocol/StorageNode.ts
|
|
@@ -5784,8 +5912,7 @@ var CrdtType = Object.freeze({
|
|
|
5784
5912
|
OBJECT: 0,
|
|
5785
5913
|
LIST: 1,
|
|
5786
5914
|
MAP: 2,
|
|
5787
|
-
REGISTER: 3
|
|
5788
|
-
TEXT: 4
|
|
5915
|
+
REGISTER: 3
|
|
5789
5916
|
});
|
|
5790
5917
|
function isRootStorageNode(node) {
|
|
5791
5918
|
return node[0] === "root";
|
|
@@ -5802,9 +5929,6 @@ function isMapStorageNode(node) {
|
|
|
5802
5929
|
function isRegisterStorageNode(node) {
|
|
5803
5930
|
return node[1].type === CrdtType.REGISTER;
|
|
5804
5931
|
}
|
|
5805
|
-
function isTextStorageNode(node) {
|
|
5806
|
-
return node[1].type === CrdtType.TEXT;
|
|
5807
|
-
}
|
|
5808
5932
|
function isCompactRootNode(node) {
|
|
5809
5933
|
return node[0] === "root";
|
|
5810
5934
|
}
|
|
@@ -5827,9 +5951,6 @@ function* compactNodesToNodeStream(compactNodes) {
|
|
|
5827
5951
|
case CrdtType.REGISTER:
|
|
5828
5952
|
yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
|
|
5829
5953
|
break;
|
|
5830
|
-
case CrdtType.TEXT:
|
|
5831
|
-
yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
|
|
5832
|
-
break;
|
|
5833
5954
|
default:
|
|
5834
5955
|
}
|
|
5835
5956
|
}
|
|
@@ -5858,17 +5979,6 @@ function* nodeStreamToCompactNodes(nodes) {
|
|
|
5858
5979
|
const id = node[0];
|
|
5859
5980
|
const crdt = node[1];
|
|
5860
5981
|
yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
|
|
5861
|
-
} else if (isTextStorageNode(node)) {
|
|
5862
|
-
const id = node[0];
|
|
5863
|
-
const crdt = node[1];
|
|
5864
|
-
yield [
|
|
5865
|
-
id,
|
|
5866
|
-
CrdtType.TEXT,
|
|
5867
|
-
crdt.parentId,
|
|
5868
|
-
crdt.parentKey,
|
|
5869
|
-
crdt.data,
|
|
5870
|
-
crdt.version
|
|
5871
|
-
];
|
|
5872
5982
|
} else {
|
|
5873
5983
|
}
|
|
5874
5984
|
}
|
|
@@ -6071,10 +6181,6 @@ ${parentKey}`;
|
|
|
6071
6181
|
get size() {
|
|
6072
6182
|
return this.#byOpId.size;
|
|
6073
6183
|
}
|
|
6074
|
-
/** The still-unacknowledged op with the given opId, if any. */
|
|
6075
|
-
get(opId) {
|
|
6076
|
-
return this.#byOpId.get(opId);
|
|
6077
|
-
}
|
|
6078
6184
|
/**
|
|
6079
6185
|
* Mark the given Op as still unacknowledged.
|
|
6080
6186
|
*/
|
|
@@ -6175,8 +6281,8 @@ function createManagedPool(roomId, options) {
|
|
|
6175
6281
|
deleteNode: (id) => void nodes.delete(id),
|
|
6176
6282
|
generateId: () => `${getCurrentConnectionId()}:${clock++}`,
|
|
6177
6283
|
generateOpId: () => `${getCurrentConnectionId()}:${opClock++}`,
|
|
6178
|
-
dispatch(ops, reverse, storageUpdates
|
|
6179
|
-
onDispatch?.(ops, reverse, storageUpdates
|
|
6284
|
+
dispatch(ops, reverse, storageUpdates) {
|
|
6285
|
+
onDispatch?.(ops, reverse, storageUpdates);
|
|
6180
6286
|
},
|
|
6181
6287
|
assertStorageIsWritable: () => {
|
|
6182
6288
|
if (!isStorageWritable()) {
|
|
@@ -8735,1105 +8841,6 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
|
|
|
8735
8841
|
}
|
|
8736
8842
|
};
|
|
8737
8843
|
|
|
8738
|
-
// src/crdts/liveTextOps.ts
|
|
8739
|
-
function attributesEqual(left, right) {
|
|
8740
|
-
if (left === right) {
|
|
8741
|
-
return true;
|
|
8742
|
-
}
|
|
8743
|
-
if (left === void 0 || right === void 0) {
|
|
8744
|
-
return false;
|
|
8745
|
-
}
|
|
8746
|
-
const leftKeys = Object.keys(left);
|
|
8747
|
-
const rightKeys = Object.keys(right);
|
|
8748
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
8749
|
-
return false;
|
|
8750
|
-
}
|
|
8751
|
-
for (const key of leftKeys) {
|
|
8752
|
-
if (left[key] !== right[key]) {
|
|
8753
|
-
return false;
|
|
8754
|
-
}
|
|
8755
|
-
}
|
|
8756
|
-
return true;
|
|
8757
|
-
}
|
|
8758
|
-
function cloneAttributes(attributes) {
|
|
8759
|
-
return attributes === void 0 ? void 0 : freeze({ ...attributes });
|
|
8760
|
-
}
|
|
8761
|
-
function normalizeSegments(segments) {
|
|
8762
|
-
const normalized = [];
|
|
8763
|
-
for (const segment of segments) {
|
|
8764
|
-
if (segment.text.length === 0) {
|
|
8765
|
-
continue;
|
|
8766
|
-
}
|
|
8767
|
-
const last = normalized.at(-1);
|
|
8768
|
-
const attributes = cloneAttributes(segment.attributes);
|
|
8769
|
-
if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
|
|
8770
|
-
last.text += segment.text;
|
|
8771
|
-
} else {
|
|
8772
|
-
normalized.push({ text: segment.text, attributes });
|
|
8773
|
-
}
|
|
8774
|
-
}
|
|
8775
|
-
return normalized;
|
|
8776
|
-
}
|
|
8777
|
-
function dataToSegments(data) {
|
|
8778
|
-
return normalizeSegments(
|
|
8779
|
-
data.map(([text, attributes]) => ({
|
|
8780
|
-
text,
|
|
8781
|
-
attributes
|
|
8782
|
-
}))
|
|
8783
|
-
);
|
|
8784
|
-
}
|
|
8785
|
-
function segmentsToData(segments) {
|
|
8786
|
-
return segments.map(
|
|
8787
|
-
(segment) => segment.attributes === void 0 ? [segment.text] : [segment.text, { ...segment.attributes }]
|
|
8788
|
-
);
|
|
8789
|
-
}
|
|
8790
|
-
function textLength(segments) {
|
|
8791
|
-
return segments.reduce((sum, segment) => sum + segment.text.length, 0);
|
|
8792
|
-
}
|
|
8793
|
-
function splitSegmentsAt(segments, index) {
|
|
8794
|
-
const result = [];
|
|
8795
|
-
let offset = 0;
|
|
8796
|
-
for (const segment of segments) {
|
|
8797
|
-
const end = offset + segment.text.length;
|
|
8798
|
-
if (index > offset && index < end) {
|
|
8799
|
-
const before2 = segment.text.slice(0, index - offset);
|
|
8800
|
-
const after2 = segment.text.slice(index - offset);
|
|
8801
|
-
result.push({ text: before2, attributes: segment.attributes });
|
|
8802
|
-
result.push({ text: after2, attributes: segment.attributes });
|
|
8803
|
-
} else {
|
|
8804
|
-
result.push({ text: segment.text, attributes: segment.attributes });
|
|
8805
|
-
}
|
|
8806
|
-
offset = end;
|
|
8807
|
-
}
|
|
8808
|
-
return result;
|
|
8809
|
-
}
|
|
8810
|
-
function clipRange(index, length, contentLength) {
|
|
8811
|
-
const clippedIndex = Math.max(0, Math.min(index, contentLength));
|
|
8812
|
-
const clippedEnd = Math.max(
|
|
8813
|
-
clippedIndex,
|
|
8814
|
-
Math.min(index + length, contentLength)
|
|
8815
|
-
);
|
|
8816
|
-
return { index: clippedIndex, length: clippedEnd - clippedIndex };
|
|
8817
|
-
}
|
|
8818
|
-
function applyInsert(segments, index, text, attributes) {
|
|
8819
|
-
if (text.length === 0) {
|
|
8820
|
-
return normalizeSegments(segments);
|
|
8821
|
-
}
|
|
8822
|
-
const split = splitSegmentsAt(segments, index);
|
|
8823
|
-
const result = [];
|
|
8824
|
-
let offset = 0;
|
|
8825
|
-
let inserted = false;
|
|
8826
|
-
for (const segment of split) {
|
|
8827
|
-
if (!inserted && offset === index) {
|
|
8828
|
-
result.push({ text, attributes });
|
|
8829
|
-
inserted = true;
|
|
8830
|
-
}
|
|
8831
|
-
result.push(segment);
|
|
8832
|
-
offset += segment.text.length;
|
|
8833
|
-
}
|
|
8834
|
-
if (!inserted) {
|
|
8835
|
-
result.push({ text, attributes });
|
|
8836
|
-
}
|
|
8837
|
-
return normalizeSegments(result);
|
|
8838
|
-
}
|
|
8839
|
-
function extractDeletedSegments(segments, index, length) {
|
|
8840
|
-
const split = splitSegmentsAt(
|
|
8841
|
-
splitSegmentsAt(segments, index),
|
|
8842
|
-
index + length
|
|
8843
|
-
);
|
|
8844
|
-
const deleted = [];
|
|
8845
|
-
let offset = 0;
|
|
8846
|
-
for (const segment of split) {
|
|
8847
|
-
const end = offset + segment.text.length;
|
|
8848
|
-
if (offset >= index && end <= index + length) {
|
|
8849
|
-
deleted.push({
|
|
8850
|
-
text: segment.text,
|
|
8851
|
-
attributes: segment.attributes
|
|
8852
|
-
});
|
|
8853
|
-
}
|
|
8854
|
-
offset = end;
|
|
8855
|
-
}
|
|
8856
|
-
return normalizeSegments(deleted);
|
|
8857
|
-
}
|
|
8858
|
-
function applyDelete(segments, index, length) {
|
|
8859
|
-
const deletedSegments = extractDeletedSegments(segments, index, length);
|
|
8860
|
-
const split = splitSegmentsAt(
|
|
8861
|
-
splitSegmentsAt(segments, index),
|
|
8862
|
-
index + length
|
|
8863
|
-
);
|
|
8864
|
-
const result = [];
|
|
8865
|
-
let offset = 0;
|
|
8866
|
-
let deletedText = "";
|
|
8867
|
-
for (const segment of split) {
|
|
8868
|
-
const end = offset + segment.text.length;
|
|
8869
|
-
if (offset >= index && end <= index + length) {
|
|
8870
|
-
deletedText += segment.text;
|
|
8871
|
-
} else {
|
|
8872
|
-
result.push(segment);
|
|
8873
|
-
}
|
|
8874
|
-
offset = end;
|
|
8875
|
-
}
|
|
8876
|
-
return {
|
|
8877
|
-
segments: normalizeSegments(result),
|
|
8878
|
-
deletedText,
|
|
8879
|
-
deletedSegments
|
|
8880
|
-
};
|
|
8881
|
-
}
|
|
8882
|
-
function applyFormat(segments, index, length, attributes) {
|
|
8883
|
-
const split = splitSegmentsAt(
|
|
8884
|
-
splitSegmentsAt(segments, index),
|
|
8885
|
-
index + length
|
|
8886
|
-
);
|
|
8887
|
-
const result = [];
|
|
8888
|
-
let offset = 0;
|
|
8889
|
-
for (const segment of split) {
|
|
8890
|
-
const end = offset + segment.text.length;
|
|
8891
|
-
if (offset >= index && end <= index + length) {
|
|
8892
|
-
const nextAttributes = {
|
|
8893
|
-
...segment.attributes ?? {}
|
|
8894
|
-
};
|
|
8895
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
8896
|
-
if (value === null) {
|
|
8897
|
-
delete nextAttributes[key];
|
|
8898
|
-
} else {
|
|
8899
|
-
nextAttributes[key] = value;
|
|
8900
|
-
}
|
|
8901
|
-
}
|
|
8902
|
-
result.push({
|
|
8903
|
-
text: segment.text,
|
|
8904
|
-
attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
|
|
8905
|
-
});
|
|
8906
|
-
} else {
|
|
8907
|
-
result.push(segment);
|
|
8908
|
-
}
|
|
8909
|
-
offset = end;
|
|
8910
|
-
}
|
|
8911
|
-
return normalizeSegments(result);
|
|
8912
|
-
}
|
|
8913
|
-
function formatReverseOperations(segments, index, length, patch) {
|
|
8914
|
-
const split = splitSegmentsAt(
|
|
8915
|
-
splitSegmentsAt(segments, index),
|
|
8916
|
-
index + length
|
|
8917
|
-
);
|
|
8918
|
-
const result = [];
|
|
8919
|
-
let offset = 0;
|
|
8920
|
-
for (const segment of split) {
|
|
8921
|
-
const end = offset + segment.text.length;
|
|
8922
|
-
if (offset >= index && end <= index + length) {
|
|
8923
|
-
const attributes = {};
|
|
8924
|
-
for (const key of Object.keys(patch)) {
|
|
8925
|
-
attributes[key] = segment.attributes?.[key] ?? null;
|
|
8926
|
-
}
|
|
8927
|
-
result.push({
|
|
8928
|
-
type: "format",
|
|
8929
|
-
index: offset,
|
|
8930
|
-
length: segment.text.length,
|
|
8931
|
-
attributes
|
|
8932
|
-
});
|
|
8933
|
-
}
|
|
8934
|
-
offset = end;
|
|
8935
|
-
}
|
|
8936
|
-
return result;
|
|
8937
|
-
}
|
|
8938
|
-
function mapIndexThroughOperation(index, op) {
|
|
8939
|
-
if (op.type === "insert") {
|
|
8940
|
-
return op.index <= index ? index + op.text.length : index;
|
|
8941
|
-
} else if (op.type === "delete") {
|
|
8942
|
-
if (op.index >= index) {
|
|
8943
|
-
return index;
|
|
8944
|
-
}
|
|
8945
|
-
return Math.max(op.index, index - op.length);
|
|
8946
|
-
} else {
|
|
8947
|
-
return index;
|
|
8948
|
-
}
|
|
8949
|
-
}
|
|
8950
|
-
function mapTextIndexThroughOperations(index, ops) {
|
|
8951
|
-
let mapped = index;
|
|
8952
|
-
for (const op of ops) {
|
|
8953
|
-
mapped = mapIndexThroughOperation(mapped, op);
|
|
8954
|
-
}
|
|
8955
|
-
return mapped;
|
|
8956
|
-
}
|
|
8957
|
-
function inverseMapIndexThroughOperation(index, op) {
|
|
8958
|
-
if (op.type === "insert") {
|
|
8959
|
-
if (index <= op.index) {
|
|
8960
|
-
return index;
|
|
8961
|
-
}
|
|
8962
|
-
return Math.max(op.index, index - op.text.length);
|
|
8963
|
-
} else if (op.type === "delete") {
|
|
8964
|
-
return op.index <= index ? index + op.length : index;
|
|
8965
|
-
} else {
|
|
8966
|
-
return index;
|
|
8967
|
-
}
|
|
8968
|
-
}
|
|
8969
|
-
function inverseMapTextIndexThroughOperations(index, ops) {
|
|
8970
|
-
let mapped = index;
|
|
8971
|
-
for (let i = ops.length - 1; i >= 0; i--) {
|
|
8972
|
-
mapped = inverseMapIndexThroughOperation(mapped, ops[i]);
|
|
8973
|
-
}
|
|
8974
|
-
return mapped;
|
|
8975
|
-
}
|
|
8976
|
-
function oppositeOrder(order) {
|
|
8977
|
-
return order === "before" ? "after" : "before";
|
|
8978
|
-
}
|
|
8979
|
-
function mapIndexOverDelete(index, deleteIndex, deleteLength) {
|
|
8980
|
-
if (deleteIndex >= index) {
|
|
8981
|
-
return index;
|
|
8982
|
-
}
|
|
8983
|
-
return Math.max(deleteIndex, index - deleteLength);
|
|
8984
|
-
}
|
|
8985
|
-
function transformInsert(op, over, order) {
|
|
8986
|
-
if (over.type === "insert") {
|
|
8987
|
-
const shifts = over.index < op.index || over.index === op.index && order === "after";
|
|
8988
|
-
return [shifts ? { ...op, index: op.index + over.text.length } : { ...op }];
|
|
8989
|
-
} else if (over.type === "delete") {
|
|
8990
|
-
return [
|
|
8991
|
-
{ ...op, index: mapIndexOverDelete(op.index, over.index, over.length) }
|
|
8992
|
-
];
|
|
8993
|
-
} else {
|
|
8994
|
-
return [{ ...op }];
|
|
8995
|
-
}
|
|
8996
|
-
}
|
|
8997
|
-
function transformDelete(op, over) {
|
|
8998
|
-
const start = op.index;
|
|
8999
|
-
const end = op.index + op.length;
|
|
9000
|
-
if (over.type === "insert") {
|
|
9001
|
-
const at = over.index;
|
|
9002
|
-
const len = over.text.length;
|
|
9003
|
-
if (at <= start) {
|
|
9004
|
-
return [{ ...op, index: start + len }];
|
|
9005
|
-
}
|
|
9006
|
-
if (at >= end) {
|
|
9007
|
-
return [{ ...op }];
|
|
9008
|
-
}
|
|
9009
|
-
return [
|
|
9010
|
-
{ type: "delete", index: start, length: at - start },
|
|
9011
|
-
{ type: "delete", index: start + len, length: end - at }
|
|
9012
|
-
];
|
|
9013
|
-
} else if (over.type === "delete") {
|
|
9014
|
-
const newStart = mapIndexOverDelete(start, over.index, over.length);
|
|
9015
|
-
const newEnd = mapIndexOverDelete(end, over.index, over.length);
|
|
9016
|
-
return newEnd - newStart > 0 ? [{ type: "delete", index: newStart, length: newEnd - newStart }] : [];
|
|
9017
|
-
} else {
|
|
9018
|
-
return [{ ...op }];
|
|
9019
|
-
}
|
|
9020
|
-
}
|
|
9021
|
-
function transformFormat(op, over, order) {
|
|
9022
|
-
const start = op.index;
|
|
9023
|
-
const end = op.index + op.length;
|
|
9024
|
-
if (over.type === "insert") {
|
|
9025
|
-
const at = over.index;
|
|
9026
|
-
const len = over.text.length;
|
|
9027
|
-
if (at <= start) {
|
|
9028
|
-
return [{ ...op, index: start + len }];
|
|
9029
|
-
}
|
|
9030
|
-
if (at >= end) {
|
|
9031
|
-
return [{ ...op }];
|
|
9032
|
-
}
|
|
9033
|
-
return [
|
|
9034
|
-
{
|
|
9035
|
-
type: "format",
|
|
9036
|
-
index: start,
|
|
9037
|
-
length: at - start,
|
|
9038
|
-
attributes: op.attributes
|
|
9039
|
-
},
|
|
9040
|
-
{
|
|
9041
|
-
type: "format",
|
|
9042
|
-
index: at + len,
|
|
9043
|
-
length: end - at,
|
|
9044
|
-
attributes: op.attributes
|
|
9045
|
-
}
|
|
9046
|
-
];
|
|
9047
|
-
} else if (over.type === "delete") {
|
|
9048
|
-
const newStart = mapIndexOverDelete(start, over.index, over.length);
|
|
9049
|
-
const newEnd = mapIndexOverDelete(end, over.index, over.length);
|
|
9050
|
-
return newEnd - newStart > 0 ? [
|
|
9051
|
-
{
|
|
9052
|
-
type: "format",
|
|
9053
|
-
index: newStart,
|
|
9054
|
-
length: newEnd - newStart,
|
|
9055
|
-
attributes: op.attributes
|
|
9056
|
-
}
|
|
9057
|
-
] : [];
|
|
9058
|
-
} else {
|
|
9059
|
-
if (order === "after") {
|
|
9060
|
-
return [{ ...op }];
|
|
9061
|
-
}
|
|
9062
|
-
const overlapStart = Math.max(start, over.index);
|
|
9063
|
-
const overlapEnd = Math.min(end, over.index + over.length);
|
|
9064
|
-
if (overlapStart >= overlapEnd) {
|
|
9065
|
-
return [{ ...op }];
|
|
9066
|
-
}
|
|
9067
|
-
const hasConflict = Object.keys(op.attributes).some(
|
|
9068
|
-
(key) => key in over.attributes
|
|
9069
|
-
);
|
|
9070
|
-
if (!hasConflict) {
|
|
9071
|
-
return [{ ...op }];
|
|
9072
|
-
}
|
|
9073
|
-
const reduced = {};
|
|
9074
|
-
for (const [key, value] of Object.entries(op.attributes)) {
|
|
9075
|
-
if (!(key in over.attributes)) {
|
|
9076
|
-
reduced[key] = value;
|
|
9077
|
-
}
|
|
9078
|
-
}
|
|
9079
|
-
const pieces = [];
|
|
9080
|
-
if (start < overlapStart) {
|
|
9081
|
-
pieces.push({
|
|
9082
|
-
type: "format",
|
|
9083
|
-
index: start,
|
|
9084
|
-
length: overlapStart - start,
|
|
9085
|
-
attributes: op.attributes
|
|
9086
|
-
});
|
|
9087
|
-
}
|
|
9088
|
-
if (Object.keys(reduced).length > 0) {
|
|
9089
|
-
pieces.push({
|
|
9090
|
-
type: "format",
|
|
9091
|
-
index: overlapStart,
|
|
9092
|
-
length: overlapEnd - overlapStart,
|
|
9093
|
-
attributes: reduced
|
|
9094
|
-
});
|
|
9095
|
-
}
|
|
9096
|
-
if (overlapEnd < end) {
|
|
9097
|
-
pieces.push({
|
|
9098
|
-
type: "format",
|
|
9099
|
-
index: overlapEnd,
|
|
9100
|
-
length: end - overlapEnd,
|
|
9101
|
-
attributes: op.attributes
|
|
9102
|
-
});
|
|
9103
|
-
}
|
|
9104
|
-
return pieces;
|
|
9105
|
-
}
|
|
9106
|
-
}
|
|
9107
|
-
function transformSingle(op, over, order) {
|
|
9108
|
-
switch (op.type) {
|
|
9109
|
-
case "insert":
|
|
9110
|
-
return transformInsert(op, over, order);
|
|
9111
|
-
case "delete":
|
|
9112
|
-
return transformDelete(op, over);
|
|
9113
|
-
case "format":
|
|
9114
|
-
return transformFormat(op, over, order);
|
|
9115
|
-
}
|
|
9116
|
-
}
|
|
9117
|
-
function transformTextOperationsX(a, b, order) {
|
|
9118
|
-
if (a.length === 0 || b.length === 0) {
|
|
9119
|
-
return [[...a], [...b]];
|
|
9120
|
-
}
|
|
9121
|
-
if (a.length === 1 && b.length === 1) {
|
|
9122
|
-
return [
|
|
9123
|
-
transformSingle(a[0], b[0], order),
|
|
9124
|
-
transformSingle(b[0], a[0], oppositeOrder(order))
|
|
9125
|
-
];
|
|
9126
|
-
}
|
|
9127
|
-
if (a.length > 1) {
|
|
9128
|
-
const [headA1, b1] = transformTextOperationsX([a[0]], b, order);
|
|
9129
|
-
const [restA1, b2] = transformTextOperationsX(a.slice(1), b1, order);
|
|
9130
|
-
return [[...headA1, ...restA1], b2];
|
|
9131
|
-
}
|
|
9132
|
-
const [a1, headB1] = transformTextOperationsX(a, [b[0]], order);
|
|
9133
|
-
const [a2, restB1] = transformTextOperationsX(a1, b.slice(1), order);
|
|
9134
|
-
return [a2, [...headB1, ...restB1]];
|
|
9135
|
-
}
|
|
9136
|
-
function transformTextOperations(ops, over, order) {
|
|
9137
|
-
return transformTextOperationsX(ops, over, order)[0];
|
|
9138
|
-
}
|
|
9139
|
-
function textOperationsEqual(a, b) {
|
|
9140
|
-
return a === b || stableStringify(a) === stableStringify(b);
|
|
9141
|
-
}
|
|
9142
|
-
function applyTextOperationsToSegments(segments, ops) {
|
|
9143
|
-
let next = [...segments];
|
|
9144
|
-
for (const op of ops) {
|
|
9145
|
-
if (op.type === "insert") {
|
|
9146
|
-
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
9147
|
-
next = applyInsert(next, index, op.text, op.attributes);
|
|
9148
|
-
} else if (op.type === "delete") {
|
|
9149
|
-
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
9150
|
-
const clipped = clipRange(index, op.length, textLength(next));
|
|
9151
|
-
next = applyDelete(next, clipped.index, clipped.length).segments;
|
|
9152
|
-
} else {
|
|
9153
|
-
const index = Math.max(0, Math.min(op.index, textLength(next)));
|
|
9154
|
-
const clipped = clipRange(index, op.length, textLength(next));
|
|
9155
|
-
next = applyFormat(next, clipped.index, clipped.length, op.attributes);
|
|
9156
|
-
}
|
|
9157
|
-
}
|
|
9158
|
-
return next;
|
|
9159
|
-
}
|
|
9160
|
-
function applyLiveTextOperations(data, ops) {
|
|
9161
|
-
return segmentsToData(
|
|
9162
|
-
applyTextOperationsToSegments(dataToSegments(data), ops)
|
|
9163
|
-
);
|
|
9164
|
-
}
|
|
9165
|
-
function invertTextOperations(segments, ops) {
|
|
9166
|
-
let shadow = [...segments];
|
|
9167
|
-
const reverse = [];
|
|
9168
|
-
for (const op of ops) {
|
|
9169
|
-
if (op.type === "insert") {
|
|
9170
|
-
shadow = applyInsert(shadow, op.index, op.text, op.attributes);
|
|
9171
|
-
reverse.unshift({
|
|
9172
|
-
type: "delete",
|
|
9173
|
-
index: op.index,
|
|
9174
|
-
length: op.text.length
|
|
9175
|
-
});
|
|
9176
|
-
} else if (op.type === "delete") {
|
|
9177
|
-
const deletedSegments = extractDeletedSegments(
|
|
9178
|
-
shadow,
|
|
9179
|
-
op.index,
|
|
9180
|
-
op.length
|
|
9181
|
-
);
|
|
9182
|
-
shadow = applyDelete(shadow, op.index, op.length).segments;
|
|
9183
|
-
const inserts = [];
|
|
9184
|
-
let insertIndex = op.index;
|
|
9185
|
-
for (const segment of deletedSegments) {
|
|
9186
|
-
inserts.push({
|
|
9187
|
-
type: "insert",
|
|
9188
|
-
index: insertIndex,
|
|
9189
|
-
text: segment.text,
|
|
9190
|
-
attributes: segment.attributes
|
|
9191
|
-
});
|
|
9192
|
-
insertIndex += segment.text.length;
|
|
9193
|
-
}
|
|
9194
|
-
for (let index = inserts.length - 1; index >= 0; index--) {
|
|
9195
|
-
reverse.unshift(inserts[index]);
|
|
9196
|
-
}
|
|
9197
|
-
} else {
|
|
9198
|
-
const inverse = formatReverseOperations(
|
|
9199
|
-
shadow,
|
|
9200
|
-
op.index,
|
|
9201
|
-
op.length,
|
|
9202
|
-
op.attributes
|
|
9203
|
-
);
|
|
9204
|
-
shadow = applyFormat(shadow, op.index, op.length, op.attributes);
|
|
9205
|
-
reverse.unshift(...inverse.reverse());
|
|
9206
|
-
}
|
|
9207
|
-
}
|
|
9208
|
-
return reverse;
|
|
9209
|
-
}
|
|
9210
|
-
|
|
9211
|
-
// src/crdts/LiveText.ts
|
|
9212
|
-
var ACCEPTED_OPS_HISTORY_LIMIT = 1e3;
|
|
9213
|
-
var LiveText = class _LiveText extends AbstractCrdt {
|
|
9214
|
-
/** The local document: #confirmed ⊕ #inFlightOps ⊕ #queuedOps. */
|
|
9215
|
-
#segments;
|
|
9216
|
-
/** The server-confirmed document (only authoritative ops applied). */
|
|
9217
|
-
#confirmed;
|
|
9218
|
-
#version;
|
|
9219
|
-
/** The op currently awaiting server acknowledgement (at most one). */
|
|
9220
|
-
#inFlightOpId;
|
|
9221
|
-
/** Its ops, continuously re-expressed against current server state. */
|
|
9222
|
-
#inFlightOps = [];
|
|
9223
|
-
/** Local edits made while an op is in flight; sent after the ack. */
|
|
9224
|
-
#queuedOps = [];
|
|
9225
|
-
#acceptedOps = [];
|
|
9226
|
-
/**
|
|
9227
|
-
* Creates a new LiveText document.
|
|
9228
|
-
*
|
|
9229
|
-
* @param textOrData Initial plain text, or an array of `[text]` /
|
|
9230
|
-
* `[text, attributes]` segments. Defaults to an empty document.
|
|
9231
|
-
*
|
|
9232
|
-
* @example
|
|
9233
|
-
* new LiveText();
|
|
9234
|
-
* new LiveText("Hello world");
|
|
9235
|
-
* new LiveText([["Hello ", { bold: true }], ["world"]]);
|
|
9236
|
-
*/
|
|
9237
|
-
constructor(textOrData = "", version = 0) {
|
|
9238
|
-
super();
|
|
9239
|
-
this.#segments = typeof textOrData === "string" ? textOrData.length === 0 ? [] : [{ text: textOrData }] : dataToSegments(textOrData);
|
|
9240
|
-
this.#confirmed = [...this.#segments];
|
|
9241
|
-
this.#version = version;
|
|
9242
|
-
Object.defineProperty(this, kInternal, {
|
|
9243
|
-
value: {
|
|
9244
|
-
encodeIndex: (localIndex) => this.#encodeIndex(localIndex),
|
|
9245
|
-
decodeIndex: (index, fromVersion) => this.#decodeIndex(index, fromVersion)
|
|
9246
|
-
},
|
|
9247
|
-
enumerable: false
|
|
9248
|
-
});
|
|
9249
|
-
}
|
|
9250
|
-
get version() {
|
|
9251
|
-
return this.#version;
|
|
9252
|
-
}
|
|
9253
|
-
get length() {
|
|
9254
|
-
return textLength(this.#segments);
|
|
9255
|
-
}
|
|
9256
|
-
/** @internal */
|
|
9257
|
-
static _deserialize([id, item], _parentToChildren, pool) {
|
|
9258
|
-
const text = new _LiveText(item.data, item.version);
|
|
9259
|
-
text._attach(id, pool);
|
|
9260
|
-
return text;
|
|
9261
|
-
}
|
|
9262
|
-
/** @internal */
|
|
9263
|
-
_toOps(parentId, parentKey) {
|
|
9264
|
-
if (this._id === void 0) {
|
|
9265
|
-
throw new Error("Cannot serialize LiveText if it is not attached");
|
|
9266
|
-
}
|
|
9267
|
-
return [
|
|
9268
|
-
{
|
|
9269
|
-
type: OpCode.CREATE_TEXT,
|
|
9270
|
-
id: this._id,
|
|
9271
|
-
parentId,
|
|
9272
|
-
parentKey,
|
|
9273
|
-
data: this.toJSON(),
|
|
9274
|
-
version: this.#version
|
|
9275
|
-
}
|
|
9276
|
-
];
|
|
9277
|
-
}
|
|
9278
|
-
/** @internal */
|
|
9279
|
-
_serialize() {
|
|
9280
|
-
if (this.parent.type !== "HasParent") {
|
|
9281
|
-
throw new Error("Cannot serialize LiveText if parent is missing");
|
|
9282
|
-
}
|
|
9283
|
-
return {
|
|
9284
|
-
type: CrdtType.TEXT,
|
|
9285
|
-
parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
|
|
9286
|
-
parentKey: this.parent.key,
|
|
9287
|
-
data: this.toJSON(),
|
|
9288
|
-
version: this.#version
|
|
9289
|
-
};
|
|
9290
|
-
}
|
|
9291
|
-
/** @internal */
|
|
9292
|
-
_attachChild(_op) {
|
|
9293
|
-
throw new Error("LiveText cannot contain child nodes");
|
|
9294
|
-
}
|
|
9295
|
-
/** @internal */
|
|
9296
|
-
_detachChild(_crdt) {
|
|
9297
|
-
throw new Error("LiveText cannot contain child nodes");
|
|
9298
|
-
}
|
|
9299
|
-
/** @internal */
|
|
9300
|
-
_apply(op, isLocal) {
|
|
9301
|
-
if (op.type !== OpCode.UPDATE_TEXT) {
|
|
9302
|
-
return super._apply(op, isLocal);
|
|
9303
|
-
}
|
|
9304
|
-
if (isLocal) {
|
|
9305
|
-
return this.#applyLocal(op);
|
|
9306
|
-
}
|
|
9307
|
-
if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
|
|
9308
|
-
return this.#applyAck(op);
|
|
9309
|
-
}
|
|
9310
|
-
if (op.opId !== void 0 && this.#acceptedOps.some((entry) => entry.opId === op.opId)) {
|
|
9311
|
-
this.#version = Math.max(this.#version, op.version ?? op.baseVersion + 1);
|
|
9312
|
-
return { modified: false };
|
|
9313
|
-
}
|
|
9314
|
-
return this.#applyRemote(op);
|
|
9315
|
-
}
|
|
9316
|
-
/**
|
|
9317
|
-
* Inserts text at the given index.
|
|
9318
|
-
*
|
|
9319
|
-
* @param index Character index at which to insert. Values outside the
|
|
9320
|
-
* document range are clipped.
|
|
9321
|
-
* @param text Text to insert.
|
|
9322
|
-
* @param attributes Optional inline attributes for the inserted text.
|
|
9323
|
-
*
|
|
9324
|
-
* @example
|
|
9325
|
-
* const text = new LiveText("Hello");
|
|
9326
|
-
* text.insert(5, " world");
|
|
9327
|
-
* text.insert(0, "Say: ", { italic: true });
|
|
9328
|
-
*/
|
|
9329
|
-
insert(index, text, attributes) {
|
|
9330
|
-
const clippedIndex = Math.max(0, Math.min(index, this.length));
|
|
9331
|
-
this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
|
|
9332
|
-
}
|
|
9333
|
-
/**
|
|
9334
|
-
* Deletes `length` characters starting at `index`.
|
|
9335
|
-
*
|
|
9336
|
-
* @example
|
|
9337
|
-
* const text = new LiveText("Hello world");
|
|
9338
|
-
* text.delete(5, 6); // "Hello"
|
|
9339
|
-
*/
|
|
9340
|
-
delete(index, length) {
|
|
9341
|
-
const clipped = clipRange(index, length, this.length);
|
|
9342
|
-
if (clipped.length === 0) {
|
|
9343
|
-
return;
|
|
9344
|
-
}
|
|
9345
|
-
this.#dispatch([
|
|
9346
|
-
{ type: "delete", index: clipped.index, length: clipped.length }
|
|
9347
|
-
]);
|
|
9348
|
-
}
|
|
9349
|
-
/**
|
|
9350
|
-
* Replaces a range of text with new text.
|
|
9351
|
-
*
|
|
9352
|
-
* @example
|
|
9353
|
-
* const text = new LiveText("Hello world");
|
|
9354
|
-
* text.replace(0, 5, "Hi"); // "Hi world"
|
|
9355
|
-
*/
|
|
9356
|
-
replace(index, length, text, attributes) {
|
|
9357
|
-
const clipped = clipRange(index, length, this.length);
|
|
9358
|
-
const ops = [];
|
|
9359
|
-
if (clipped.length > 0) {
|
|
9360
|
-
ops.push({
|
|
9361
|
-
type: "delete",
|
|
9362
|
-
index: clipped.index,
|
|
9363
|
-
length: clipped.length
|
|
9364
|
-
});
|
|
9365
|
-
}
|
|
9366
|
-
if (text.length > 0) {
|
|
9367
|
-
ops.push({ type: "insert", index: clipped.index, text, attributes });
|
|
9368
|
-
}
|
|
9369
|
-
this.#dispatch(ops);
|
|
9370
|
-
}
|
|
9371
|
-
/**
|
|
9372
|
-
* Encode a local-document index (an offset into this LiveText's current
|
|
9373
|
-
* #segments, which CodeMirror or any consumer mirrors as its document)
|
|
9374
|
-
* into server-confirmed coordinates suitable for broadcasting to peers via
|
|
9375
|
-
* presence or any other side channel.
|
|
9376
|
-
*
|
|
9377
|
-
* The returned index is in this LiveText's current #confirmed coordinates
|
|
9378
|
-
* — that is, with this client's local pending ops inverse-mapped out.
|
|
9379
|
-
* Pair it with the current {@link LiveText.version} when sending so the
|
|
9380
|
-
* receiver can call {@link PrivateLiveTextApi.decodeIndex} to land the
|
|
9381
|
-
* position in their own local document coordinates regardless of their
|
|
9382
|
-
* private pending ops.
|
|
9383
|
-
*
|
|
9384
|
-
* Index ambiguity at boundaries is resolved by an inverse-of-forward
|
|
9385
|
-
* convention: a position at or before a local insertion is reported as
|
|
9386
|
-
* the position right before the insertion in #confirmed; a position past
|
|
9387
|
-
* the insertion shifts left by the insertion's length. Positions inside
|
|
9388
|
-
* an own-pending insertion collapse to the insertion point.
|
|
9389
|
-
*/
|
|
9390
|
-
#encodeIndex(localIndex) {
|
|
9391
|
-
let mapped = Math.max(0, Math.min(localIndex, this.length));
|
|
9392
|
-
mapped = inverseMapTextIndexThroughOperations(mapped, this.#queuedOps);
|
|
9393
|
-
mapped = inverseMapTextIndexThroughOperations(mapped, this.#inFlightOps);
|
|
9394
|
-
return mapped;
|
|
9395
|
-
}
|
|
9396
|
-
/**
|
|
9397
|
-
* Decode an `(index, fromVersion)` pair produced by
|
|
9398
|
-
* {@link PrivateLiveTextApi.encodeIndex} — typically on a peer — into an
|
|
9399
|
-
* offset in this LiveText's current local document (an index suitable for
|
|
9400
|
-
* placing a CodeMirror marker, an annotation anchor, or anything else that
|
|
9401
|
-
* lives over #segments).
|
|
9402
|
-
*
|
|
9403
|
-
* Composes the accepted ops applied since `fromVersion` (drawn from
|
|
9404
|
-
* #acceptedOps in locally-applied form) with this client's own local
|
|
9405
|
-
* pending ops, in that order. The result is in current #segments
|
|
9406
|
-
* coordinates.
|
|
9407
|
-
*
|
|
9408
|
-
* Returns `null` when the position cannot be decoded against the current
|
|
9409
|
-
* state:
|
|
9410
|
-
* - `fromVersion` is greater than this LiveText's current version: the
|
|
9411
|
-
* peer is ahead of us. The caller should park the message and retry
|
|
9412
|
-
* after more accepted ops arrive.
|
|
9413
|
-
* - `fromVersion` falls outside the retained accepted-ops history. This
|
|
9414
|
-
* only happens after very long-lived disconnections; the caller can
|
|
9415
|
-
* fall back to using the raw index and letting subsequent local
|
|
9416
|
-
* transactions map it (with bounded drift).
|
|
9417
|
-
*/
|
|
9418
|
-
#decodeIndex(index, fromVersion) {
|
|
9419
|
-
if (fromVersion > this.#version) {
|
|
9420
|
-
return null;
|
|
9421
|
-
}
|
|
9422
|
-
if (fromVersion < this.#version) {
|
|
9423
|
-
const oldest = this.#acceptedOps[0]?.version;
|
|
9424
|
-
if (oldest === void 0 || oldest > fromVersion + 1) {
|
|
9425
|
-
return null;
|
|
9426
|
-
}
|
|
9427
|
-
}
|
|
9428
|
-
let mapped = index;
|
|
9429
|
-
for (const entry of this.#acceptedOps) {
|
|
9430
|
-
if (entry.version <= fromVersion) continue;
|
|
9431
|
-
if (entry.version > this.#version) break;
|
|
9432
|
-
if (entry.ops.length === 0) continue;
|
|
9433
|
-
mapped = mapTextIndexThroughOperations(mapped, entry.ops);
|
|
9434
|
-
}
|
|
9435
|
-
mapped = mapTextIndexThroughOperations(mapped, this.#inFlightOps);
|
|
9436
|
-
mapped = mapTextIndexThroughOperations(mapped, this.#queuedOps);
|
|
9437
|
-
return Math.max(0, Math.min(mapped, this.length));
|
|
9438
|
-
}
|
|
9439
|
-
/**
|
|
9440
|
-
* Applies or removes inline attributes on a range of text.
|
|
9441
|
-
*
|
|
9442
|
-
* Set an attribute to `null` to remove it from the range.
|
|
9443
|
-
*
|
|
9444
|
-
* @example
|
|
9445
|
-
* const text = new LiveText("Hello world");
|
|
9446
|
-
* text.format(0, 5, { bold: true });
|
|
9447
|
-
* text.format(0, 5, { bold: null });
|
|
9448
|
-
*/
|
|
9449
|
-
format(index, length, attributes) {
|
|
9450
|
-
const clipped = clipRange(index, length, this.length);
|
|
9451
|
-
if (clipped.length === 0) {
|
|
9452
|
-
return;
|
|
9453
|
-
}
|
|
9454
|
-
this.#dispatch([
|
|
9455
|
-
{
|
|
9456
|
-
type: "format",
|
|
9457
|
-
index: clipped.index,
|
|
9458
|
-
length: clipped.length,
|
|
9459
|
-
attributes
|
|
9460
|
-
}
|
|
9461
|
-
]);
|
|
9462
|
-
}
|
|
9463
|
-
/** Local edits made through the public API. */
|
|
9464
|
-
#dispatch(ops) {
|
|
9465
|
-
if (ops.length === 0) {
|
|
9466
|
-
return;
|
|
9467
|
-
}
|
|
9468
|
-
this._pool?.assertStorageIsWritable();
|
|
9469
|
-
const attached = this._pool !== void 0 && this._id !== void 0;
|
|
9470
|
-
const reverse = attached ? this.#invertOperations(ops) : [];
|
|
9471
|
-
const changes = this.#applyOperationsLocally(ops);
|
|
9472
|
-
if (!attached) {
|
|
9473
|
-
return;
|
|
9474
|
-
}
|
|
9475
|
-
const pool = nn(this._pool);
|
|
9476
|
-
const id = nn(this._id);
|
|
9477
|
-
const updates = /* @__PURE__ */ new Map([
|
|
9478
|
-
[
|
|
9479
|
-
id,
|
|
9480
|
-
{
|
|
9481
|
-
type: "LiveText",
|
|
9482
|
-
node: this,
|
|
9483
|
-
version: this.#version,
|
|
9484
|
-
updates: changes
|
|
9485
|
-
}
|
|
9486
|
-
]
|
|
9487
|
-
]);
|
|
9488
|
-
if (this.#inFlightOpId === void 0) {
|
|
9489
|
-
const opId = pool.generateOpId();
|
|
9490
|
-
this.#inFlightOpId = opId;
|
|
9491
|
-
this.#inFlightOps = [...ops];
|
|
9492
|
-
pool.dispatch(
|
|
9493
|
-
[
|
|
9494
|
-
{
|
|
9495
|
-
type: OpCode.UPDATE_TEXT,
|
|
9496
|
-
id,
|
|
9497
|
-
opId,
|
|
9498
|
-
baseVersion: this.#version,
|
|
9499
|
-
ops: [...ops]
|
|
9500
|
-
}
|
|
9501
|
-
],
|
|
9502
|
-
reverse,
|
|
9503
|
-
updates
|
|
9504
|
-
);
|
|
9505
|
-
} else {
|
|
9506
|
-
this.#queuedOps.push(...ops);
|
|
9507
|
-
pool.dispatch([], reverse, updates, { clearRedoStack: true });
|
|
9508
|
-
}
|
|
9509
|
-
}
|
|
9510
|
-
/**
|
|
9511
|
-
* A local replay of an existing wire op: an undo/redo frame, or an
|
|
9512
|
-
* unacknowledged op re-sent after a reconnect.
|
|
9513
|
-
*/
|
|
9514
|
-
#applyLocal(op) {
|
|
9515
|
-
const mutableOp = op;
|
|
9516
|
-
if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
|
|
9517
|
-
this.#inFlightOps = [...this.#inFlightOps, ...this.#queuedOps];
|
|
9518
|
-
this.#queuedOps = [];
|
|
9519
|
-
mutableOp.baseVersion = this.#version;
|
|
9520
|
-
mutableOp.ops = [...this.#inFlightOps];
|
|
9521
|
-
return { modified: false };
|
|
9522
|
-
}
|
|
9523
|
-
let ops = op.ops;
|
|
9524
|
-
for (const entry of this.#acceptedOps) {
|
|
9525
|
-
if (entry.version > op.baseVersion && entry.ops.length > 0) {
|
|
9526
|
-
ops = transformTextOperations(ops, entry.ops, "after");
|
|
9527
|
-
}
|
|
9528
|
-
}
|
|
9529
|
-
const reverse = this.#invertOperations(ops);
|
|
9530
|
-
const changes = this.#applyOperationsLocally(ops);
|
|
9531
|
-
if (this.#inFlightOpId === void 0 && ops.length > 0) {
|
|
9532
|
-
this.#inFlightOpId = nn(op.opId, "Local ops must have an opId");
|
|
9533
|
-
this.#inFlightOps = [...ops];
|
|
9534
|
-
mutableOp.baseVersion = this.#version;
|
|
9535
|
-
mutableOp.ops = [...ops];
|
|
9536
|
-
} else {
|
|
9537
|
-
this.#queuedOps.push(...ops);
|
|
9538
|
-
mutableOp.baseVersion = this.#version;
|
|
9539
|
-
mutableOp.ops = [];
|
|
9540
|
-
}
|
|
9541
|
-
if (changes.length === 0) {
|
|
9542
|
-
return { modified: false };
|
|
9543
|
-
}
|
|
9544
|
-
return {
|
|
9545
|
-
reverse,
|
|
9546
|
-
modified: {
|
|
9547
|
-
type: "LiveText",
|
|
9548
|
-
node: this,
|
|
9549
|
-
version: this.#version,
|
|
9550
|
-
updates: changes
|
|
9551
|
-
}
|
|
9552
|
-
};
|
|
9553
|
-
}
|
|
9554
|
-
/** Server acknowledgement of our in-flight op. */
|
|
9555
|
-
#applyAck(op) {
|
|
9556
|
-
const ackedVersion = op.version ?? Math.max(this.#version, op.baseVersion + 1);
|
|
9557
|
-
const predicted = this.#inFlightOps;
|
|
9558
|
-
const opId = this.#inFlightOpId;
|
|
9559
|
-
this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
|
|
9560
|
-
this.#inFlightOpId = void 0;
|
|
9561
|
-
this.#inFlightOps = [];
|
|
9562
|
-
let appliedOps = [];
|
|
9563
|
-
let result = { modified: false };
|
|
9564
|
-
if (!textOperationsEqual(op.ops, predicted)) {
|
|
9565
|
-
error2(
|
|
9566
|
-
"LiveText: acknowledgement did not match the local prediction; resynchronizing"
|
|
9567
|
-
);
|
|
9568
|
-
const rebuilt = this.#rebuildLocalFromConfirmed();
|
|
9569
|
-
appliedOps = rebuilt.appliedOps;
|
|
9570
|
-
if (rebuilt.changes.length > 0) {
|
|
9571
|
-
result = {
|
|
9572
|
-
reverse: [],
|
|
9573
|
-
modified: {
|
|
9574
|
-
type: "LiveText",
|
|
9575
|
-
node: this,
|
|
9576
|
-
version: ackedVersion,
|
|
9577
|
-
updates: rebuilt.changes
|
|
9578
|
-
}
|
|
9579
|
-
};
|
|
9580
|
-
}
|
|
9581
|
-
}
|
|
9582
|
-
this.#version = Math.max(this.#version, ackedVersion);
|
|
9583
|
-
this.#recordAccepted(ackedVersion, appliedOps, opId);
|
|
9584
|
-
this.#flushQueued();
|
|
9585
|
-
return result;
|
|
9586
|
-
}
|
|
9587
|
-
/** An accepted op from another client (or a server-fabricated fix op). */
|
|
9588
|
-
#applyRemote(op) {
|
|
9589
|
-
const version = op.version ?? this.#version + 1;
|
|
9590
|
-
this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
|
|
9591
|
-
const [overInFlight, inFlight] = transformTextOperationsX(
|
|
9592
|
-
op.ops,
|
|
9593
|
-
this.#inFlightOps,
|
|
9594
|
-
"before"
|
|
9595
|
-
);
|
|
9596
|
-
const [applied, queued] = transformTextOperationsX(
|
|
9597
|
-
overInFlight,
|
|
9598
|
-
this.#queuedOps,
|
|
9599
|
-
"before"
|
|
9600
|
-
);
|
|
9601
|
-
this.#inFlightOps = inFlight;
|
|
9602
|
-
this.#queuedOps = queued;
|
|
9603
|
-
this.#recordAccepted(version, applied, op.opId);
|
|
9604
|
-
if (applied.length === 0) {
|
|
9605
|
-
this.#version = Math.max(this.#version, version);
|
|
9606
|
-
return { modified: false };
|
|
9607
|
-
}
|
|
9608
|
-
const reverse = this.#invertOperations(applied);
|
|
9609
|
-
const changes = this.#applyOperationsLocally(applied);
|
|
9610
|
-
this.#version = Math.max(this.#version, version);
|
|
9611
|
-
return {
|
|
9612
|
-
reverse,
|
|
9613
|
-
modified: {
|
|
9614
|
-
type: "LiveText",
|
|
9615
|
-
node: this,
|
|
9616
|
-
version: this.#version,
|
|
9617
|
-
updates: changes
|
|
9618
|
-
}
|
|
9619
|
-
};
|
|
9620
|
-
}
|
|
9621
|
-
/** Send the queued ops as the next in-flight op (after an ack). */
|
|
9622
|
-
#flushQueued() {
|
|
9623
|
-
if (this.#queuedOps.length === 0 || this._pool === void 0 || this._id === void 0) {
|
|
9624
|
-
return;
|
|
9625
|
-
}
|
|
9626
|
-
const opId = this._pool.generateOpId();
|
|
9627
|
-
this.#inFlightOpId = opId;
|
|
9628
|
-
this.#inFlightOps = this.#queuedOps;
|
|
9629
|
-
this.#queuedOps = [];
|
|
9630
|
-
this._pool.dispatch(
|
|
9631
|
-
[
|
|
9632
|
-
{
|
|
9633
|
-
type: OpCode.UPDATE_TEXT,
|
|
9634
|
-
id: this._id,
|
|
9635
|
-
opId,
|
|
9636
|
-
baseVersion: this.#version,
|
|
9637
|
-
ops: [...this.#inFlightOps]
|
|
9638
|
-
}
|
|
9639
|
-
],
|
|
9640
|
-
[],
|
|
9641
|
-
/* @__PURE__ */ new Map(),
|
|
9642
|
-
// The local content was already applied (and made undoable) when the
|
|
9643
|
-
// edits happened; this is purely an outbound flush.
|
|
9644
|
-
{ clearRedoStack: false }
|
|
9645
|
-
);
|
|
9646
|
-
}
|
|
9647
|
-
/**
|
|
9648
|
-
* Rebuild the local document as confirmed ⊕ queued ops, returning the
|
|
9649
|
-
* coarse delta that was applied. Only used by defensive recovery paths.
|
|
9650
|
-
*/
|
|
9651
|
-
#rebuildLocalFromConfirmed() {
|
|
9652
|
-
const before2 = this.#segments;
|
|
9653
|
-
const after2 = applyTextOperationsToSegments(this.#confirmed, [
|
|
9654
|
-
...this.#inFlightOps,
|
|
9655
|
-
...this.#queuedOps
|
|
9656
|
-
]);
|
|
9657
|
-
if (stableStringify(segmentsToData(before2)) === stableStringify(segmentsToData(after2))) {
|
|
9658
|
-
this.#segments = after2;
|
|
9659
|
-
return { appliedOps: [], changes: [] };
|
|
9660
|
-
}
|
|
9661
|
-
const beforeText = before2.map((segment) => segment.text).join("");
|
|
9662
|
-
this.#segments = after2;
|
|
9663
|
-
this.invalidate();
|
|
9664
|
-
const appliedOps = [];
|
|
9665
|
-
const changes = [];
|
|
9666
|
-
if (beforeText.length > 0) {
|
|
9667
|
-
appliedOps.push({ type: "delete", index: 0, length: beforeText.length });
|
|
9668
|
-
changes.push({
|
|
9669
|
-
type: "delete",
|
|
9670
|
-
index: 0,
|
|
9671
|
-
length: beforeText.length,
|
|
9672
|
-
deletedText: beforeText
|
|
9673
|
-
});
|
|
9674
|
-
}
|
|
9675
|
-
let index = 0;
|
|
9676
|
-
for (const segment of after2) {
|
|
9677
|
-
appliedOps.push({
|
|
9678
|
-
type: "insert",
|
|
9679
|
-
index,
|
|
9680
|
-
text: segment.text,
|
|
9681
|
-
attributes: segment.attributes
|
|
9682
|
-
});
|
|
9683
|
-
changes.push({
|
|
9684
|
-
type: "insert",
|
|
9685
|
-
index,
|
|
9686
|
-
text: segment.text,
|
|
9687
|
-
attributes: segment.attributes
|
|
9688
|
-
});
|
|
9689
|
-
index += segment.text.length;
|
|
9690
|
-
}
|
|
9691
|
-
return { appliedOps, changes };
|
|
9692
|
-
}
|
|
9693
|
-
/**
|
|
9694
|
-
* Reconcile this node against an authoritative storage snapshot (e.g.
|
|
9695
|
-
* after a reconnect). The confirmed state and version are replaced by the
|
|
9696
|
-
* snapshot's; pending (in-flight + queued) ops are preserved on top and
|
|
9697
|
-
* will be re-sent by the offline-ops replay.
|
|
9698
|
-
*
|
|
9699
|
-
* @internal
|
|
9700
|
-
*/
|
|
9701
|
-
_resyncText(data, version) {
|
|
9702
|
-
this.#confirmed = dataToSegments(data);
|
|
9703
|
-
this.#version = version;
|
|
9704
|
-
this.#acceptedOps = [];
|
|
9705
|
-
const rebuilt = this.#rebuildLocalFromConfirmed();
|
|
9706
|
-
if (rebuilt.changes.length === 0) {
|
|
9707
|
-
return void 0;
|
|
9708
|
-
}
|
|
9709
|
-
return {
|
|
9710
|
-
type: "LiveText",
|
|
9711
|
-
node: this,
|
|
9712
|
-
version: this.#version,
|
|
9713
|
-
updates: rebuilt.changes
|
|
9714
|
-
};
|
|
9715
|
-
}
|
|
9716
|
-
/**
|
|
9717
|
-
* Called when the server rejected one of our ops. Drops all pending state
|
|
9718
|
-
* for this node (edits queued behind a rejected op cannot be trusted
|
|
9719
|
-
* either); the room follows up with a storage resync.
|
|
9720
|
-
*
|
|
9721
|
-
* @internal
|
|
9722
|
-
*/
|
|
9723
|
-
_rejectPendingOp(opId) {
|
|
9724
|
-
if (opId !== this.#inFlightOpId) {
|
|
9725
|
-
return;
|
|
9726
|
-
}
|
|
9727
|
-
this.#inFlightOpId = void 0;
|
|
9728
|
-
this.#inFlightOps = [];
|
|
9729
|
-
this.#queuedOps = [];
|
|
9730
|
-
}
|
|
9731
|
-
#recordAccepted(version, ops, opId) {
|
|
9732
|
-
if (this.#acceptedOps.some((entry) => entry.version === version)) {
|
|
9733
|
-
return;
|
|
9734
|
-
}
|
|
9735
|
-
this.#acceptedOps.push({ version, opId, ops: [...ops] });
|
|
9736
|
-
this.#acceptedOps.sort((left, right) => left.version - right.version);
|
|
9737
|
-
if (this.#acceptedOps.length > ACCEPTED_OPS_HISTORY_LIMIT) {
|
|
9738
|
-
this.#acceptedOps.splice(
|
|
9739
|
-
0,
|
|
9740
|
-
this.#acceptedOps.length - ACCEPTED_OPS_HISTORY_LIMIT
|
|
9741
|
-
);
|
|
9742
|
-
}
|
|
9743
|
-
}
|
|
9744
|
-
#applyOperationsLocally(ops) {
|
|
9745
|
-
const changes = [];
|
|
9746
|
-
for (const op of ops) {
|
|
9747
|
-
if (op.type === "insert") {
|
|
9748
|
-
this.#segments = applyInsert(
|
|
9749
|
-
this.#segments,
|
|
9750
|
-
op.index,
|
|
9751
|
-
op.text,
|
|
9752
|
-
op.attributes
|
|
9753
|
-
);
|
|
9754
|
-
changes.push({
|
|
9755
|
-
type: "insert",
|
|
9756
|
-
index: op.index,
|
|
9757
|
-
text: op.text,
|
|
9758
|
-
attributes: op.attributes
|
|
9759
|
-
});
|
|
9760
|
-
} else if (op.type === "delete") {
|
|
9761
|
-
const result = applyDelete(this.#segments, op.index, op.length);
|
|
9762
|
-
this.#segments = result.segments;
|
|
9763
|
-
changes.push({
|
|
9764
|
-
type: "delete",
|
|
9765
|
-
index: op.index,
|
|
9766
|
-
length: op.length,
|
|
9767
|
-
deletedText: result.deletedText
|
|
9768
|
-
});
|
|
9769
|
-
} else {
|
|
9770
|
-
this.#segments = applyFormat(
|
|
9771
|
-
this.#segments,
|
|
9772
|
-
op.index,
|
|
9773
|
-
op.length,
|
|
9774
|
-
op.attributes
|
|
9775
|
-
);
|
|
9776
|
-
changes.push({
|
|
9777
|
-
type: "format",
|
|
9778
|
-
index: op.index,
|
|
9779
|
-
length: op.length,
|
|
9780
|
-
attributes: op.attributes
|
|
9781
|
-
});
|
|
9782
|
-
}
|
|
9783
|
-
}
|
|
9784
|
-
this.invalidate();
|
|
9785
|
-
return changes;
|
|
9786
|
-
}
|
|
9787
|
-
#invertOperations(ops) {
|
|
9788
|
-
return [
|
|
9789
|
-
{
|
|
9790
|
-
type: OpCode.UPDATE_TEXT,
|
|
9791
|
-
id: nn(this._id),
|
|
9792
|
-
baseVersion: this.#version,
|
|
9793
|
-
ops: invertTextOperations(this.#segments, ops)
|
|
9794
|
-
}
|
|
9795
|
-
];
|
|
9796
|
-
}
|
|
9797
|
-
/** Returns the plain text content without attributes. Equivalent to joining the text from each segment in {@link LiveText.toJSON}. */
|
|
9798
|
-
toString() {
|
|
9799
|
-
return this.#segments.map((segment) => segment.text).join("");
|
|
9800
|
-
}
|
|
9801
|
-
/**
|
|
9802
|
-
* Returns a JSON-compatible snapshot of the document as a {@link LiveTextData}
|
|
9803
|
-
* array.
|
|
9804
|
-
*
|
|
9805
|
-
* @example
|
|
9806
|
-
* new LiveText([["Hello ", { bold: true }], ["world"]]).toJSON();
|
|
9807
|
-
* // [["Hello ", { bold: true }], ["world"]]
|
|
9808
|
-
*/
|
|
9809
|
-
toJSON() {
|
|
9810
|
-
return super.toJSON();
|
|
9811
|
-
}
|
|
9812
|
-
/** @internal */
|
|
9813
|
-
_toJSON() {
|
|
9814
|
-
return segmentsToData(this.#segments);
|
|
9815
|
-
}
|
|
9816
|
-
/** @internal */
|
|
9817
|
-
_toTreeNode(key) {
|
|
9818
|
-
return {
|
|
9819
|
-
type: "LiveText",
|
|
9820
|
-
id: this._id ?? nanoid(),
|
|
9821
|
-
key,
|
|
9822
|
-
payload: [
|
|
9823
|
-
{
|
|
9824
|
-
type: "Json",
|
|
9825
|
-
id: `${this._id ?? nanoid()}:text`,
|
|
9826
|
-
key: "text",
|
|
9827
|
-
payload: this.toString()
|
|
9828
|
-
}
|
|
9829
|
-
]
|
|
9830
|
-
};
|
|
9831
|
-
}
|
|
9832
|
-
clone() {
|
|
9833
|
-
return new _LiveText(this.toJSON(), this.#version);
|
|
9834
|
-
}
|
|
9835
|
-
};
|
|
9836
|
-
|
|
9837
8844
|
// src/crdts/liveblocks-helpers.ts
|
|
9838
8845
|
function creationOpToLiveNode(op) {
|
|
9839
8846
|
return lsonToLiveNode(creationOpToLson(op));
|
|
@@ -9848,8 +8855,6 @@ function creationOpToLson(op) {
|
|
|
9848
8855
|
return new LiveMap();
|
|
9849
8856
|
case OpCode.CREATE_LIST:
|
|
9850
8857
|
return new LiveList([]);
|
|
9851
|
-
case OpCode.CREATE_TEXT:
|
|
9852
|
-
return new LiveText(op.data, op.version);
|
|
9853
8858
|
default:
|
|
9854
8859
|
return assertNever(op, "Unknown creation Op");
|
|
9855
8860
|
}
|
|
@@ -9872,8 +8877,6 @@ function deserialize(node, parentToChildren, pool) {
|
|
|
9872
8877
|
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
9873
8878
|
} else if (isRegisterStorageNode(node)) {
|
|
9874
8879
|
return LiveRegister._deserialize(node, parentToChildren, pool);
|
|
9875
|
-
} else if (isTextStorageNode(node)) {
|
|
9876
|
-
return LiveText._deserialize(node, parentToChildren, pool);
|
|
9877
8880
|
} else {
|
|
9878
8881
|
throw new Error("Unexpected CRDT type");
|
|
9879
8882
|
}
|
|
@@ -9887,14 +8890,12 @@ function deserializeToLson(node, parentToChildren, pool) {
|
|
|
9887
8890
|
return LiveMap._deserialize(node, parentToChildren, pool);
|
|
9888
8891
|
} else if (isRegisterStorageNode(node)) {
|
|
9889
8892
|
return node[1].data;
|
|
9890
|
-
} else if (isTextStorageNode(node)) {
|
|
9891
|
-
return LiveText._deserialize(node, parentToChildren, pool);
|
|
9892
8893
|
} else {
|
|
9893
8894
|
throw new Error("Unexpected CRDT type");
|
|
9894
8895
|
}
|
|
9895
8896
|
}
|
|
9896
8897
|
function isLiveStructure(value) {
|
|
9897
|
-
return isLiveList(value) || isLiveMap(value) || isLiveObject(value)
|
|
8898
|
+
return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
|
|
9898
8899
|
}
|
|
9899
8900
|
function isLiveNode(value) {
|
|
9900
8901
|
return isLiveStructure(value) || isLiveRegister(value);
|
|
@@ -9908,9 +8909,6 @@ function isLiveMap(value) {
|
|
|
9908
8909
|
function isLiveObject(value) {
|
|
9909
8910
|
return value instanceof LiveObject;
|
|
9910
8911
|
}
|
|
9911
|
-
function isLiveText(value) {
|
|
9912
|
-
return value instanceof LiveText;
|
|
9913
|
-
}
|
|
9914
8912
|
function isLiveRegister(value) {
|
|
9915
8913
|
return value instanceof LiveRegister;
|
|
9916
8914
|
}
|
|
@@ -9920,14 +8918,14 @@ function cloneLson(value) {
|
|
|
9920
8918
|
function liveNodeToLson(obj) {
|
|
9921
8919
|
if (obj instanceof LiveRegister) {
|
|
9922
8920
|
return obj.data;
|
|
9923
|
-
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject
|
|
8921
|
+
} else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
|
|
9924
8922
|
return obj;
|
|
9925
8923
|
} else {
|
|
9926
8924
|
return assertNever(obj, "Unknown AbstractCrdt");
|
|
9927
8925
|
}
|
|
9928
8926
|
}
|
|
9929
8927
|
function lsonToLiveNode(value) {
|
|
9930
|
-
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList
|
|
8928
|
+
if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
|
|
9931
8929
|
return value;
|
|
9932
8930
|
} else {
|
|
9933
8931
|
return new LiveRegister(value);
|
|
@@ -10070,16 +9068,6 @@ function diffNodeMap(prev, next) {
|
|
|
10070
9068
|
parentKey: crdt.parentKey
|
|
10071
9069
|
});
|
|
10072
9070
|
break;
|
|
10073
|
-
case CrdtType.TEXT:
|
|
10074
|
-
ops.push({
|
|
10075
|
-
type: OpCode.CREATE_TEXT,
|
|
10076
|
-
id,
|
|
10077
|
-
parentId: crdt.parentId,
|
|
10078
|
-
parentKey: crdt.parentKey,
|
|
10079
|
-
data: crdt.data,
|
|
10080
|
-
version: crdt.version
|
|
10081
|
-
});
|
|
10082
|
-
break;
|
|
10083
9071
|
}
|
|
10084
9072
|
}
|
|
10085
9073
|
});
|
|
@@ -10112,43 +9100,19 @@ function mergeListStorageUpdates(first, second) {
|
|
|
10112
9100
|
updates: updates.concat(second.updates)
|
|
10113
9101
|
};
|
|
10114
9102
|
}
|
|
10115
|
-
function mergeTextStorageUpdates(first, second) {
|
|
10116
|
-
return {
|
|
10117
|
-
...second,
|
|
10118
|
-
updates: first.updates.concat(second.updates)
|
|
10119
|
-
};
|
|
10120
|
-
}
|
|
10121
9103
|
function mergeStorageUpdates(first, second) {
|
|
10122
9104
|
if (first === void 0) {
|
|
10123
9105
|
return second;
|
|
10124
9106
|
}
|
|
10125
|
-
let merged;
|
|
10126
9107
|
if (first.type === "LiveObject" && second.type === "LiveObject") {
|
|
10127
|
-
|
|
9108
|
+
return mergeObjectStorageUpdates(first, second);
|
|
10128
9109
|
} else if (first.type === "LiveMap" && second.type === "LiveMap") {
|
|
10129
|
-
|
|
9110
|
+
return mergeMapStorageUpdates(first, second);
|
|
10130
9111
|
} else if (first.type === "LiveList" && second.type === "LiveList") {
|
|
10131
|
-
|
|
10132
|
-
} else if (first.type === "LiveText" && second.type === "LiveText") {
|
|
10133
|
-
merged = mergeTextStorageUpdates(first, second);
|
|
9112
|
+
return mergeListStorageUpdates(first, second);
|
|
10134
9113
|
} else {
|
|
10135
|
-
merged = second;
|
|
10136
|
-
}
|
|
10137
|
-
const sa = first[kStorageUpdateSource];
|
|
10138
|
-
const sb = second[kStorageUpdateSource];
|
|
10139
|
-
if (sa !== void 0 || sb !== void 0) {
|
|
10140
|
-
if (sa?.origin === "remote" || sb?.origin === "remote") {
|
|
10141
|
-
merged[kStorageUpdateSource] = { origin: "remote" };
|
|
10142
|
-
} else if (sa?.via === "history" || sb?.via === "history") {
|
|
10143
|
-
const historySource = sb?.via === "history" ? sb : sa?.via === "history" ? sa : void 0;
|
|
10144
|
-
if (historySource?.via === "history") {
|
|
10145
|
-
merged[kStorageUpdateSource] = historySource;
|
|
10146
|
-
}
|
|
10147
|
-
} else {
|
|
10148
|
-
merged[kStorageUpdateSource] = { origin: "local", via: "mutation" };
|
|
10149
|
-
}
|
|
10150
9114
|
}
|
|
10151
|
-
return
|
|
9115
|
+
return second;
|
|
10152
9116
|
}
|
|
10153
9117
|
|
|
10154
9118
|
// src/devtools/bridge.ts
|
|
@@ -11092,8 +10056,6 @@ function createRoom(options, config) {
|
|
|
11092
10056
|
activeBatch: null,
|
|
11093
10057
|
unacknowledgedOps
|
|
11094
10058
|
};
|
|
11095
|
-
let nextHistoryItemId = 0;
|
|
11096
|
-
let historyDisabled = 0;
|
|
11097
10059
|
const nodeMapBuffer = makeNodeMapBuffer();
|
|
11098
10060
|
const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
|
|
11099
10061
|
let lastTokenKey;
|
|
@@ -11178,10 +10140,7 @@ function createRoom(options, config) {
|
|
|
11178
10140
|
}
|
|
11179
10141
|
}
|
|
11180
10142
|
});
|
|
11181
|
-
function onDispatch(ops, reverse, storageUpdates
|
|
11182
|
-
for (const value of storageUpdates.values()) {
|
|
11183
|
-
value[kStorageUpdateSource] = { origin: "local", via: "mutation" };
|
|
11184
|
-
}
|
|
10143
|
+
function onDispatch(ops, reverse, storageUpdates) {
|
|
11185
10144
|
if (context.activeBatch) {
|
|
11186
10145
|
for (const op of ops) {
|
|
11187
10146
|
context.activeBatch.ops.push(op);
|
|
@@ -11200,10 +10159,8 @@ function createRoom(options, config) {
|
|
|
11200
10159
|
if (reverse.length > 0) {
|
|
11201
10160
|
addToUndoStack(reverse);
|
|
11202
10161
|
}
|
|
11203
|
-
if (options2?.clearRedoStack ?? ops.length > 0) {
|
|
11204
|
-
clearRedoStack();
|
|
11205
|
-
}
|
|
11206
10162
|
if (ops.length > 0) {
|
|
10163
|
+
context.redoStack.length = 0;
|
|
11207
10164
|
dispatchOps(ops);
|
|
11208
10165
|
}
|
|
11209
10166
|
notify({ storageUpdates });
|
|
@@ -11223,7 +10180,6 @@ function createRoom(options, config) {
|
|
|
11223
10180
|
others: makeEventSource(),
|
|
11224
10181
|
storageBatch: makeEventSource(),
|
|
11225
10182
|
history: makeEventSource(),
|
|
11226
|
-
privateHistory: makeEventSource(),
|
|
11227
10183
|
storageDidLoad: makeEventSource(),
|
|
11228
10184
|
storageStatus: makeEventSource(),
|
|
11229
10185
|
ydoc: makeEventSource(),
|
|
@@ -11316,23 +10272,6 @@ function createRoom(options, config) {
|
|
|
11316
10272
|
}
|
|
11317
10273
|
const ops = diffNodeMap(currentItems, nodes);
|
|
11318
10274
|
const result = applyRemoteOps(ops);
|
|
11319
|
-
for (const [id, crdt] of nodes) {
|
|
11320
|
-
if (crdt.type === CrdtType.TEXT) {
|
|
11321
|
-
const node = context.pool.nodes.get(id);
|
|
11322
|
-
if (node !== void 0 && isLiveText(node)) {
|
|
11323
|
-
const update = node._resyncText(crdt.data, crdt.version);
|
|
11324
|
-
if (update !== void 0) {
|
|
11325
|
-
result.updates.storageUpdates.set(
|
|
11326
|
-
id,
|
|
11327
|
-
mergeStorageUpdates(
|
|
11328
|
-
result.updates.storageUpdates.get(id),
|
|
11329
|
-
update
|
|
11330
|
-
)
|
|
11331
|
-
);
|
|
11332
|
-
}
|
|
11333
|
-
}
|
|
11334
|
-
}
|
|
11335
|
-
}
|
|
11336
10275
|
notify(result.updates);
|
|
11337
10276
|
} else {
|
|
11338
10277
|
context.root = LiveObject._fromItems(
|
|
@@ -11356,26 +10295,11 @@ function createRoom(options, config) {
|
|
|
11356
10295
|
}
|
|
11357
10296
|
});
|
|
11358
10297
|
}
|
|
11359
|
-
function notifyPrivateHistory(event) {
|
|
11360
|
-
if (historyDisabled > 0) return;
|
|
11361
|
-
eventHub.privateHistory.notify(event);
|
|
11362
|
-
}
|
|
11363
|
-
function clearRedoStack() {
|
|
11364
|
-
if (context.redoStack.length === 0) return;
|
|
11365
|
-
const ids = context.redoStack.map((item) => item.id);
|
|
11366
|
-
context.redoStack.length = 0;
|
|
11367
|
-
notifyPrivateHistory({ action: "discard", ids });
|
|
11368
|
-
}
|
|
11369
10298
|
function _addToRealUndoStack(frames) {
|
|
11370
10299
|
if (context.undoStack.length >= 50) {
|
|
11371
|
-
|
|
11372
|
-
if (evicted !== void 0) {
|
|
11373
|
-
notifyPrivateHistory({ action: "discard", ids: [evicted.id] });
|
|
11374
|
-
}
|
|
10300
|
+
context.undoStack.shift();
|
|
11375
10301
|
}
|
|
11376
|
-
|
|
11377
|
-
context.undoStack.push({ id, frames });
|
|
11378
|
-
notifyPrivateHistory({ action: "push", id });
|
|
10302
|
+
context.undoStack.push(frames);
|
|
11379
10303
|
onHistoryChange();
|
|
11380
10304
|
}
|
|
11381
10305
|
function addToUndoStack(frames) {
|
|
@@ -11413,7 +10337,7 @@ function createRoom(options, config) {
|
|
|
11413
10337
|
"Internal. Tried to get connection id but connection was never open"
|
|
11414
10338
|
);
|
|
11415
10339
|
}
|
|
11416
|
-
function applyLocalOps(frames
|
|
10340
|
+
function applyLocalOps(frames) {
|
|
11417
10341
|
const [pframes, ops] = partition(
|
|
11418
10342
|
frames,
|
|
11419
10343
|
(f) => f.type === "presence"
|
|
@@ -11425,8 +10349,7 @@ function createRoom(options, config) {
|
|
|
11425
10349
|
pframes,
|
|
11426
10350
|
opsWithOpIds,
|
|
11427
10351
|
/* isLocal */
|
|
11428
|
-
true
|
|
11429
|
-
localStorageUpdateSource
|
|
10352
|
+
true
|
|
11430
10353
|
);
|
|
11431
10354
|
return { opsToEmit: opsWithOpIds, reverse, updates };
|
|
11432
10355
|
}
|
|
@@ -11438,7 +10361,7 @@ function createRoom(options, config) {
|
|
|
11438
10361
|
false
|
|
11439
10362
|
);
|
|
11440
10363
|
}
|
|
11441
|
-
function applyOps(pframes, ops, isLocal
|
|
10364
|
+
function applyOps(pframes, ops, isLocal) {
|
|
11442
10365
|
const output = {
|
|
11443
10366
|
reverse: new Deque(),
|
|
11444
10367
|
storageUpdates: /* @__PURE__ */ new Map(),
|
|
@@ -11476,7 +10399,6 @@ function createRoom(options, config) {
|
|
|
11476
10399
|
}
|
|
11477
10400
|
const applyOpResult = applyOp(op, source);
|
|
11478
10401
|
if (applyOpResult.modified) {
|
|
11479
|
-
applyOpResult.modified[kStorageUpdateSource] = source === 1 /* THEIRS */ ? { origin: "remote" } : localStorageUpdateSource;
|
|
11480
10402
|
const nodeId = applyOpResult.modified.node._id;
|
|
11481
10403
|
if (!(nodeId && createdNodeIds.has(nodeId))) {
|
|
11482
10404
|
output.storageUpdates.set(
|
|
@@ -11488,7 +10410,7 @@ function createRoom(options, config) {
|
|
|
11488
10410
|
);
|
|
11489
10411
|
output.reverse.pushLeft(applyOpResult.reverse);
|
|
11490
10412
|
}
|
|
11491
|
-
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT
|
|
10413
|
+
if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
|
|
11492
10414
|
createdNodeIds.add(op.id);
|
|
11493
10415
|
}
|
|
11494
10416
|
}
|
|
@@ -11508,7 +10430,6 @@ function createRoom(options, config) {
|
|
|
11508
10430
|
switch (op.type) {
|
|
11509
10431
|
case OpCode.DELETE_OBJECT_KEY:
|
|
11510
10432
|
case OpCode.UPDATE_OBJECT:
|
|
11511
|
-
case OpCode.UPDATE_TEXT:
|
|
11512
10433
|
case OpCode.DELETE_CRDT: {
|
|
11513
10434
|
const node = context.pool.nodes.get(op.id);
|
|
11514
10435
|
if (node === void 0) {
|
|
@@ -11533,7 +10454,6 @@ function createRoom(options, config) {
|
|
|
11533
10454
|
case OpCode.CREATE_OBJECT:
|
|
11534
10455
|
case OpCode.CREATE_LIST:
|
|
11535
10456
|
case OpCode.CREATE_MAP:
|
|
11536
|
-
case OpCode.CREATE_TEXT:
|
|
11537
10457
|
case OpCode.CREATE_REGISTER: {
|
|
11538
10458
|
if (op.parentId === void 0) {
|
|
11539
10459
|
return { modified: false };
|
|
@@ -11783,37 +10703,16 @@ function createRoom(options, config) {
|
|
|
11783
10703
|
}
|
|
11784
10704
|
break;
|
|
11785
10705
|
}
|
|
11786
|
-
// Receiving a RejectedOps message means the server
|
|
11787
|
-
//
|
|
11788
|
-
//
|
|
11789
|
-
//
|
|
11790
|
-
// server's retained history window — and we can recover: drop the
|
|
11791
|
-
// rejected pending state and re-fetch the authoritative storage
|
|
11792
|
-
// snapshot. For other ops (e.g. permission rejections), rolling back
|
|
11793
|
-
// particular Ops is hard/impossible, so we keep the old behavior of
|
|
11794
|
-
// accepting the out-of-sync reality and surfacing an error.
|
|
10706
|
+
// Receiving a RejectedOps message in the client means that the server is no
|
|
10707
|
+
// longer in sync with the client. Trying to synchronize the client again by
|
|
10708
|
+
// rolling back particular Ops may be hard/impossible. It's fine to not try and
|
|
10709
|
+
// accept the out-of-sync reality and throw an error.
|
|
11795
10710
|
case ServerMsgCode.REJECT_STORAGE_OP: {
|
|
11796
10711
|
errorWithTitle(
|
|
11797
10712
|
"Storage mutation rejection error",
|
|
11798
10713
|
message.reason
|
|
11799
10714
|
);
|
|
11800
|
-
|
|
11801
|
-
for (const opId of message.opIds) {
|
|
11802
|
-
const rejectedOp = context.unacknowledgedOps.get(opId);
|
|
11803
|
-
context.unacknowledgedOps.delete(opId);
|
|
11804
|
-
context.buffer.storageOperations = context.buffer.storageOperations.filter((op) => op.opId !== opId);
|
|
11805
|
-
if (rejectedOp !== void 0 && rejectedOp.type === OpCode.UPDATE_TEXT) {
|
|
11806
|
-
const node = context.pool.nodes.get(rejectedOp.id);
|
|
11807
|
-
if (node !== void 0 && isLiveText(node)) {
|
|
11808
|
-
node._rejectPendingOp(opId);
|
|
11809
|
-
needsStorageResync = true;
|
|
11810
|
-
}
|
|
11811
|
-
}
|
|
11812
|
-
}
|
|
11813
|
-
if (needsStorageResync) {
|
|
11814
|
-
refreshStorage();
|
|
11815
|
-
flushNowOrSoon();
|
|
11816
|
-
} else if (process.env.NODE_ENV !== "production") {
|
|
10715
|
+
if (process.env.NODE_ENV !== "production") {
|
|
11817
10716
|
throw new Error(
|
|
11818
10717
|
`Storage mutations rejected by server: ${message.reason}`
|
|
11819
10718
|
);
|
|
@@ -12355,19 +11254,14 @@ function createRoom(options, config) {
|
|
|
12355
11254
|
if (context.activeBatch) {
|
|
12356
11255
|
throw new Error("undo is not allowed during a batch");
|
|
12357
11256
|
}
|
|
12358
|
-
const
|
|
12359
|
-
if (
|
|
11257
|
+
const frames = context.undoStack.pop();
|
|
11258
|
+
if (frames === void 0) {
|
|
12360
11259
|
return;
|
|
12361
11260
|
}
|
|
12362
11261
|
context.pausedHistory = null;
|
|
12363
|
-
const result = applyLocalOps(
|
|
12364
|
-
origin: "local",
|
|
12365
|
-
via: "history",
|
|
12366
|
-
action: "undo"
|
|
12367
|
-
});
|
|
12368
|
-
context.redoStack.push({ id: item.id, frames: result.reverse });
|
|
12369
|
-
notifyPrivateHistory({ action: "undo", id: item.id });
|
|
11262
|
+
const result = applyLocalOps(frames);
|
|
12370
11263
|
notify(result.updates);
|
|
11264
|
+
context.redoStack.push(result.reverse);
|
|
12371
11265
|
onHistoryChange();
|
|
12372
11266
|
for (const op of result.opsToEmit) {
|
|
12373
11267
|
context.buffer.storageOperations.push(op);
|
|
@@ -12378,19 +11272,14 @@ function createRoom(options, config) {
|
|
|
12378
11272
|
if (context.activeBatch) {
|
|
12379
11273
|
throw new Error("redo is not allowed during a batch");
|
|
12380
11274
|
}
|
|
12381
|
-
const
|
|
12382
|
-
if (
|
|
11275
|
+
const frames = context.redoStack.pop();
|
|
11276
|
+
if (frames === void 0) {
|
|
12383
11277
|
return;
|
|
12384
11278
|
}
|
|
12385
11279
|
context.pausedHistory = null;
|
|
12386
|
-
const result = applyLocalOps(
|
|
12387
|
-
origin: "local",
|
|
12388
|
-
via: "history",
|
|
12389
|
-
action: "redo"
|
|
12390
|
-
});
|
|
12391
|
-
context.undoStack.push({ id: item.id, frames: result.reverse });
|
|
12392
|
-
notifyPrivateHistory({ action: "redo", id: item.id });
|
|
11280
|
+
const result = applyLocalOps(frames);
|
|
12393
11281
|
notify(result.updates);
|
|
11282
|
+
context.undoStack.push(result.reverse);
|
|
12394
11283
|
onHistoryChange();
|
|
12395
11284
|
for (const op of result.opsToEmit) {
|
|
12396
11285
|
context.buffer.storageOperations.push(op);
|
|
@@ -12400,8 +11289,6 @@ function createRoom(options, config) {
|
|
|
12400
11289
|
function clear() {
|
|
12401
11290
|
context.undoStack.length = 0;
|
|
12402
11291
|
context.redoStack.length = 0;
|
|
12403
|
-
notifyPrivateHistory({ action: "clear" });
|
|
12404
|
-
onHistoryChange();
|
|
12405
11292
|
}
|
|
12406
11293
|
function batch2(callback) {
|
|
12407
11294
|
if (context.activeBatch) {
|
|
@@ -12430,7 +11317,7 @@ function createRoom(options, config) {
|
|
|
12430
11317
|
commitPausedHistoryToUndoStack();
|
|
12431
11318
|
}
|
|
12432
11319
|
if (currentBatch.ops.length > 0) {
|
|
12433
|
-
|
|
11320
|
+
context.redoStack.length = 0;
|
|
12434
11321
|
}
|
|
12435
11322
|
if (currentBatch.ops.length > 0) {
|
|
12436
11323
|
dispatchOps(currentBatch.ops);
|
|
@@ -12459,6 +11346,7 @@ function createRoom(options, config) {
|
|
|
12459
11346
|
}
|
|
12460
11347
|
commitPausedHistoryToUndoStack();
|
|
12461
11348
|
}
|
|
11349
|
+
let historyDisabled = 0;
|
|
12462
11350
|
function disableHistory(fn) {
|
|
12463
11351
|
const origUndo = context.undoStack;
|
|
12464
11352
|
const origRedo = context.redoStack;
|
|
@@ -12560,6 +11448,7 @@ function createRoom(options, config) {
|
|
|
12560
11448
|
roomId,
|
|
12561
11449
|
threadId: options2.threadId,
|
|
12562
11450
|
commentId: options2.commentId,
|
|
11451
|
+
visibility: options2.visibility,
|
|
12563
11452
|
metadata: options2.metadata,
|
|
12564
11453
|
body: options2.body,
|
|
12565
11454
|
commentMetadata: options2.commentMetadata,
|
|
@@ -12697,28 +11586,13 @@ function createRoom(options, config) {
|
|
|
12697
11586
|
},
|
|
12698
11587
|
// prettier-ignore
|
|
12699
11588
|
get undoStack() {
|
|
12700
|
-
return
|
|
12701
|
-
context.undoStack.map((item) => ({
|
|
12702
|
-
id: item.id,
|
|
12703
|
-
frames: item.frames
|
|
12704
|
-
}))
|
|
12705
|
-
);
|
|
12706
|
-
},
|
|
12707
|
-
// prettier-ignore
|
|
12708
|
-
get redoStack() {
|
|
12709
|
-
return structuredClone(
|
|
12710
|
-
context.redoStack.map((item) => ({
|
|
12711
|
-
id: item.id,
|
|
12712
|
-
frames: item.frames
|
|
12713
|
-
}))
|
|
12714
|
-
);
|
|
11589
|
+
return deepClone(context.undoStack);
|
|
12715
11590
|
},
|
|
12716
11591
|
// prettier-ignore
|
|
12717
11592
|
get nodeCount() {
|
|
12718
11593
|
return context.pool.nodes.size;
|
|
12719
11594
|
},
|
|
12720
11595
|
// prettier-ignore
|
|
12721
|
-
history: eventHub.privateHistory.observable,
|
|
12722
11596
|
getYjsProvider() {
|
|
12723
11597
|
return context.yjsProvider;
|
|
12724
11598
|
},
|
|
@@ -13738,12 +12612,6 @@ function toPlainLson(lson) {
|
|
|
13738
12612
|
liveblocksType: "LiveList",
|
|
13739
12613
|
data: [...lson].map((item) => toPlainLson(item))
|
|
13740
12614
|
};
|
|
13741
|
-
} else if (lson instanceof LiveText) {
|
|
13742
|
-
return {
|
|
13743
|
-
liveblocksType: "LiveText",
|
|
13744
|
-
data: lson.toJSON(),
|
|
13745
|
-
version: lson.version
|
|
13746
|
-
};
|
|
13747
12615
|
} else {
|
|
13748
12616
|
return lson;
|
|
13749
12617
|
}
|
|
@@ -13925,7 +12793,6 @@ export {
|
|
|
13925
12793
|
LiveList,
|
|
13926
12794
|
LiveMap,
|
|
13927
12795
|
LiveObject,
|
|
13928
|
-
LiveText,
|
|
13929
12796
|
LiveblocksError,
|
|
13930
12797
|
MENTION_CHARACTER,
|
|
13931
12798
|
MutableSignal,
|
|
@@ -13937,7 +12804,6 @@ export {
|
|
|
13937
12804
|
SortedList,
|
|
13938
12805
|
TextEditorType,
|
|
13939
12806
|
WebsocketCloseCodes,
|
|
13940
|
-
applyLiveTextOperations,
|
|
13941
12807
|
asPos,
|
|
13942
12808
|
assert,
|
|
13943
12809
|
assertNever,
|
|
@@ -13995,10 +12861,8 @@ export {
|
|
|
13995
12861
|
isRegisterStorageNode,
|
|
13996
12862
|
isRootStorageNode,
|
|
13997
12863
|
isStartsWithOperator,
|
|
13998
|
-
isTextStorageNode,
|
|
13999
12864
|
isUrl,
|
|
14000
12865
|
kInternal,
|
|
14001
|
-
kStorageUpdateSource,
|
|
14002
12866
|
keys,
|
|
14003
12867
|
makeAbortController,
|
|
14004
12868
|
makeEventSource,
|
|
@@ -14025,7 +12889,6 @@ export {
|
|
|
14025
12889
|
stringifyCommentBody,
|
|
14026
12890
|
throwUsageError,
|
|
14027
12891
|
toPlainLson,
|
|
14028
|
-
transformTextOperations,
|
|
14029
12892
|
tryParseJson,
|
|
14030
12893
|
url,
|
|
14031
12894
|
urljoin,
|