@pm-cm/yjs 0.0.11 → 0.0.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/dist/index.cjs +158 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +158 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -98,6 +98,9 @@ function createYjsBridge(config, options) {
|
|
|
98
98
|
throw new Error("sharedProseMirror belongs to a different Y.Doc than the provided doc");
|
|
99
99
|
}
|
|
100
100
|
let lastBridgedText = null;
|
|
101
|
+
let pendingSkipCleanup = null;
|
|
102
|
+
let skipPending = false;
|
|
103
|
+
let parseFailed = false;
|
|
101
104
|
const syncTextToProsemirror = (origin) => {
|
|
102
105
|
const text = normalize(sharedText.toString());
|
|
103
106
|
if (lastBridgedText === text) {
|
|
@@ -110,6 +113,7 @@ function createYjsBridge(config, options) {
|
|
|
110
113
|
onError
|
|
111
114
|
});
|
|
112
115
|
if (result.ok) {
|
|
116
|
+
parseFailed = false;
|
|
113
117
|
let canonical = text;
|
|
114
118
|
if (origin === ORIGIN_INIT) {
|
|
115
119
|
try {
|
|
@@ -122,6 +126,9 @@ function createYjsBridge(config, options) {
|
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
lastBridgedText = canonical;
|
|
129
|
+
} else {
|
|
130
|
+
parseFailed = true;
|
|
131
|
+
lastBridgedText = text;
|
|
125
132
|
}
|
|
126
133
|
return result.ok;
|
|
127
134
|
};
|
|
@@ -183,13 +190,17 @@ function createYjsBridge(config, options) {
|
|
|
183
190
|
let parseError = false;
|
|
184
191
|
if (fallbackText.length > 0) {
|
|
185
192
|
replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize);
|
|
193
|
+
lastBridgedText = normalize(fallbackText);
|
|
186
194
|
const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {
|
|
187
195
|
schema,
|
|
188
196
|
parse,
|
|
189
197
|
normalize,
|
|
190
198
|
onError
|
|
191
199
|
});
|
|
192
|
-
if (!fallbackResult.ok)
|
|
200
|
+
if (!fallbackResult.ok) {
|
|
201
|
+
parseError = true;
|
|
202
|
+
parseFailed = true;
|
|
203
|
+
}
|
|
193
204
|
}
|
|
194
205
|
return { source: "text", ...parseError && { parseError: true } };
|
|
195
206
|
}
|
|
@@ -214,10 +225,57 @@ function createYjsBridge(config, options) {
|
|
|
214
225
|
}
|
|
215
226
|
if (transactionTouchedXmlFragment(transaction.changed, sharedProseMirror)) {
|
|
216
227
|
lastBridgedText = normalize(sharedText.toString());
|
|
228
|
+
parseFailed = false;
|
|
217
229
|
return;
|
|
218
230
|
}
|
|
219
231
|
if (skipOrigins !== null && skipOrigins.has(transaction.origin)) {
|
|
220
|
-
|
|
232
|
+
const expectedText = normalize(sharedText.toString());
|
|
233
|
+
lastBridgedText = expectedText;
|
|
234
|
+
const pmTextNow = sharedProseMirrorToText(sharedProseMirror);
|
|
235
|
+
if (pmTextNow !== null && normalize(pmTextNow) === expectedText) {
|
|
236
|
+
parseFailed = false;
|
|
237
|
+
if (pendingSkipCleanup) pendingSkipCleanup();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const xmlTextAtSkipStart = pmTextNow;
|
|
241
|
+
if (pendingSkipCleanup) pendingSkipCleanup();
|
|
242
|
+
skipPending = true;
|
|
243
|
+
let resolved = false;
|
|
244
|
+
const resolve = () => {
|
|
245
|
+
if (resolved) return;
|
|
246
|
+
resolved = true;
|
|
247
|
+
skipPending = false;
|
|
248
|
+
sharedProseMirror.unobserveDeep(xmlCatchUpObserver);
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
pendingSkipCleanup = null;
|
|
251
|
+
};
|
|
252
|
+
const xmlCatchUpObserver = (_events, transaction2) => {
|
|
253
|
+
if (resolved) return;
|
|
254
|
+
if (skipOrigins.has(transaction2.origin)) {
|
|
255
|
+
const pmText = sharedProseMirrorToText(sharedProseMirror);
|
|
256
|
+
if (pmText !== null && normalize(pmText) === expectedText) {
|
|
257
|
+
parseFailed = false;
|
|
258
|
+
resolve();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const runFallback = () => {
|
|
263
|
+
resolve();
|
|
264
|
+
if (lastBridgedText !== expectedText) return;
|
|
265
|
+
const pmText = sharedProseMirrorToText(sharedProseMirror);
|
|
266
|
+
if (pmText === null || normalize(pmText) !== expectedText) {
|
|
267
|
+
const changedDuringSkip = xmlTextAtSkipStart !== null && pmText !== null && normalize(pmText) !== normalize(xmlTextAtSkipStart) || xmlTextAtSkipStart === null && pmText !== null || xmlTextAtSkipStart !== null && pmText === null;
|
|
268
|
+
if (changedDuringSkip) {
|
|
269
|
+
lastBridgedText = pmText !== null ? normalize(pmText) : lastBridgedText;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
lastBridgedText = null;
|
|
273
|
+
syncTextToProsemirror(ORIGIN_TEXT_TO_PM);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const timer = setTimeout(runFallback, 500);
|
|
277
|
+
sharedProseMirror.observeDeep(xmlCatchUpObserver);
|
|
278
|
+
pendingSkipCleanup = resolve;
|
|
221
279
|
return;
|
|
222
280
|
}
|
|
223
281
|
syncTextToProsemirror(ORIGIN_TEXT_TO_PM);
|
|
@@ -227,6 +285,12 @@ function createYjsBridge(config, options) {
|
|
|
227
285
|
return {
|
|
228
286
|
bootstrapResult,
|
|
229
287
|
syncToSharedText(doc2) {
|
|
288
|
+
if (skipPending) {
|
|
289
|
+
return { ok: false, reason: "skip-pending" };
|
|
290
|
+
}
|
|
291
|
+
if (parseFailed) {
|
|
292
|
+
return { ok: false, reason: "parse-failed" };
|
|
293
|
+
}
|
|
230
294
|
let text;
|
|
231
295
|
try {
|
|
232
296
|
text = serialize(doc2);
|
|
@@ -246,6 +310,7 @@ function createYjsBridge(config, options) {
|
|
|
246
310
|
},
|
|
247
311
|
dispose() {
|
|
248
312
|
sharedText.unobserve(textObserver);
|
|
313
|
+
if (pendingSkipCleanup) pendingSkipCleanup();
|
|
249
314
|
}
|
|
250
315
|
};
|
|
251
316
|
}
|
|
@@ -297,7 +362,6 @@ var wiredBridges = /* @__PURE__ */ new WeakMap();
|
|
|
297
362
|
var defaultOnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
298
363
|
function createBridgeSyncPlugin(bridge, options = {}) {
|
|
299
364
|
const warn = options.onWarning ?? defaultOnWarning;
|
|
300
|
-
let yjsBatchSeen = false;
|
|
301
365
|
let needsSync = false;
|
|
302
366
|
return new Plugin({
|
|
303
367
|
key: bridgeSyncPluginKey,
|
|
@@ -308,10 +372,6 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
308
372
|
apply(tr, _prev) {
|
|
309
373
|
if (!tr.docChanged) return { needsSync };
|
|
310
374
|
if (bridge.isYjsSyncChange(tr)) {
|
|
311
|
-
yjsBatchSeen = true;
|
|
312
|
-
return { needsSync };
|
|
313
|
-
}
|
|
314
|
-
if (yjsBatchSeen && tr.getMeta("appendedTransaction")) {
|
|
315
375
|
return { needsSync };
|
|
316
376
|
}
|
|
317
377
|
needsSync = true;
|
|
@@ -329,6 +389,14 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
329
389
|
if (needsSync) {
|
|
330
390
|
const result = bridge.syncToSharedText(view.state.doc);
|
|
331
391
|
if (!result.ok) {
|
|
392
|
+
if (result.reason === "skip-pending") {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (result.reason === "parse-failed") {
|
|
396
|
+
options.onSyncFailure?.(result, view);
|
|
397
|
+
warn({ code: "sync-failed", message: `bridge sync failed: ${result.reason}` });
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
332
400
|
if (result.reason !== "unchanged") {
|
|
333
401
|
options.onSyncFailure?.(result, view);
|
|
334
402
|
warn({ code: "sync-failed", message: `bridge sync failed: ${result.reason}` });
|
|
@@ -336,12 +404,15 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
336
404
|
}
|
|
337
405
|
}
|
|
338
406
|
needsSync = false;
|
|
339
|
-
yjsBatchSeen = false;
|
|
340
407
|
},
|
|
341
408
|
destroy() {
|
|
342
409
|
const remaining = (wiredBridges.get(bridge) ?? 1) - 1;
|
|
343
|
-
if (remaining <= 0)
|
|
344
|
-
|
|
410
|
+
if (remaining <= 0) {
|
|
411
|
+
wiredBridges.delete(bridge);
|
|
412
|
+
if (options.autoDispose) bridge.dispose();
|
|
413
|
+
} else {
|
|
414
|
+
wiredBridges.set(bridge, remaining);
|
|
415
|
+
}
|
|
345
416
|
}
|
|
346
417
|
};
|
|
347
418
|
}
|
|
@@ -387,6 +458,8 @@ function broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAncho
|
|
|
387
458
|
awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead });
|
|
388
459
|
}
|
|
389
460
|
var defaultOnWarning2 = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
461
|
+
var awarenessRefCounts = /* @__PURE__ */ new WeakMap();
|
|
462
|
+
var awarenessFieldNames = /* @__PURE__ */ new WeakMap();
|
|
390
463
|
function createCursorSyncPlugin(options) {
|
|
391
464
|
const { awareness, serialize, sharedText } = options;
|
|
392
465
|
const warn = options.onWarning ?? defaultOnWarning2;
|
|
@@ -436,53 +509,80 @@ function createCursorSyncPlugin(options) {
|
|
|
436
509
|
}
|
|
437
510
|
},
|
|
438
511
|
view(editorView) {
|
|
512
|
+
const viewCount = awarenessRefCounts.get(awareness) ?? 0;
|
|
513
|
+
awarenessRefCounts.set(awareness, viewCount + 1);
|
|
514
|
+
let fieldNames = awarenessFieldNames.get(awareness);
|
|
515
|
+
if (!fieldNames) {
|
|
516
|
+
fieldNames = /* @__PURE__ */ new Set();
|
|
517
|
+
awarenessFieldNames.set(awareness, fieldNames);
|
|
518
|
+
}
|
|
519
|
+
fieldNames.add(cursorFieldName);
|
|
520
|
+
if (sharedText) {
|
|
521
|
+
fieldNames.add(cmCursorFieldName);
|
|
522
|
+
}
|
|
439
523
|
let suppressCmReaction = false;
|
|
440
524
|
let lastCmAbsAnchor = -1;
|
|
441
525
|
let lastCmAbsHead = -1;
|
|
442
526
|
let cmCursorHandledByListener = false;
|
|
527
|
+
let inAwarenessHandler = false;
|
|
443
528
|
const handleAwarenessUpdate = ({ added, updated }) => {
|
|
529
|
+
if (inAwarenessHandler) return;
|
|
444
530
|
if (suppressCmReaction) return;
|
|
445
531
|
if (!sharedText?.doc) return;
|
|
446
532
|
const localId = awareness.clientID;
|
|
447
533
|
if (!updated.includes(localId) && !added.includes(localId)) return;
|
|
448
534
|
const localState = awareness.getLocalState();
|
|
449
535
|
if (!localState) return;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
536
|
+
inAwarenessHandler = true;
|
|
537
|
+
try {
|
|
538
|
+
const cmCursor = localState[cmCursorFieldName];
|
|
539
|
+
if (!cmCursor?.anchor || !cmCursor?.head) {
|
|
540
|
+
if (lastCmAbsAnchor !== -1) {
|
|
541
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
542
|
+
lastCmAbsAnchor = -1;
|
|
543
|
+
lastCmAbsHead = -1;
|
|
544
|
+
}
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const absAnchor = createAbsolutePositionFromRelativePosition(cmCursor.anchor, sharedText.doc);
|
|
548
|
+
const absHead = createAbsolutePositionFromRelativePosition(cmCursor.head, sharedText.doc);
|
|
549
|
+
if (!absAnchor || !absHead) {
|
|
453
550
|
awareness.setLocalStateField(cursorFieldName, null);
|
|
454
551
|
lastCmAbsAnchor = -1;
|
|
455
552
|
lastCmAbsHead = -1;
|
|
553
|
+
return;
|
|
456
554
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
555
|
+
if (absAnchor.type !== sharedText || absHead.type !== sharedText) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return;
|
|
559
|
+
const map = getOrBuildMap(editorView.state.doc);
|
|
560
|
+
if (!map) {
|
|
561
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const pmAnchor = reverseCursorMapLookup(map, absAnchor.index);
|
|
565
|
+
const pmHead = reverseCursorMapLookup(map, absHead.index);
|
|
566
|
+
if (pmAnchor === null || pmHead === null) {
|
|
567
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
lastCmAbsAnchor = absAnchor.index;
|
|
571
|
+
lastCmAbsHead = absHead.index;
|
|
572
|
+
if (pendingCmCursor != null) {
|
|
573
|
+
cmCursorHandledByListener = true;
|
|
574
|
+
}
|
|
575
|
+
const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead);
|
|
576
|
+
if (!ok && !warnedSyncPluginMissing) {
|
|
577
|
+
warnedSyncPluginMissing = true;
|
|
578
|
+
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
579
|
+
}
|
|
580
|
+
} catch {
|
|
462
581
|
awareness.setLocalStateField(cursorFieldName, null);
|
|
463
582
|
lastCmAbsAnchor = -1;
|
|
464
583
|
lastCmAbsHead = -1;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return;
|
|
468
|
-
const map = getOrBuildMap(editorView.state.doc);
|
|
469
|
-
if (!map) {
|
|
470
|
-
awareness.setLocalStateField(cursorFieldName, null);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
const pmAnchor = reverseCursorMapLookup(map, absAnchor.index);
|
|
474
|
-
const pmHead = reverseCursorMapLookup(map, absHead.index);
|
|
475
|
-
if (pmAnchor === null || pmHead === null) {
|
|
476
|
-
awareness.setLocalStateField(cursorFieldName, null);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
lastCmAbsAnchor = absAnchor.index;
|
|
480
|
-
lastCmAbsHead = absHead.index;
|
|
481
|
-
cmCursorHandledByListener = true;
|
|
482
|
-
const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead);
|
|
483
|
-
if (!ok && !warnedSyncPluginMissing) {
|
|
484
|
-
warnedSyncPluginMissing = true;
|
|
485
|
-
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
584
|
+
} finally {
|
|
585
|
+
inAwarenessHandler = false;
|
|
486
586
|
}
|
|
487
587
|
};
|
|
488
588
|
if (sharedText) {
|
|
@@ -521,6 +621,9 @@ function createCursorSyncPlugin(options) {
|
|
|
521
621
|
}
|
|
522
622
|
} else {
|
|
523
623
|
awareness.setLocalStateField(cursorFieldName, null);
|
|
624
|
+
if (sharedText?.doc) {
|
|
625
|
+
awareness.setLocalStateField(cmCursorFieldName, null);
|
|
626
|
+
}
|
|
524
627
|
}
|
|
525
628
|
}
|
|
526
629
|
cmCursorHandledByListener = false;
|
|
@@ -555,9 +658,18 @@ function createCursorSyncPlugin(options) {
|
|
|
555
658
|
if (sharedText) {
|
|
556
659
|
awareness.off("update", handleAwarenessUpdate);
|
|
557
660
|
}
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
|
|
661
|
+
const remaining = (awarenessRefCounts.get(awareness) ?? 1) - 1;
|
|
662
|
+
if (remaining <= 0) {
|
|
663
|
+
awarenessRefCounts.delete(awareness);
|
|
664
|
+
const fields = awarenessFieldNames.get(awareness);
|
|
665
|
+
if (fields) {
|
|
666
|
+
for (const field of fields) {
|
|
667
|
+
awareness.setLocalStateField(field, null);
|
|
668
|
+
}
|
|
669
|
+
awarenessFieldNames.delete(awareness);
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
awarenessRefCounts.set(awareness, remaining);
|
|
561
673
|
}
|
|
562
674
|
}
|
|
563
675
|
};
|
|
@@ -598,7 +710,10 @@ function createCollabPlugins(schema, options) {
|
|
|
598
710
|
yUndoPlugin(options.yUndoPluginOpts)
|
|
599
711
|
];
|
|
600
712
|
if (options.bridge) {
|
|
601
|
-
plugins.push(createBridgeSyncPlugin(options.bridge, {
|
|
713
|
+
plugins.push(createBridgeSyncPlugin(options.bridge, {
|
|
714
|
+
onWarning: options.onWarning,
|
|
715
|
+
autoDispose: options.autoDisposeBridge
|
|
716
|
+
}));
|
|
602
717
|
}
|
|
603
718
|
if (enableCursorSync && options.serialize) {
|
|
604
719
|
plugins.push(
|