@arbidocs/coedit 0.4.155 → 0.4.157

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/server.cjs CHANGED
@@ -1917,7 +1917,6 @@ function applyJournal(doc, registry, journal, origin, context) {
1917
1917
 
1918
1918
  // src/sync.ts
1919
1919
  var LOCAL_ORIGIN = /* @__PURE__ */ Symbol("arbi-coedit/local");
1920
- var DEFAULT_REMOTE_DELAY_MS = 40;
1921
1920
  var MirrorSync = class {
1922
1921
  doc;
1923
1922
  registry = new NodeRegistry();
@@ -1926,14 +1925,11 @@ var MirrorSync = class {
1926
1925
  actorId;
1927
1926
  onFailure;
1928
1927
  onLocalJournal;
1928
+ aroundRemote;
1929
1929
  disposers = [];
1930
- remoteDelayMs;
1931
1930
  operation = 0;
1932
1931
  applyingRemote = false;
1933
1932
  failed = false;
1934
- remoteDirty = false;
1935
- remoteOrigin;
1936
- remoteTimer;
1937
1933
  /** Parts whose node ids must be re-registered before the next local journal; true means all. */
1938
1934
  staleParts = null;
1939
1935
  constructor(options) {
@@ -1942,7 +1938,7 @@ var MirrorSync = class {
1942
1938
  this.actorId = options.actorId;
1943
1939
  this.onFailure = options.onFailure;
1944
1940
  this.onLocalJournal = options.onLocalJournal;
1945
- this.remoteDelayMs = options.remoteDelayMs ?? DEFAULT_REMOTE_DELAY_MS;
1941
+ this.aroundRemote = options.aroundRemote;
1946
1942
  registerMirror(this.doc, this.registry);
1947
1943
  this.cache = new MaterializeCache(this.doc);
1948
1944
  this.disposers.push(() => this.cache.destroy());
@@ -1963,16 +1959,10 @@ var MirrorSync = class {
1963
1959
  const onAfterTransaction = (transaction) => {
1964
1960
  if (transaction.origin === LOCAL_ORIGIN || this.failed) return;
1965
1961
  if (transaction.changed.size === 0 && transaction.changedParentTypes.size === 0) return;
1966
- this.remoteDirty = true;
1967
- this.remoteOrigin = transaction.origin;
1968
- if (this.remoteDelayMs <= 0) this.flushRemote();
1969
- else if (this.remoteTimer === void 0) {
1970
- this.remoteTimer = setTimeout(() => this.flushRemote(), this.remoteDelayMs);
1971
- }
1962
+ this.publishRemote(transaction.origin);
1972
1963
  };
1973
1964
  this.doc.on("afterTransaction", onAfterTransaction);
1974
1965
  this.disposers.push(() => this.doc.off("afterTransaction", onAfterTransaction));
1975
- this.disposers.push(() => clearTimeout(this.remoteTimer));
1976
1966
  }
1977
1967
  get hasFailed() {
1978
1968
  return this.failed;
@@ -1981,17 +1971,8 @@ var MirrorSync = class {
1981
1971
  beforeRemote() {
1982
1972
  this.port.flushPendingJournals();
1983
1973
  }
1984
- /** Publish any waiting remote change now. Call before a local operation runs. */
1985
- flushRemote() {
1986
- clearTimeout(this.remoteTimer);
1987
- this.remoteTimer = void 0;
1988
- if (!this.remoteDirty || this.failed) return;
1989
- this.remoteDirty = false;
1990
- this.publishRemote(this.remoteOrigin);
1991
- }
1992
1974
  /** Make the local tree equal to the room: used when the editor opened an older snapshot of it. */
1993
1975
  adopt() {
1994
- this.remoteDirty = false;
1995
1976
  this.publishRemote(void 0);
1996
1977
  }
1997
1978
  /** Re-register node ids for parts a remote publication replaced; deferred until a local edit needs them. */
@@ -2001,23 +1982,29 @@ var MirrorSync = class {
2001
1982
  this.staleParts = null;
2002
1983
  }
2003
1984
  publishRemote(origin) {
1985
+ if (this.failed) return;
1986
+ const published = this.aroundRemote?.();
2004
1987
  this.applyingRemote = true;
2005
1988
  const changed = this.cache.take();
1989
+ let ok = false;
2006
1990
  try {
2007
1991
  const result = this.port.applyRemotePackage(materialize(this.doc, this.cache, changed), {
2008
- origin: "dev.docx-editor.collaboration.remote",
1992
+ // The editor's own origin for a peer's change: it carries the local caret only across those.
1993
+ origin: store.ORIGIN_IDS.mutationRemote,
2009
1994
  actorId: typeof origin === "string" ? origin : this.actorId,
2010
1995
  operationId: `${this.actorId}:remote:${this.operation++}`
2011
1996
  });
2012
1997
  if (!result.ok) throw new JournalError(result.reason);
2013
1998
  if (changed === null || this.staleParts === true) this.staleParts = true;
2014
1999
  else this.staleParts = /* @__PURE__ */ new Set([...this.staleParts ?? [], ...changed]);
2000
+ ok = true;
2015
2001
  } catch (error) {
2016
2002
  this.cache.restore(changed);
2017
2003
  this.fail(error instanceof MaterializeError ? "materialize" : "apply", error);
2018
2004
  } finally {
2019
2005
  this.applyingRemote = false;
2020
2006
  }
2007
+ if (ok) published?.();
2021
2008
  }
2022
2009
  fail(code, error) {
2023
2010
  if (this.failed) return;
@@ -2071,7 +2058,6 @@ var DocxCollaborationSession = class {
2071
2058
  doc;
2072
2059
  awareness;
2073
2060
  offlineEditing;
2074
- remoteDelayMs;
2075
2061
  tracker = collaboration.createCollaborationStatusTracker("initializing");
2076
2062
  statusListeners = /* @__PURE__ */ new Set();
2077
2063
  participantListeners = /* @__PURE__ */ new Set();
@@ -2084,6 +2070,10 @@ var DocxCollaborationSession = class {
2084
2070
  selectionsCache = [];
2085
2071
  selectionsKey = "[]";
2086
2072
  baselineVector;
2073
+ /** The selection the editor last published, carried across the remote changes since. */
2074
+ carried = null;
2075
+ selectionMove = null;
2076
+ inputFlush = null;
2087
2077
  disposers = [];
2088
2078
  constructor(options) {
2089
2079
  this.documentId = options.documentId;
@@ -2092,7 +2082,6 @@ var DocxCollaborationSession = class {
2092
2082
  this.doc = options.doc;
2093
2083
  this.awareness = options.awareness;
2094
2084
  this.offlineEditing = options.offlineEditing ?? false;
2095
- this.remoteDelayMs = options.remoteDelayMs;
2096
2085
  this.undoManager = new Y3__namespace.UndoManager(
2097
2086
  [
2098
2087
  this.doc.getMap("parts"),
@@ -2154,7 +2143,6 @@ var DocxCollaborationSession = class {
2154
2143
  return this.port !== void 0;
2155
2144
  }
2156
2145
  gateOperations() {
2157
- this.sync?.flushRemote();
2158
2146
  switch (this.status()) {
2159
2147
  case "ready":
2160
2148
  return this.port ? null : "collaboration-session-not-attached";
@@ -2183,7 +2171,7 @@ var DocxCollaborationSession = class {
2183
2171
  doc: this.doc,
2184
2172
  port,
2185
2173
  actorId: this.identity.actorId,
2186
- remoteDelayMs: this.remoteDelayMs,
2174
+ aroundRemote: () => this.carrySelection(),
2187
2175
  onFailure: (code, detail) => {
2188
2176
  this.markFailed(code === "journal" ? "local-mirror-failed" : "remote-apply-failed", detail);
2189
2177
  },
@@ -2207,8 +2195,21 @@ var DocxCollaborationSession = class {
2207
2195
  }
2208
2196
  /** Transport hook: call before integrating inbound Yjs data so queued local edits are mirrored first. */
2209
2197
  beforeRemote() {
2198
+ this.inputFlush?.();
2210
2199
  this.sync?.beforeRemote();
2211
2200
  }
2201
+ /**
2202
+ * Bind the editor's pending-input flush (`PaginatedSurface.flushPendingInput`). The session writes
2203
+ * below the editor's input buffer, so it lands queued typing before a remote change and lays the
2204
+ * change out right after it: the editor carries its caret at that layout, and a keystroke before
2205
+ * it would otherwise use the caret from before the change. Returns the unbind function.
2206
+ */
2207
+ bindEditorInput(flush) {
2208
+ this.inputFlush = flush;
2209
+ return () => {
2210
+ if (this.inputFlush === flush) this.inputFlush = null;
2211
+ };
2212
+ }
2212
2213
  // ── Undo ─────────────────────────────────────────────────────────────────
2213
2214
  canUndo() {
2214
2215
  return this.undoManager.undoStack.length > 0;
@@ -2221,7 +2222,6 @@ var DocxCollaborationSession = class {
2221
2222
  if (this.gateOperations() !== null || !this.canUndo()) return false;
2222
2223
  this.undoManager.stopCapturing();
2223
2224
  this.undoManager.undo();
2224
- this.sync?.flushRemote();
2225
2225
  return true;
2226
2226
  }
2227
2227
  redo() {
@@ -2229,13 +2229,79 @@ var DocxCollaborationSession = class {
2229
2229
  if (this.gateOperations() !== null || !this.canRedo()) return false;
2230
2230
  this.undoManager.stopCapturing();
2231
2231
  this.undoManager.redo();
2232
- this.sync?.flushRemote();
2233
2232
  return true;
2234
2233
  }
2235
2234
  // ── Presence ─────────────────────────────────────────────────────────────
2236
2235
  setLocalSelection(selection) {
2236
+ this.carried = selection && selection.kind !== "cells" ? { from: null, selection } : null;
2237
+ this.selectionMove = null;
2237
2238
  this.awareness.setLocalStateField("selection", selection);
2238
2239
  }
2240
+ /** Where the last remote change carried the local selection; see `carrySelection`. */
2241
+ remoteSelectionMove() {
2242
+ return this.selectionMove;
2243
+ }
2244
+ /**
2245
+ * Called before a remote change reaches the port: notes the selection's paragraphs and their text,
2246
+ * and returns what carries it across the change once the port holds the new tree. The editor
2247
+ * takes the carried selection when a local edit was coalesced with the remote change, where it
2248
+ * cannot map its caret by the text alone. Several remote changes before the editor publishes its
2249
+ * selection again chain, all from the selection it published.
2250
+ */
2251
+ carrySelection() {
2252
+ const port = this.port;
2253
+ const carried = this.carried;
2254
+ const settle = () => this.inputFlush?.();
2255
+ if (!port || !carried) return settle;
2256
+ const before = (address) => {
2257
+ const paragraph = port.paragraphByStableId(address.paragraphId);
2258
+ if (!paragraph) return null;
2259
+ const offset = Math.min(address.offset, paragraph.text.length);
2260
+ return { nodeId: paragraph.nodeId, text: paragraph.text, offset };
2261
+ };
2262
+ const anchor = before(carried.selection.anchor);
2263
+ const head = before(carried.selection.head);
2264
+ if (!anchor || !head) {
2265
+ this.carried = null;
2266
+ this.selectionMove = null;
2267
+ return settle;
2268
+ }
2269
+ const from = carried.from ?? {
2270
+ anchor: { nodeId: anchor.nodeId, offset: anchor.offset },
2271
+ head: { nodeId: head.nodeId, offset: head.offset }
2272
+ };
2273
+ return () => {
2274
+ const after = (address, was) => {
2275
+ const paragraph = port.paragraphByStableId(address.paragraphId);
2276
+ if (!paragraph) return null;
2277
+ const offset = replication.mapOffsetAcrossText(was.offset, was.text, paragraph.text);
2278
+ return { paragraphId: address.paragraphId, nodeId: paragraph.nodeId, offset };
2279
+ };
2280
+ const nextAnchor = after(carried.selection.anchor, anchor);
2281
+ const nextHead = after(carried.selection.head, head);
2282
+ if (!nextAnchor || !nextHead) {
2283
+ this.carried = null;
2284
+ this.selectionMove = null;
2285
+ } else {
2286
+ this.carried = {
2287
+ from,
2288
+ selection: {
2289
+ ...carried.selection,
2290
+ anchor: { paragraphId: nextAnchor.paragraphId, offset: nextAnchor.offset },
2291
+ head: { paragraphId: nextHead.paragraphId, offset: nextHead.offset }
2292
+ }
2293
+ };
2294
+ this.selectionMove = {
2295
+ from,
2296
+ to: {
2297
+ anchor: { nodeId: nextAnchor.nodeId, offset: nextAnchor.offset },
2298
+ head: { nodeId: nextHead.nodeId, offset: nextHead.offset }
2299
+ }
2300
+ };
2301
+ }
2302
+ settle();
2303
+ };
2304
+ }
2239
2305
  participants() {
2240
2306
  return this.participantsCache;
2241
2307
  }
@@ -2307,6 +2373,7 @@ var DocxCollaborationSession = class {
2307
2373
  this.sync?.destroy();
2308
2374
  this.sync = void 0;
2309
2375
  this.port = void 0;
2376
+ this.inputFlush = null;
2310
2377
  this.undoManager.destroy();
2311
2378
  this.awareness.setLocalState(null);
2312
2379
  for (const dispose of this.disposers.splice(0)) dispose();
@@ -2397,8 +2464,7 @@ function openRoomAutomation(options) {
2397
2464
  doc: options.doc,
2398
2465
  awareness: awareness$1,
2399
2466
  documentId: options.documentId,
2400
- identity: options.identity,
2401
- remoteDelayMs: 0
2467
+ identity: options.identity
2402
2468
  });
2403
2469
  session.markReady();
2404
2470
  try {
@@ -2430,8 +2496,7 @@ function openRoomStore(options) {
2430
2496
  doc: options.doc,
2431
2497
  awareness: awareness$1,
2432
2498
  documentId: options.documentId,
2433
- identity: options.identity,
2434
- remoteDelayMs: 0
2499
+ identity: options.identity
2435
2500
  });
2436
2501
  session.markReady();
2437
2502
  try {