@ai-matrx/office 0.8.12 → 0.8.13
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/CHANGELOG.md +2 -5
- package/dist/collab-sheets-transform.cjs +457 -39
- package/dist/collab-sheets-transform.cjs.map +1 -1
- package/dist/collab-sheets-transform.d.cts +65 -7
- package/dist/collab-sheets-transform.d.ts +65 -7
- package/dist/collab-sheets-transform.js +463 -36
- package/dist/collab-sheets-transform.js.map +1 -1
- package/dist/docs.cjs +30 -20
- package/dist/docs.cjs.map +1 -1
- package/dist/docs.d.cts +22 -0
- package/dist/docs.d.ts +22 -0
- package/dist/docs.js +11 -1
- package/dist/docs.js.map +1 -1
- package/dist/univer-collab.cjs +201 -5
- package/dist/univer-collab.cjs.map +1 -1
- package/dist/univer-collab.d.cts +71 -2
- package/dist/univer-collab.d.ts +71 -2
- package/dist/univer-collab.js +201 -5
- package/dist/univer-collab.js.map +1 -1
- package/dist/univer.cjs +4 -0
- package/dist/univer.cjs.map +1 -1
- package/dist/univer.d.cts +3 -1
- package/dist/univer.d.ts +3 -1
- package/dist/univer.js +4 -0
- package/dist/univer.js.map +1 -1
- package/package.json +6 -6
package/dist/univer-collab.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Y from 'yjs';
|
|
2
2
|
import { Awareness } from 'y-protocols/awareness';
|
|
3
|
-
import { OpSessionNotice, OpSession, OpTransport, OpPresence, OpSessionIncident, OpSessionStatus, AwarenessState } from '@ai-matrx/data/realtime/collab';
|
|
3
|
+
import { OpSessionNotice, OpSession, OpTransport, OpPresence, OpSessionIncident, OpSessionStatus, AwarenessState, CollabRpcClient, collabReplace } from '@ai-matrx/data/realtime/collab';
|
|
4
4
|
import { IDocumentData } from '@univerjs/core';
|
|
5
5
|
import { CommitReason } from '@ai-matrx/kit/working-copy';
|
|
6
6
|
|
|
@@ -336,6 +336,12 @@ interface DocsOtPort {
|
|
|
336
336
|
}>;
|
|
337
337
|
/** Ask for a save (the record's one coalesced save). */
|
|
338
338
|
markDirty(): void;
|
|
339
|
+
/** This seat's text selection in the first view (body offsets), for presence (C8). Absent when no view can say. */
|
|
340
|
+
selection?(): Array<{
|
|
341
|
+
startOffset: number;
|
|
342
|
+
endOffset: number;
|
|
343
|
+
segmentId?: string;
|
|
344
|
+
}> | null;
|
|
339
345
|
}
|
|
340
346
|
type DocsOtNotice = OpSessionNotice<DocsOp> | {
|
|
341
347
|
kind: "not_shared";
|
|
@@ -399,6 +405,20 @@ declare function undoStacksOf(service: unknown): {
|
|
|
399
405
|
declare function rebaseUndoStack(items: UndoItem[], remote: DocsOp): number;
|
|
400
406
|
declare function openDocsOtRoom(o: OpenDocsOtRoomOptions): DocsOtRoom;
|
|
401
407
|
|
|
408
|
+
interface CaretRange {
|
|
409
|
+
start: number;
|
|
410
|
+
end: number;
|
|
411
|
+
}
|
|
412
|
+
/** One other seat's caret, ready to draw. */
|
|
413
|
+
interface RemoteCaret {
|
|
414
|
+
/** The seat (Yjs client id as a string): two tabs of one person are two carets. */
|
|
415
|
+
key: string;
|
|
416
|
+
uid: string;
|
|
417
|
+
name: string;
|
|
418
|
+
color: string;
|
|
419
|
+
ranges: CaretRange[];
|
|
420
|
+
}
|
|
421
|
+
|
|
402
422
|
/**
|
|
403
423
|
* THE DOCUMENT MODEL — the engine of one cloud document per tab.
|
|
404
424
|
*
|
|
@@ -447,6 +467,8 @@ type DocumentOtFactory = (port: DocsOtPort, onAwareness: (awareness: DocumentAwa
|
|
|
447
467
|
interface DocumentAwareness {
|
|
448
468
|
states: Map<number, AwarenessState>;
|
|
449
469
|
selfUid: string;
|
|
470
|
+
/** Other seats' carets and selections (ordered-log rooms only, C8), already moved through every edit applied since. */
|
|
471
|
+
carets?: RemoteCaret[];
|
|
450
472
|
}
|
|
451
473
|
|
|
452
474
|
/** `collab.ot_rooms` (§12.2): which documents without a room open one. */
|
|
@@ -467,4 +489,51 @@ interface DocumentOtFactoryOptions {
|
|
|
467
489
|
/** The `DocumentModel.startOtRoom` factory for one document. */
|
|
468
490
|
declare function documentOtFactory(o: DocumentOtFactoryOptions): DocumentOtFactory;
|
|
469
491
|
|
|
470
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Restore a saved version of a document (COLLAB-DESIGN §6.2, §20): THE one path for History, so a document with a
|
|
494
|
+
* co-editing room is never restored behind its room's back.
|
|
495
|
+
*
|
|
496
|
+
* - Open in this tab: save the restored row, then the open editor swaps it in (`showSavedSnapshot`); an ordered-log
|
|
497
|
+
* room sends it through `collab_replace` itself (`DocumentOtHandle.replace`).
|
|
498
|
+
* - Not open in this tab but the document has a room (another tab or person has it open, or its log is ahead of its
|
|
499
|
+
* checkpoint): `collab_replace` writes the snapshot row, the `replace` op, the checkpoint and the frame in one
|
|
500
|
+
* transaction, so every open seat reloads. A snapshot row alone left those seats stale.
|
|
501
|
+
* - No room: a snapshot row; the next open loads it.
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
type DocumentRestoreResult = {
|
|
505
|
+
via: "open_editor";
|
|
506
|
+
snapshotId: string;
|
|
507
|
+
} | {
|
|
508
|
+
via: "room";
|
|
509
|
+
rev: number;
|
|
510
|
+
snapshotId: string;
|
|
511
|
+
} | {
|
|
512
|
+
via: "saved";
|
|
513
|
+
snapshotId: string;
|
|
514
|
+
};
|
|
515
|
+
interface DocumentRestoreDeps {
|
|
516
|
+
client: () => CollabRpcClient;
|
|
517
|
+
roomExists: (client: CollabRpcClient, documentId: string) => Promise<boolean>;
|
|
518
|
+
replace: typeof collabReplace;
|
|
519
|
+
stateHash: (snapshot: unknown) => Promise<string>;
|
|
520
|
+
hasOpenEditor: (documentId: string) => boolean;
|
|
521
|
+
showInOpenEditor: (documentId: string, row: {
|
|
522
|
+
id: string;
|
|
523
|
+
snapshot: unknown;
|
|
524
|
+
}) => Promise<boolean>;
|
|
525
|
+
saveRow: (args: {
|
|
526
|
+
documentId: string;
|
|
527
|
+
snapshot: unknown;
|
|
528
|
+
label: string | null;
|
|
529
|
+
}) => Promise<{
|
|
530
|
+
id: string;
|
|
531
|
+
}>;
|
|
532
|
+
}
|
|
533
|
+
declare function restoreDocumentSnapshot(args: {
|
|
534
|
+
documentId: string;
|
|
535
|
+
snapshot: unknown;
|
|
536
|
+
label: string | null;
|
|
537
|
+
}, deps?: Partial<DocumentRestoreDeps>): Promise<DocumentRestoreResult>;
|
|
538
|
+
|
|
539
|
+
export { COLLAB_COVERED_KEY, type CollabBase, type CollabMutationInfo, type CollabProviderLike, type CommandServiceLike, DOCS_COLLAB_PROTO, DOCS_TRANSFORM_VERSION, DOC_LOCAL_KEYS, type DocsLocalMutation, type DocsLost, type DocsOp, type DocsOtNotice, type DocsOtPort, type DocsOtRoom, type DocsTransformResult, type DocumentOtFactoryOptions, type DocumentRestoreDeps, type DocumentRestoreResult, type OpenDocsOtRoomOptions, type OtRoomsKnob, RICH_TEXT_EDITING, SENDER_VIEW_PARAMS, WorkbookCollabSession, type WorkbookCollabSessionOptions, applyDocsOp, canonicalDocsSnapshot, canonicalDocsString, canonicalKeys, composeDocsOps, docsStateHash, documentOtFactory, documentUsesOrderedRoom, dropEmptyStyleRetains, invertDocsOp, isCellEditorScratch, isNoopDocsOp, normalizeDocsOp, openDocsOtRoom, rebaseUndoStack, restoreDocumentSnapshot, stampCovered, stampDeterministicIds, stripSenderView, takeCovered, transformDocsOp, transformTextX, undoStacksOf };
|
package/dist/univer-collab.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Y from 'yjs';
|
|
2
2
|
import { Awareness } from 'y-protocols/awareness';
|
|
3
|
-
import { OpSessionNotice, OpSession, OpTransport, OpPresence, OpSessionIncident, OpSessionStatus, AwarenessState } from '@ai-matrx/data/realtime/collab';
|
|
3
|
+
import { OpSessionNotice, OpSession, OpTransport, OpPresence, OpSessionIncident, OpSessionStatus, AwarenessState, CollabRpcClient, collabReplace } from '@ai-matrx/data/realtime/collab';
|
|
4
4
|
import { IDocumentData } from '@univerjs/core';
|
|
5
5
|
import { CommitReason } from '@ai-matrx/kit/working-copy';
|
|
6
6
|
|
|
@@ -336,6 +336,12 @@ interface DocsOtPort {
|
|
|
336
336
|
}>;
|
|
337
337
|
/** Ask for a save (the record's one coalesced save). */
|
|
338
338
|
markDirty(): void;
|
|
339
|
+
/** This seat's text selection in the first view (body offsets), for presence (C8). Absent when no view can say. */
|
|
340
|
+
selection?(): Array<{
|
|
341
|
+
startOffset: number;
|
|
342
|
+
endOffset: number;
|
|
343
|
+
segmentId?: string;
|
|
344
|
+
}> | null;
|
|
339
345
|
}
|
|
340
346
|
type DocsOtNotice = OpSessionNotice<DocsOp> | {
|
|
341
347
|
kind: "not_shared";
|
|
@@ -399,6 +405,20 @@ declare function undoStacksOf(service: unknown): {
|
|
|
399
405
|
declare function rebaseUndoStack(items: UndoItem[], remote: DocsOp): number;
|
|
400
406
|
declare function openDocsOtRoom(o: OpenDocsOtRoomOptions): DocsOtRoom;
|
|
401
407
|
|
|
408
|
+
interface CaretRange {
|
|
409
|
+
start: number;
|
|
410
|
+
end: number;
|
|
411
|
+
}
|
|
412
|
+
/** One other seat's caret, ready to draw. */
|
|
413
|
+
interface RemoteCaret {
|
|
414
|
+
/** The seat (Yjs client id as a string): two tabs of one person are two carets. */
|
|
415
|
+
key: string;
|
|
416
|
+
uid: string;
|
|
417
|
+
name: string;
|
|
418
|
+
color: string;
|
|
419
|
+
ranges: CaretRange[];
|
|
420
|
+
}
|
|
421
|
+
|
|
402
422
|
/**
|
|
403
423
|
* THE DOCUMENT MODEL — the engine of one cloud document per tab.
|
|
404
424
|
*
|
|
@@ -447,6 +467,8 @@ type DocumentOtFactory = (port: DocsOtPort, onAwareness: (awareness: DocumentAwa
|
|
|
447
467
|
interface DocumentAwareness {
|
|
448
468
|
states: Map<number, AwarenessState>;
|
|
449
469
|
selfUid: string;
|
|
470
|
+
/** Other seats' carets and selections (ordered-log rooms only, C8), already moved through every edit applied since. */
|
|
471
|
+
carets?: RemoteCaret[];
|
|
450
472
|
}
|
|
451
473
|
|
|
452
474
|
/** `collab.ot_rooms` (§12.2): which documents without a room open one. */
|
|
@@ -467,4 +489,51 @@ interface DocumentOtFactoryOptions {
|
|
|
467
489
|
/** The `DocumentModel.startOtRoom` factory for one document. */
|
|
468
490
|
declare function documentOtFactory(o: DocumentOtFactoryOptions): DocumentOtFactory;
|
|
469
491
|
|
|
470
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Restore a saved version of a document (COLLAB-DESIGN §6.2, §20): THE one path for History, so a document with a
|
|
494
|
+
* co-editing room is never restored behind its room's back.
|
|
495
|
+
*
|
|
496
|
+
* - Open in this tab: save the restored row, then the open editor swaps it in (`showSavedSnapshot`); an ordered-log
|
|
497
|
+
* room sends it through `collab_replace` itself (`DocumentOtHandle.replace`).
|
|
498
|
+
* - Not open in this tab but the document has a room (another tab or person has it open, or its log is ahead of its
|
|
499
|
+
* checkpoint): `collab_replace` writes the snapshot row, the `replace` op, the checkpoint and the frame in one
|
|
500
|
+
* transaction, so every open seat reloads. A snapshot row alone left those seats stale.
|
|
501
|
+
* - No room: a snapshot row; the next open loads it.
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
type DocumentRestoreResult = {
|
|
505
|
+
via: "open_editor";
|
|
506
|
+
snapshotId: string;
|
|
507
|
+
} | {
|
|
508
|
+
via: "room";
|
|
509
|
+
rev: number;
|
|
510
|
+
snapshotId: string;
|
|
511
|
+
} | {
|
|
512
|
+
via: "saved";
|
|
513
|
+
snapshotId: string;
|
|
514
|
+
};
|
|
515
|
+
interface DocumentRestoreDeps {
|
|
516
|
+
client: () => CollabRpcClient;
|
|
517
|
+
roomExists: (client: CollabRpcClient, documentId: string) => Promise<boolean>;
|
|
518
|
+
replace: typeof collabReplace;
|
|
519
|
+
stateHash: (snapshot: unknown) => Promise<string>;
|
|
520
|
+
hasOpenEditor: (documentId: string) => boolean;
|
|
521
|
+
showInOpenEditor: (documentId: string, row: {
|
|
522
|
+
id: string;
|
|
523
|
+
snapshot: unknown;
|
|
524
|
+
}) => Promise<boolean>;
|
|
525
|
+
saveRow: (args: {
|
|
526
|
+
documentId: string;
|
|
527
|
+
snapshot: unknown;
|
|
528
|
+
label: string | null;
|
|
529
|
+
}) => Promise<{
|
|
530
|
+
id: string;
|
|
531
|
+
}>;
|
|
532
|
+
}
|
|
533
|
+
declare function restoreDocumentSnapshot(args: {
|
|
534
|
+
documentId: string;
|
|
535
|
+
snapshot: unknown;
|
|
536
|
+
label: string | null;
|
|
537
|
+
}, deps?: Partial<DocumentRestoreDeps>): Promise<DocumentRestoreResult>;
|
|
538
|
+
|
|
539
|
+
export { COLLAB_COVERED_KEY, type CollabBase, type CollabMutationInfo, type CollabProviderLike, type CommandServiceLike, DOCS_COLLAB_PROTO, DOCS_TRANSFORM_VERSION, DOC_LOCAL_KEYS, type DocsLocalMutation, type DocsLost, type DocsOp, type DocsOtNotice, type DocsOtPort, type DocsOtRoom, type DocsTransformResult, type DocumentOtFactoryOptions, type DocumentRestoreDeps, type DocumentRestoreResult, type OpenDocsOtRoomOptions, type OtRoomsKnob, RICH_TEXT_EDITING, SENDER_VIEW_PARAMS, WorkbookCollabSession, type WorkbookCollabSessionOptions, applyDocsOp, canonicalDocsSnapshot, canonicalDocsString, canonicalKeys, composeDocsOps, docsStateHash, documentOtFactory, documentUsesOrderedRoom, dropEmptyStyleRetains, invertDocsOp, isCellEditorScratch, isNoopDocsOp, normalizeDocsOp, openDocsOtRoom, rebaseUndoStack, restoreDocumentSnapshot, stampCovered, stampDeterministicIds, stripSenderView, takeCovered, transformDocsOp, transformTextX, undoStacksOf };
|
package/dist/univer-collab.js
CHANGED
|
@@ -900,10 +900,10 @@ function canonicalRuns(runs) {
|
|
|
900
900
|
}
|
|
901
901
|
return out;
|
|
902
902
|
}
|
|
903
|
-
function foldPieces(items,
|
|
903
|
+
function foldPieces(items, keyOf2) {
|
|
904
904
|
const groups = /* @__PURE__ */ new Map();
|
|
905
905
|
for (const it of items ?? []) {
|
|
906
|
-
const k =
|
|
906
|
+
const k = keyOf2(it);
|
|
907
907
|
const g = groups.get(k) ?? [];
|
|
908
908
|
g.push(it);
|
|
909
909
|
groups.set(k, g);
|
|
@@ -1220,6 +1220,120 @@ import {
|
|
|
1220
1220
|
supabaseOpTransport
|
|
1221
1221
|
} from "@ai-matrx/data/realtime/collab";
|
|
1222
1222
|
|
|
1223
|
+
// src/docs/document-model.ts
|
|
1224
|
+
import { TextX as TextX2 } from "@univerjs/core";
|
|
1225
|
+
|
|
1226
|
+
// src/univer/openEditorContent.ts
|
|
1227
|
+
var replacers = /* @__PURE__ */ new Map();
|
|
1228
|
+
var keyOf = (kind, recordId) => `${kind}:${recordId}`;
|
|
1229
|
+
function hasOpenEditorContent(kind, recordId) {
|
|
1230
|
+
return replacers.has(keyOf(kind, recordId));
|
|
1231
|
+
}
|
|
1232
|
+
async function showSavedSnapshot(kind, recordId, row) {
|
|
1233
|
+
const replace = replacers.get(keyOf(kind, recordId));
|
|
1234
|
+
if (!replace) return false;
|
|
1235
|
+
await replace(row);
|
|
1236
|
+
return true;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/docs/document-model.ts
|
|
1240
|
+
function moveRangesThroughBodyEdit(ranges, actions) {
|
|
1241
|
+
const a = actions;
|
|
1242
|
+
if (!Array.isArray(a) || a[0] !== "body") return null;
|
|
1243
|
+
const sub = a[1];
|
|
1244
|
+
if (!sub || sub.et !== "text-x" || !Array.isArray(sub.e)) return null;
|
|
1245
|
+
const e = sub.e;
|
|
1246
|
+
return ranges.map((r) => {
|
|
1247
|
+
const collapsed = r.startOffset === r.endOffset;
|
|
1248
|
+
const startOffset = TextX2.transformPosition(e, r.startOffset, collapsed);
|
|
1249
|
+
return { startOffset, endOffset: collapsed ? startOffset : TextX2.transformPosition(e, r.endOffset, true) };
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/docs/remote-carets.ts
|
|
1254
|
+
var CARET_HEARTBEAT_MS = 2e3;
|
|
1255
|
+
var CARET_STALE_MS = 12e3;
|
|
1256
|
+
var CARET_MAX_RANGES = 4;
|
|
1257
|
+
function publishedCaretFromSelection(sel, now) {
|
|
1258
|
+
if (!sel || sel.length === 0) return null;
|
|
1259
|
+
if (sel.some((r) => r.segmentId)) return null;
|
|
1260
|
+
const ranges = sel.slice(0, CARET_MAX_RANGES).map((r) => ({ start: Math.min(r.startOffset, r.endOffset), end: Math.max(r.startOffset, r.endOffset) }));
|
|
1261
|
+
return { ranges, at: now };
|
|
1262
|
+
}
|
|
1263
|
+
var sameRanges = (a, b) => a.length === b.length && a.every((r, i) => r.start === b[i].start && r.end === b[i].end);
|
|
1264
|
+
function readCaret(raw) {
|
|
1265
|
+
const c = raw?.caret;
|
|
1266
|
+
if (!c || !Array.isArray(c.ranges) || typeof c.at !== "number") return null;
|
|
1267
|
+
const ranges = [];
|
|
1268
|
+
for (const r of c.ranges.slice(0, CARET_MAX_RANGES)) {
|
|
1269
|
+
const s = r?.start;
|
|
1270
|
+
const e = r?.end;
|
|
1271
|
+
if (typeof s !== "number" || typeof e !== "number" || !Number.isFinite(s) || !Number.isFinite(e) || s < 0 || e < 0) return null;
|
|
1272
|
+
ranges.push({ start: s, end: e });
|
|
1273
|
+
}
|
|
1274
|
+
return ranges.length ? { ranges, at: c.at } : null;
|
|
1275
|
+
}
|
|
1276
|
+
var RemoteCaretBook = class {
|
|
1277
|
+
constructor(selfClientId, now = Date.now) {
|
|
1278
|
+
this.selfClientId = selfClientId;
|
|
1279
|
+
this.now = now;
|
|
1280
|
+
}
|
|
1281
|
+
selfClientId;
|
|
1282
|
+
now;
|
|
1283
|
+
carets = /* @__PURE__ */ new Map();
|
|
1284
|
+
/** Take the room's awareness states: new or republished carets replace ours, seats that left are dropped. Returns whether anything changed. */
|
|
1285
|
+
ingest(states) {
|
|
1286
|
+
let changed = false;
|
|
1287
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1288
|
+
states.forEach((raw, id) => {
|
|
1289
|
+
if (id === this.selfClientId) return;
|
|
1290
|
+
const s = raw;
|
|
1291
|
+
const caret = readCaret(raw);
|
|
1292
|
+
if (!s || typeof s.uid !== "string" || !caret) return;
|
|
1293
|
+
const key = String(id);
|
|
1294
|
+
seen.add(key);
|
|
1295
|
+
const prev = this.carets.get(key);
|
|
1296
|
+
const name = typeof s.name === "string" && s.name ? s.name : "Someone";
|
|
1297
|
+
const color = typeof s.color === "string" && s.color ? s.color : "#3b82f6";
|
|
1298
|
+
if (prev && prev.at === caret.at && prev.name === name && prev.color === color) return;
|
|
1299
|
+
this.carets.set(key, { key, uid: s.uid, name, color, ranges: caret.ranges, at: caret.at, seenAt: this.now() });
|
|
1300
|
+
changed = true;
|
|
1301
|
+
});
|
|
1302
|
+
for (const key of [...this.carets.keys()]) {
|
|
1303
|
+
if (!seen.has(key)) {
|
|
1304
|
+
this.carets.delete(key);
|
|
1305
|
+
changed = true;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return changed;
|
|
1309
|
+
}
|
|
1310
|
+
/** Move every caret through one body edit that was just applied (`actions` of a rich-text mutation). Returns whether any moved. */
|
|
1311
|
+
shiftThrough(actions) {
|
|
1312
|
+
let moved = false;
|
|
1313
|
+
for (const c of this.carets.values()) {
|
|
1314
|
+
const next = moveRangesThroughBodyEdit(c.ranges.map((r) => ({ startOffset: r.start, endOffset: r.end })), actions);
|
|
1315
|
+
if (!next) return false;
|
|
1316
|
+
const ranges = next.map((r) => ({ start: r.startOffset, end: r.endOffset }));
|
|
1317
|
+
if (!sameRanges(ranges, c.ranges)) {
|
|
1318
|
+
c.ranges = ranges;
|
|
1319
|
+
moved = true;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return moved;
|
|
1323
|
+
}
|
|
1324
|
+
/** The carets to draw now (stale ones left out). */
|
|
1325
|
+
list() {
|
|
1326
|
+
const t = this.now();
|
|
1327
|
+
const out = [];
|
|
1328
|
+
for (const c of this.carets.values()) {
|
|
1329
|
+
if (t - c.seenAt > CARET_STALE_MS) continue;
|
|
1330
|
+
out.push({ key: c.key, uid: c.uid, name: c.name, color: c.color, ranges: c.ranges.map((r) => ({ ...r })) });
|
|
1331
|
+
}
|
|
1332
|
+
out.sort((a, b) => a.key.localeCompare(b.key));
|
|
1333
|
+
return out;
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1223
1337
|
// src/docs/document-service.ts
|
|
1224
1338
|
import { requireOrganizationContext } from "@ai-matrx/api";
|
|
1225
1339
|
import { getClaimsUser } from "@ai-matrx/auth";
|
|
@@ -1292,11 +1406,11 @@ function pickColor2(uid) {
|
|
|
1292
1406
|
return palette[Math.abs(h) % palette.length] ?? "#ec4899";
|
|
1293
1407
|
}
|
|
1294
1408
|
function documentOtFactory(o) {
|
|
1295
|
-
return async (
|
|
1409
|
+
return async (basePort, onAwareness) => {
|
|
1296
1410
|
const latest = await getLatestDocumentSnapshot(o.documentId);
|
|
1297
1411
|
if (!latest.success) throw new Error(latest.error);
|
|
1298
1412
|
if (!latest.data) {
|
|
1299
|
-
const mounted =
|
|
1413
|
+
const mounted = basePort.snapshot();
|
|
1300
1414
|
if (mounted) {
|
|
1301
1415
|
const saved = await saveDocumentSnapshot({ documentId: o.documentId, snapshot: mounted, origin: "autosave" });
|
|
1302
1416
|
if (!saved.success) throw new Error(saved.error);
|
|
@@ -1305,7 +1419,31 @@ function documentOtFactory(o) {
|
|
|
1305
1419
|
const doc = new Y2.Doc();
|
|
1306
1420
|
const awareness = new Awareness2(doc);
|
|
1307
1421
|
awareness.setLocalState({ uid: o.uid, clientId: o.clientId, name: o.displayName ?? o.uid.slice(0, 8), color: pickColor2(o.uid), ts: Date.now() });
|
|
1308
|
-
const
|
|
1422
|
+
const carets = new RemoteCaretBook(awareness.clientID);
|
|
1423
|
+
const emit = () => onAwareness({ states: new Map(awareness.getStates()), selfUid: o.uid, carets: carets.list() });
|
|
1424
|
+
const publishAwareness = () => {
|
|
1425
|
+
carets.ingest(awareness.getStates());
|
|
1426
|
+
emit();
|
|
1427
|
+
};
|
|
1428
|
+
const bodyActions = (params) => {
|
|
1429
|
+
const p = params;
|
|
1430
|
+
return p && !p.segmentId ? p.actions : void 0;
|
|
1431
|
+
};
|
|
1432
|
+
const port = {
|
|
1433
|
+
...basePort,
|
|
1434
|
+
applyRemote(id, params) {
|
|
1435
|
+
basePort.applyRemote(id, params);
|
|
1436
|
+
const a = bodyActions(params);
|
|
1437
|
+
if (a !== void 0 && carets.shiftThrough(a)) emit();
|
|
1438
|
+
},
|
|
1439
|
+
onLocal: (listener) => basePort.onLocal((m) => {
|
|
1440
|
+
if (m.id === RICH_TEXT_EDITING) {
|
|
1441
|
+
const a = bodyActions(m.params);
|
|
1442
|
+
if (a !== void 0 && carets.shiftThrough(a)) emit();
|
|
1443
|
+
}
|
|
1444
|
+
listener(m);
|
|
1445
|
+
})
|
|
1446
|
+
};
|
|
1309
1447
|
awareness.on("change", publishAwareness);
|
|
1310
1448
|
const provider = new SupabaseYjsProvider({
|
|
1311
1449
|
workbookId: o.documentId,
|
|
@@ -1341,11 +1479,31 @@ function documentOtFactory(o) {
|
|
|
1341
1479
|
...o.onStatus ? { onStatus: o.onStatus } : {}
|
|
1342
1480
|
});
|
|
1343
1481
|
await room.session.ready;
|
|
1482
|
+
let lastKey = "";
|
|
1483
|
+
let lastAt = 0;
|
|
1484
|
+
const publishCaret = () => {
|
|
1485
|
+
if (typeof document !== "undefined" && document.hidden) return;
|
|
1486
|
+
let sel = null;
|
|
1487
|
+
try {
|
|
1488
|
+
sel = port.selection?.() ?? null;
|
|
1489
|
+
} catch {
|
|
1490
|
+
sel = null;
|
|
1491
|
+
}
|
|
1492
|
+
const t = Date.now();
|
|
1493
|
+
const c = publishedCaretFromSelection(sel, t);
|
|
1494
|
+
const key = c ? JSON.stringify(c.ranges) : "";
|
|
1495
|
+
if (key === lastKey && (key === "" || t - lastAt < CARET_HEARTBEAT_MS)) return;
|
|
1496
|
+
lastKey = key;
|
|
1497
|
+
lastAt = t;
|
|
1498
|
+
awareness.setLocalStateField("caret", c);
|
|
1499
|
+
};
|
|
1500
|
+
const caretTimer = setInterval(publishCaret, 120);
|
|
1344
1501
|
return {
|
|
1345
1502
|
isSaver: () => room.isSaver(),
|
|
1346
1503
|
write: (reason) => room.write(reason),
|
|
1347
1504
|
replace: (snapshot) => room.replace(snapshot),
|
|
1348
1505
|
stop: () => {
|
|
1506
|
+
clearInterval(caretTimer);
|
|
1349
1507
|
room.stop();
|
|
1350
1508
|
awareness.off("change", publishAwareness);
|
|
1351
1509
|
provider.disconnect();
|
|
@@ -1355,6 +1513,43 @@ function documentOtFactory(o) {
|
|
|
1355
1513
|
};
|
|
1356
1514
|
};
|
|
1357
1515
|
}
|
|
1516
|
+
|
|
1517
|
+
// src/docs/document-restore.ts
|
|
1518
|
+
import { collabReplace as collabReplace2, collabRoomExists as collabRoomExists2 } from "@ai-matrx/data/realtime/collab";
|
|
1519
|
+
var defaultDeps = {
|
|
1520
|
+
client: () => getDocsHost().getSupabase(),
|
|
1521
|
+
roomExists: collabRoomExists2,
|
|
1522
|
+
replace: collabReplace2,
|
|
1523
|
+
stateHash: (snapshot) => docsStateHash(snapshot),
|
|
1524
|
+
hasOpenEditor: (documentId) => hasOpenEditorContent("document", documentId),
|
|
1525
|
+
showInOpenEditor: (documentId, row) => showSavedSnapshot("document", documentId, row),
|
|
1526
|
+
async saveRow(args) {
|
|
1527
|
+
const res = await saveDocumentSnapshot({ documentId: args.documentId, snapshot: args.snapshot, origin: "restored", label: args.label });
|
|
1528
|
+
if (!res.success) throw new Error(res.error);
|
|
1529
|
+
return { id: res.data.id };
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
async function restoreDocumentSnapshot(args, deps = {}) {
|
|
1533
|
+
const d = { ...defaultDeps, ...deps };
|
|
1534
|
+
if (d.hasOpenEditor(args.documentId)) {
|
|
1535
|
+
const row2 = await d.saveRow(args);
|
|
1536
|
+
await d.showInOpenEditor(args.documentId, { id: row2.id, snapshot: args.snapshot });
|
|
1537
|
+
return { via: "open_editor", snapshotId: row2.id };
|
|
1538
|
+
}
|
|
1539
|
+
const client = d.client();
|
|
1540
|
+
if (await d.roomExists(client, args.documentId)) {
|
|
1541
|
+
const next = stampDeterministicIds(structuredClone(args.snapshot ?? {}));
|
|
1542
|
+
const out = await d.replace(client, {
|
|
1543
|
+
roomId: args.documentId,
|
|
1544
|
+
snapshot: next,
|
|
1545
|
+
stateHash: await d.stateHash(next),
|
|
1546
|
+
origin: "restore"
|
|
1547
|
+
});
|
|
1548
|
+
return { via: "room", rev: out.rev, snapshotId: out.ref };
|
|
1549
|
+
}
|
|
1550
|
+
const row = await d.saveRow(args);
|
|
1551
|
+
return { via: "saved", snapshotId: row.id };
|
|
1552
|
+
}
|
|
1358
1553
|
export {
|
|
1359
1554
|
COLLAB_COVERED_KEY,
|
|
1360
1555
|
DOCS_COLLAB_PROTO,
|
|
@@ -1378,6 +1573,7 @@ export {
|
|
|
1378
1573
|
normalizeDocsOp,
|
|
1379
1574
|
openDocsOtRoom,
|
|
1380
1575
|
rebaseUndoStack,
|
|
1576
|
+
restoreDocumentSnapshot,
|
|
1381
1577
|
stampCovered,
|
|
1382
1578
|
stampDeterministicIds,
|
|
1383
1579
|
stripSenderView,
|