@ai-matrx/office 0.7.0 → 0.8.0
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 +15 -0
- package/dist/docs.cjs +177 -28
- package/dist/docs.cjs.map +1 -1
- package/dist/docs.d.cts +83 -2
- package/dist/docs.d.ts +83 -2
- package/dist/docs.js +154 -5
- package/dist/docs.js.map +1 -1
- package/dist/univer-collab.cjs +1004 -1
- package/dist/univer-collab.cjs.map +1 -1
- package/dist/univer-collab.d.cts +252 -1
- package/dist/univer-collab.d.ts +252 -1
- package/dist/univer-collab.js +1010 -1
- package/dist/univer-collab.js.map +1 -1
- package/package.json +3 -2
package/dist/docs.d.ts
CHANGED
|
@@ -178,6 +178,60 @@ type CommandServiceLike = {
|
|
|
178
178
|
syncExecuteCommand(id: string, params: MutationParams, options?: ExecuteOptions): unknown;
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* The Docs co-editing room on the ordered log (COLLAB-DESIGN §6, unit C3): the `OpDocumentAdapter` for
|
|
183
|
+
* `openOpSession` over a document's editors, plus saves, restore and the saver choice.
|
|
184
|
+
*
|
|
185
|
+
* - CAPTURE (§6.3). The document model hands over every local `doc.mutation.rich-text-editing` with the applied
|
|
186
|
+
* params (the handler's own rewrite, C0 surprise 6), the pre-state cloned in `beforeCommandExecuted`, and the
|
|
187
|
+
* outermost command id. `onlyLocal` / `fromCollab` never arrive here (the model filters them). Only `segmentId`
|
|
188
|
+
* and `actions` travel; the inverse is computed from the APPLIED actions on a COPY (`invertWithDoc` mutates its
|
|
189
|
+
* op). Each mutation goes to the session as it happens, so a remote apply can never land between a local
|
|
190
|
+
* mutation and its hand-over; the command id rides along as the change's label.
|
|
191
|
+
* - APPLY (§6.7). `syncExecuteCommand(id, { unitId: own, segmentId, actions, textRanges: null,
|
|
192
|
+
* noNeedSetTextRange: true }, { onlyLocal: true, fromCollab: true })` through the model (every view of the
|
|
193
|
+
* document in this tab). A throw, `false`, or actions the handler emptied fail the apply: the session rebuilds,
|
|
194
|
+
* never skips. While an IME composition is open, remote applies QUEUE (acks continue); a local op captured
|
|
195
|
+
* meanwhile is rebased over the queue (`L' = T(L, Q, 'left')`, `Q' = T(Q, L, 'right')`), and the queue drains
|
|
196
|
+
* at `compositionend`.
|
|
197
|
+
* - UNDO (§6.9). Each applied remote op rebases every stored undo and redo item of this seat
|
|
198
|
+
* (`T(item, R1, 'left')`); `textRanges` and `layoutState` are stripped from rebased items; an item whose rebase
|
|
199
|
+
* is a barrier, throws or comes back empty is dropped. Undo therefore only ever reverts this person's changes.
|
|
200
|
+
* - SAVES (§7.3). Only a quiet seat whose hash matches every peer at its rev saves, and only the chosen saver
|
|
201
|
+
* (the lowest client id among seats publishing a quiet hash) saves on its own; a manual save always tries.
|
|
202
|
+
* - RESTORE (§20). A whole-document replace goes through `collab_replace`: the snapshot is loaded into this seat's
|
|
203
|
+
* editor first, so the stored snapshot and its hash are the Univer-normalised form every seat will load.
|
|
204
|
+
*/
|
|
205
|
+
|
|
206
|
+
/** One local mutation as the document model hands it over. */
|
|
207
|
+
interface DocsLocalMutation {
|
|
208
|
+
id: string;
|
|
209
|
+
params: unknown;
|
|
210
|
+
/** The document before this mutation (cloned in `beforeCommandExecuted`), when the view could supply it. */
|
|
211
|
+
pre: unknown | null;
|
|
212
|
+
/** The outermost command this mutation belongs to (the depth tracker with its try/finally guard). */
|
|
213
|
+
cmd: string | null;
|
|
214
|
+
/** Whether the mutation changes what a snapshot stores. */
|
|
215
|
+
content: boolean;
|
|
216
|
+
}
|
|
217
|
+
/** What the document model lends the room (implemented by `DocumentModel`). */
|
|
218
|
+
interface DocsOtPort {
|
|
219
|
+
onLocal(listener: (m: DocsLocalMutation) => void): () => void;
|
|
220
|
+
/** Apply one committed mutation to every view of the document; throws when any view could not. */
|
|
221
|
+
applyRemote(id: string, params: Record<string, unknown>): void;
|
|
222
|
+
/** The document as the editor holds it now (`doc.save()`), or null when no view is mounted. */
|
|
223
|
+
snapshot(): unknown | null;
|
|
224
|
+
/** Replace the document in every view (and the kept editor). */
|
|
225
|
+
load(snapshot: unknown): void;
|
|
226
|
+
/** Univer's undo/redo service of each mounted view and the unit it holds. */
|
|
227
|
+
undoTargets(): Array<{
|
|
228
|
+
service: unknown;
|
|
229
|
+
unitId: string;
|
|
230
|
+
}>;
|
|
231
|
+
/** Ask for a save (the record's one coalesced save). */
|
|
232
|
+
markDirty(): void;
|
|
233
|
+
}
|
|
234
|
+
|
|
181
235
|
/**
|
|
182
236
|
* THE DOCUMENT MODEL — the engine of one cloud document per tab.
|
|
183
237
|
*
|
|
@@ -226,6 +280,8 @@ interface DocumentViewPort {
|
|
|
226
280
|
remount: (snapshot: DocumentSnapshotData) => void;
|
|
227
281
|
/** Whether this view may change the document (read at edit time). */
|
|
228
282
|
editable: () => boolean;
|
|
283
|
+
/** Univer's undo/redo service of this view's instance (the co-editing room rebases its items, §6.9). */
|
|
284
|
+
undoRedo?: () => unknown;
|
|
229
285
|
/**
|
|
230
286
|
* The Univer unit id of this view's mounted document. Every seat mounts its own unit, so a
|
|
231
287
|
* mutation carries the SENDER's id and must be re-addressed to this view's unit before it is
|
|
@@ -257,6 +313,14 @@ interface DocumentModelDeps {
|
|
|
257
313
|
/** A mutation could not be applied to a view: the app says so (a notice); the model re-syncs from the saved version. */
|
|
258
314
|
onApplyFailed?: (documentId: string, mutationId: string, error: unknown) => void;
|
|
259
315
|
}
|
|
316
|
+
/** The document's room on the ordered log (COLLAB-DESIGN §6, C3), bound through `startOtRoom`. */
|
|
317
|
+
interface DocumentOtHandle {
|
|
318
|
+
stop: () => void;
|
|
319
|
+
write: (reason: CommitReason) => Promise<boolean>;
|
|
320
|
+
replace: (snapshot: DocumentSnapshotData) => Promise<void>;
|
|
321
|
+
isSaver: () => boolean;
|
|
322
|
+
}
|
|
323
|
+
type DocumentOtFactory = (port: DocsOtPort, onAwareness: (awareness: DocumentAwareness) => void) => Promise<DocumentOtHandle | null>;
|
|
260
324
|
/** A collab room (WorkbookCollabSession) bound to the model's command stream. */
|
|
261
325
|
interface DocumentCollabHandle {
|
|
262
326
|
stop: () => void;
|
|
@@ -314,6 +378,10 @@ declare class DocumentModel {
|
|
|
314
378
|
private readonly knownSnapshots;
|
|
315
379
|
private readonly collabListeners;
|
|
316
380
|
private collab;
|
|
381
|
+
/** The ordered-log room (C3); exclusive with `collab` (the 0.6 relay room). */
|
|
382
|
+
private ot;
|
|
383
|
+
private otStarting;
|
|
384
|
+
private readonly otListeners;
|
|
317
385
|
/** The saved snapshot the body started from, and the room entries it already holds. */
|
|
318
386
|
private baseId;
|
|
319
387
|
private baseCovered;
|
|
@@ -347,6 +415,13 @@ declare class DocumentModel {
|
|
|
347
415
|
/** The kept editor, handed to the view that mounts next (null when none is kept). */
|
|
348
416
|
takeParkedEditor(): ParkedDocumentEditor | null;
|
|
349
417
|
private onViewMutation;
|
|
418
|
+
/**
|
|
419
|
+
* Per view: the outermost command running (a mutation's group label) and, while a room listens, the document
|
|
420
|
+
* before each rich-text mutation (its inverse is computed from the APPLIED actions against this copy). Univer's
|
|
421
|
+
* `executeCommand` has no `finally`: a command that mutates then throws never fires `onCommandExecuted`, so
|
|
422
|
+
* the depth is restored in a try/finally around the instance's `executeCommand` (C0 surprise 7).
|
|
423
|
+
*/
|
|
424
|
+
private trackCommands;
|
|
350
425
|
/** The unit id a view's document has (what a replayed mutation must be addressed to). */
|
|
351
426
|
private unitIdOf;
|
|
352
427
|
/** Replay one mutation into every view except `exceptViewId`, addressed to each view's own unit. */
|
|
@@ -390,7 +465,7 @@ declare class DocumentModel {
|
|
|
390
465
|
replaceContent(row: {
|
|
391
466
|
id: string;
|
|
392
467
|
snapshot: DocumentSnapshotData;
|
|
393
|
-
}): void
|
|
468
|
+
}): void | Promise<void>;
|
|
394
469
|
/** A peer replaced the document: show the newest save (once). */
|
|
395
470
|
private onPeerReplaced;
|
|
396
471
|
/** A loaded row becomes the base: its content (what views mount) and the room entries it holds. */
|
|
@@ -404,6 +479,12 @@ declare class DocumentModel {
|
|
|
404
479
|
private readonly virtualCommandService;
|
|
405
480
|
/** Join the document's collab room once (the first view that asks). */
|
|
406
481
|
startCollab(factory: DocumentCollabFactory): Promise<void>;
|
|
482
|
+
/** The model as the ordered-log room sees it (COLLAB-DESIGN §6.7: the model is the client, not the view). */
|
|
483
|
+
private readonly otPort;
|
|
484
|
+
/** Join the document's ordered-log room once (C3). Exclusive with `startCollab`. */
|
|
485
|
+
startOtRoom(factory: DocumentOtFactory): Promise<void>;
|
|
486
|
+
/** Which co-editing room this document is in right now. */
|
|
487
|
+
collabMode(): "ordered" | "relay" | null;
|
|
407
488
|
isCollabHost(): boolean;
|
|
408
489
|
getAwareness(): DocumentAwareness | null;
|
|
409
490
|
subscribeAwareness(listener: () => void): () => void;
|
|
@@ -602,4 +683,4 @@ declare function documentBodyWriteHandler(deps: {
|
|
|
602
683
|
assertWritable: (whatItBlocks: string) => void;
|
|
603
684
|
}): DocumentBodyWriteHandler;
|
|
604
685
|
|
|
605
|
-
export { type BodyEdit, type BodyText, type CreateDocumentArgs, DOCUMENT_BODY_MAX_LENGTH, DOCUMENT_DESCRIPTION_MAX_LENGTH, DOCUMENT_NAME_MAX_LENGTH, DOCUMENT_SAVE_DELAY_MS, type DocsHostPorts, type DocsSupabase, type DocumentAwareness, type DocumentBodyPort, type DocumentBodyWriteHandler, type DocumentCollabBase, type DocumentCollabFactory, type DocumentCollabHandle, DocumentModel, type DocumentModelDeps, type DocumentSnapshotData, type DocumentViewPort, EMOJI_GROUPS, type Glyph, MIN_FIT_ZOOM, type ParkedDocumentEditor, SPECIAL_GROUPS, type SaveDocumentSnapshotArgs, bodyTextOf, configureDocsHost, copyDocument, createDocument, deleteDocument, discardFailedDocument, documentBodyWriteHandler, documentSourceLabel, fitZoomRatio, getDocsHost, getDocument, getLatestDocumentSnapshot, listAccessibleDocuments, listDocumentSnapshots, listTrashedDocuments, moveDocument, planBodyEdit, renameDocument, restoreDocument, sanitizeUniverDocSnapshot, saveDocumentSnapshot, searchSymbols, updateDocumentDescription, validateDocumentBody, validateDocumentDescription, validateDocumentName, writeBodyText };
|
|
686
|
+
export { type BodyEdit, type BodyText, type CreateDocumentArgs, DOCUMENT_BODY_MAX_LENGTH, DOCUMENT_DESCRIPTION_MAX_LENGTH, DOCUMENT_NAME_MAX_LENGTH, DOCUMENT_SAVE_DELAY_MS, type DocsHostPorts, type DocsSupabase, type DocumentAwareness, type DocumentBodyPort, type DocumentBodyWriteHandler, type DocumentCollabBase, type DocumentCollabFactory, type DocumentCollabHandle, DocumentModel, type DocumentModelDeps, type DocumentOtFactory, type DocumentOtHandle, type DocumentSnapshotData, type DocumentViewPort, EMOJI_GROUPS, type Glyph, MIN_FIT_ZOOM, type ParkedDocumentEditor, SPECIAL_GROUPS, type SaveDocumentSnapshotArgs, bodyTextOf, configureDocsHost, copyDocument, createDocument, deleteDocument, discardFailedDocument, documentBodyWriteHandler, documentSourceLabel, fitZoomRatio, getDocsHost, getDocument, getLatestDocumentSnapshot, listAccessibleDocuments, listDocumentSnapshots, listTrashedDocuments, moveDocument, planBodyEdit, renameDocument, restoreDocument, sanitizeUniverDocSnapshot, saveDocumentSnapshot, searchSymbols, updateDocumentDescription, validateDocumentBody, validateDocumentDescription, validateDocumentName, writeBodyText };
|
package/dist/docs.js
CHANGED
|
@@ -45,6 +45,9 @@ function takeCovered(snapshot) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// src/docs/document-model.ts
|
|
48
|
+
var RICH_TEXT_EDITING_ID = "doc.mutation.rich-text-editing";
|
|
49
|
+
var COMMAND_TYPE = 0;
|
|
50
|
+
var DEPTH_GUARDED = /* @__PURE__ */ Symbol.for("ai-matrx.office.docs.command-depth-guard");
|
|
48
51
|
var DOCUMENT_SAVE_DELAY_MS = 2500;
|
|
49
52
|
var viewIds = 0;
|
|
50
53
|
var DocumentModel = class {
|
|
@@ -61,6 +64,10 @@ var DocumentModel = class {
|
|
|
61
64
|
knownSnapshots = /* @__PURE__ */ new Set();
|
|
62
65
|
collabListeners = /* @__PURE__ */ new Set();
|
|
63
66
|
collab = null;
|
|
67
|
+
/** The ordered-log room (C3); exclusive with `collab` (the 0.6 relay room). */
|
|
68
|
+
ot = null;
|
|
69
|
+
otStarting = null;
|
|
70
|
+
otListeners = /* @__PURE__ */ new Set();
|
|
64
71
|
/** The saved snapshot the body started from, and the room entries it already holds. */
|
|
65
72
|
baseId = null;
|
|
66
73
|
baseCovered = /* @__PURE__ */ new Set();
|
|
@@ -124,7 +131,14 @@ var DocumentModel = class {
|
|
|
124
131
|
const subscription = port.commandService.onMutationExecutedForCollab(
|
|
125
132
|
(info, options) => this.onViewMutation(id, info, options)
|
|
126
133
|
);
|
|
127
|
-
|
|
134
|
+
const view = { id, port, dispose: () => {
|
|
135
|
+
}, commands: [], pre: null };
|
|
136
|
+
const hooks = this.trackCommands(view);
|
|
137
|
+
view.dispose = () => {
|
|
138
|
+
subscription.dispose();
|
|
139
|
+
hooks();
|
|
140
|
+
};
|
|
141
|
+
this.views.set(id, view);
|
|
128
142
|
if (!this.handle.hasPending()) {
|
|
129
143
|
try {
|
|
130
144
|
const mounted = port.snapshot();
|
|
@@ -137,8 +151,8 @@ var DocumentModel = class {
|
|
|
137
151
|
return () => {
|
|
138
152
|
if (detached) return;
|
|
139
153
|
detached = true;
|
|
140
|
-
const
|
|
141
|
-
if (!
|
|
154
|
+
const view2 = this.views.get(id);
|
|
155
|
+
if (!view2) return;
|
|
142
156
|
if (this.views.size === 1) {
|
|
143
157
|
try {
|
|
144
158
|
const snapshot = port.snapshot();
|
|
@@ -147,7 +161,7 @@ var DocumentModel = class {
|
|
|
147
161
|
console.warn(`[document-model] could not keep ${this.documentId}'s last view's document`, error);
|
|
148
162
|
}
|
|
149
163
|
}
|
|
150
|
-
|
|
164
|
+
view2.dispose();
|
|
151
165
|
this.views.delete(id);
|
|
152
166
|
if (this.lastEditedView === id) this.lastEditedView = null;
|
|
153
167
|
};
|
|
@@ -177,8 +191,63 @@ var DocumentModel = class {
|
|
|
177
191
|
this.lastEditedView = viewId;
|
|
178
192
|
this.relay(info, viewId);
|
|
179
193
|
for (const listener of this.collabListeners) listener(info, options);
|
|
194
|
+
if (this.otListeners.size && !options?.onlyLocal) {
|
|
195
|
+
const m = {
|
|
196
|
+
id: info.id,
|
|
197
|
+
params: info.params,
|
|
198
|
+
pre: info.id === RICH_TEXT_EDITING_ID ? view.pre : null,
|
|
199
|
+
cmd: view.commands[0] ?? null,
|
|
200
|
+
content: isContentMutation(info)
|
|
201
|
+
};
|
|
202
|
+
view.pre = null;
|
|
203
|
+
for (const listener of Array.from(this.otListeners)) listener(m);
|
|
204
|
+
}
|
|
180
205
|
if (isContentMutation(info)) this.markDirty();
|
|
181
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Per view: the outermost command running (a mutation's group label) and, while a room listens, the document
|
|
209
|
+
* before each rich-text mutation (its inverse is computed from the APPLIED actions against this copy). Univer's
|
|
210
|
+
* `executeCommand` has no `finally`: a command that mutates then throws never fires `onCommandExecuted`, so
|
|
211
|
+
* the depth is restored in a try/finally around the instance's `executeCommand` (C0 surprise 7).
|
|
212
|
+
*/
|
|
213
|
+
trackCommands(view) {
|
|
214
|
+
const cs = view.port.commandService;
|
|
215
|
+
if (typeof cs.beforeCommandExecuted !== "function" || typeof cs.onCommandExecuted !== "function") return () => {
|
|
216
|
+
};
|
|
217
|
+
const before = cs.beforeCommandExecuted((info) => {
|
|
218
|
+
if (info.type === COMMAND_TYPE) view.commands.push(info.id);
|
|
219
|
+
else if (info.id === RICH_TEXT_EDITING_ID && this.otListeners.size && !this.relaying) {
|
|
220
|
+
try {
|
|
221
|
+
const snap = view.port.snapshot();
|
|
222
|
+
view.pre = snap ? structuredClone(snap) : null;
|
|
223
|
+
} catch {
|
|
224
|
+
view.pre = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const after = cs.onCommandExecuted((info) => {
|
|
229
|
+
if (info.type !== COMMAND_TYPE) return;
|
|
230
|
+
const at = view.commands.lastIndexOf(info.id);
|
|
231
|
+
if (at >= 0) view.commands.splice(at, 1);
|
|
232
|
+
});
|
|
233
|
+
const guarded = cs;
|
|
234
|
+
if (typeof cs.executeCommand === "function" && !guarded[DEPTH_GUARDED]) {
|
|
235
|
+
const original = cs.executeCommand.bind(cs);
|
|
236
|
+
guarded[DEPTH_GUARDED] = true;
|
|
237
|
+
cs.executeCommand = async (id, params, options) => {
|
|
238
|
+
const depth = view.commands.length;
|
|
239
|
+
try {
|
|
240
|
+
return await original(id, params, options);
|
|
241
|
+
} finally {
|
|
242
|
+
if (view.commands.length > depth) view.commands.length = depth;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return () => {
|
|
247
|
+
before.dispose();
|
|
248
|
+
after.dispose();
|
|
249
|
+
};
|
|
250
|
+
}
|
|
182
251
|
/** The unit id a view's document has (what a replayed mutation must be addressed to). */
|
|
183
252
|
unitIdOf(view) {
|
|
184
253
|
try {
|
|
@@ -242,6 +311,11 @@ var DocumentModel = class {
|
|
|
242
311
|
* peer's edits reach it through the room). An explicit Save always writes.
|
|
243
312
|
*/
|
|
244
313
|
async write(reason) {
|
|
314
|
+
if (this.ot) {
|
|
315
|
+
const wrote = await this.ot.write(reason);
|
|
316
|
+
if (wrote) this.deps.onSaved?.(this.documentId, reason === "manual" ? "manual" : "autosave");
|
|
317
|
+
return wrote;
|
|
318
|
+
}
|
|
245
319
|
const snapshot = this.latest();
|
|
246
320
|
if (!snapshot) throw new Error("There is no open document to save.");
|
|
247
321
|
if (this.collab && !this.collab.isHost() && reason !== "manual") return false;
|
|
@@ -286,7 +360,7 @@ var DocumentModel = class {
|
|
|
286
360
|
* person chooses; `overUnsaved` is "Take theirs" (`./documentModels.ts`).
|
|
287
361
|
*/
|
|
288
362
|
async onRemoteSnapshot(snapshotId, { overUnsaved = false } = {}) {
|
|
289
|
-
if (this.collab) return;
|
|
363
|
+
if (this.collab || this.ot) return;
|
|
290
364
|
if (this.knownSnapshots.has(snapshotId)) return;
|
|
291
365
|
if (this.saving) return;
|
|
292
366
|
if (this.hasUnsaved() && !overUnsaved) {
|
|
@@ -305,6 +379,7 @@ var DocumentModel = class {
|
|
|
305
379
|
* room the peers reload it and the shared log is emptied.
|
|
306
380
|
*/
|
|
307
381
|
replaceContent(row) {
|
|
382
|
+
if (this.ot) return this.ot.replace(row.snapshot);
|
|
308
383
|
this.show(row);
|
|
309
384
|
this.collab?.announceReplaced(row.id);
|
|
310
385
|
}
|
|
@@ -388,7 +463,79 @@ var DocumentModel = class {
|
|
|
388
463
|
}
|
|
389
464
|
return this.collabStarting;
|
|
390
465
|
}
|
|
466
|
+
/** The model as the ordered-log room sees it (COLLAB-DESIGN §6.7: the model is the client, not the view). */
|
|
467
|
+
otPort = {
|
|
468
|
+
onLocal: (listener) => {
|
|
469
|
+
this.otListeners.add(listener);
|
|
470
|
+
return () => void this.otListeners.delete(listener);
|
|
471
|
+
},
|
|
472
|
+
applyRemote: (id, params) => {
|
|
473
|
+
if (this.views.size === 0) throw new Error("no view of the document is mounted");
|
|
474
|
+
this.relaying = true;
|
|
475
|
+
try {
|
|
476
|
+
for (const view of this.views.values()) {
|
|
477
|
+
const unitId = this.unitIdOf(view);
|
|
478
|
+
const input = Array.isArray(params.actions) ? params.actions.length : 0;
|
|
479
|
+
const p = { ...params, ...unitId ? { unitId } : {}, textRanges: null, noNeedSetTextRange: true };
|
|
480
|
+
const ok = view.port.commandService.syncExecuteCommand(id, p, { onlyLocal: true, fromCollab: true });
|
|
481
|
+
if (ok === false) throw new Error(`"${id}" was refused by the editor`);
|
|
482
|
+
if (input > 0 && Array.isArray(p.actions) && p.actions.length === 0) throw new Error(`"${id}" was emptied by the editor`);
|
|
483
|
+
}
|
|
484
|
+
} finally {
|
|
485
|
+
this.relaying = false;
|
|
486
|
+
}
|
|
487
|
+
if (isContentMutation({ id, type: 2, params })) this.markDirty();
|
|
488
|
+
},
|
|
489
|
+
snapshot: () => this.latest(),
|
|
490
|
+
load: (snapshot) => {
|
|
491
|
+
const s2 = snapshot;
|
|
492
|
+
this.stored = s2;
|
|
493
|
+
this.savedFingerprint = contentFingerprint(s2);
|
|
494
|
+
this.relaying = true;
|
|
495
|
+
try {
|
|
496
|
+
for (const view of this.views.values()) view.port.remount(s2);
|
|
497
|
+
this.parked?.remount(s2);
|
|
498
|
+
} finally {
|
|
499
|
+
this.relaying = false;
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
undoTargets: () => {
|
|
503
|
+
const out = [];
|
|
504
|
+
for (const view of this.views.values()) {
|
|
505
|
+
const unitId = this.unitIdOf(view);
|
|
506
|
+
const service = view.port.undoRedo?.();
|
|
507
|
+
if (unitId && service) out.push({ service, unitId });
|
|
508
|
+
}
|
|
509
|
+
return out;
|
|
510
|
+
},
|
|
511
|
+
markDirty: () => this.markDirty()
|
|
512
|
+
};
|
|
513
|
+
/** Join the document's ordered-log room once (C3). Exclusive with `startCollab`. */
|
|
514
|
+
startOtRoom(factory) {
|
|
515
|
+
if (this.ot || this.collab) return Promise.resolve();
|
|
516
|
+
if (!this.otStarting) {
|
|
517
|
+
this.otStarting = factory(this.otPort, (awareness) => {
|
|
518
|
+
this.awareness = awareness;
|
|
519
|
+
for (const listener of Array.from(this.awarenessListeners)) listener();
|
|
520
|
+
}).then((handle) => {
|
|
521
|
+
if (!handle) return;
|
|
522
|
+
if (this.views.size === 0) {
|
|
523
|
+
handle.stop();
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
this.ot = handle;
|
|
527
|
+
}).finally(() => {
|
|
528
|
+
this.otStarting = null;
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return this.otStarting;
|
|
532
|
+
}
|
|
533
|
+
/** Which co-editing room this document is in right now. */
|
|
534
|
+
collabMode() {
|
|
535
|
+
return this.ot ? "ordered" : this.collab ? "relay" : null;
|
|
536
|
+
}
|
|
391
537
|
isCollabHost() {
|
|
538
|
+
if (this.ot) return this.ot.isSaver();
|
|
392
539
|
return this.collab ? this.collab.isHost() : true;
|
|
393
540
|
}
|
|
394
541
|
getAwareness() {
|
|
@@ -416,6 +563,8 @@ var DocumentModel = class {
|
|
|
416
563
|
rest() {
|
|
417
564
|
this.collab?.stop();
|
|
418
565
|
this.collab = null;
|
|
566
|
+
this.ot?.stop();
|
|
567
|
+
this.ot = null;
|
|
419
568
|
this.awareness = null;
|
|
420
569
|
}
|
|
421
570
|
closed = false;
|