@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.cjs
CHANGED
|
@@ -141,6 +141,9 @@ function createYjsBridge(config, options) {
|
|
|
141
141
|
throw new Error("sharedProseMirror belongs to a different Y.Doc than the provided doc");
|
|
142
142
|
}
|
|
143
143
|
let lastBridgedText = null;
|
|
144
|
+
let pendingSkipCleanup = null;
|
|
145
|
+
let skipPending = false;
|
|
146
|
+
let parseFailed = false;
|
|
144
147
|
const syncTextToProsemirror = (origin) => {
|
|
145
148
|
const text = normalize(sharedText.toString());
|
|
146
149
|
if (lastBridgedText === text) {
|
|
@@ -153,6 +156,7 @@ function createYjsBridge(config, options) {
|
|
|
153
156
|
onError
|
|
154
157
|
});
|
|
155
158
|
if (result.ok) {
|
|
159
|
+
parseFailed = false;
|
|
156
160
|
let canonical = text;
|
|
157
161
|
if (origin === ORIGIN_INIT) {
|
|
158
162
|
try {
|
|
@@ -165,6 +169,9 @@ function createYjsBridge(config, options) {
|
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
lastBridgedText = canonical;
|
|
172
|
+
} else {
|
|
173
|
+
parseFailed = true;
|
|
174
|
+
lastBridgedText = text;
|
|
168
175
|
}
|
|
169
176
|
return result.ok;
|
|
170
177
|
};
|
|
@@ -226,13 +233,17 @@ function createYjsBridge(config, options) {
|
|
|
226
233
|
let parseError = false;
|
|
227
234
|
if (fallbackText.length > 0) {
|
|
228
235
|
replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize);
|
|
236
|
+
lastBridgedText = normalize(fallbackText);
|
|
229
237
|
const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {
|
|
230
238
|
schema,
|
|
231
239
|
parse,
|
|
232
240
|
normalize,
|
|
233
241
|
onError
|
|
234
242
|
});
|
|
235
|
-
if (!fallbackResult.ok)
|
|
243
|
+
if (!fallbackResult.ok) {
|
|
244
|
+
parseError = true;
|
|
245
|
+
parseFailed = true;
|
|
246
|
+
}
|
|
236
247
|
}
|
|
237
248
|
return { source: "text", ...parseError && { parseError: true } };
|
|
238
249
|
}
|
|
@@ -257,10 +268,57 @@ function createYjsBridge(config, options) {
|
|
|
257
268
|
}
|
|
258
269
|
if (transactionTouchedXmlFragment(transaction.changed, sharedProseMirror)) {
|
|
259
270
|
lastBridgedText = normalize(sharedText.toString());
|
|
271
|
+
parseFailed = false;
|
|
260
272
|
return;
|
|
261
273
|
}
|
|
262
274
|
if (skipOrigins !== null && skipOrigins.has(transaction.origin)) {
|
|
263
|
-
|
|
275
|
+
const expectedText = normalize(sharedText.toString());
|
|
276
|
+
lastBridgedText = expectedText;
|
|
277
|
+
const pmTextNow = sharedProseMirrorToText(sharedProseMirror);
|
|
278
|
+
if (pmTextNow !== null && normalize(pmTextNow) === expectedText) {
|
|
279
|
+
parseFailed = false;
|
|
280
|
+
if (pendingSkipCleanup) pendingSkipCleanup();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const xmlTextAtSkipStart = pmTextNow;
|
|
284
|
+
if (pendingSkipCleanup) pendingSkipCleanup();
|
|
285
|
+
skipPending = true;
|
|
286
|
+
let resolved = false;
|
|
287
|
+
const resolve = () => {
|
|
288
|
+
if (resolved) return;
|
|
289
|
+
resolved = true;
|
|
290
|
+
skipPending = false;
|
|
291
|
+
sharedProseMirror.unobserveDeep(xmlCatchUpObserver);
|
|
292
|
+
clearTimeout(timer);
|
|
293
|
+
pendingSkipCleanup = null;
|
|
294
|
+
};
|
|
295
|
+
const xmlCatchUpObserver = (_events, transaction2) => {
|
|
296
|
+
if (resolved) return;
|
|
297
|
+
if (skipOrigins.has(transaction2.origin)) {
|
|
298
|
+
const pmText = sharedProseMirrorToText(sharedProseMirror);
|
|
299
|
+
if (pmText !== null && normalize(pmText) === expectedText) {
|
|
300
|
+
parseFailed = false;
|
|
301
|
+
resolve();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const runFallback = () => {
|
|
306
|
+
resolve();
|
|
307
|
+
if (lastBridgedText !== expectedText) return;
|
|
308
|
+
const pmText = sharedProseMirrorToText(sharedProseMirror);
|
|
309
|
+
if (pmText === null || normalize(pmText) !== expectedText) {
|
|
310
|
+
const changedDuringSkip = xmlTextAtSkipStart !== null && pmText !== null && normalize(pmText) !== normalize(xmlTextAtSkipStart) || xmlTextAtSkipStart === null && pmText !== null || xmlTextAtSkipStart !== null && pmText === null;
|
|
311
|
+
if (changedDuringSkip) {
|
|
312
|
+
lastBridgedText = pmText !== null ? normalize(pmText) : lastBridgedText;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
lastBridgedText = null;
|
|
316
|
+
syncTextToProsemirror(ORIGIN_TEXT_TO_PM);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const timer = setTimeout(runFallback, 500);
|
|
320
|
+
sharedProseMirror.observeDeep(xmlCatchUpObserver);
|
|
321
|
+
pendingSkipCleanup = resolve;
|
|
264
322
|
return;
|
|
265
323
|
}
|
|
266
324
|
syncTextToProsemirror(ORIGIN_TEXT_TO_PM);
|
|
@@ -270,6 +328,12 @@ function createYjsBridge(config, options) {
|
|
|
270
328
|
return {
|
|
271
329
|
bootstrapResult,
|
|
272
330
|
syncToSharedText(doc2) {
|
|
331
|
+
if (skipPending) {
|
|
332
|
+
return { ok: false, reason: "skip-pending" };
|
|
333
|
+
}
|
|
334
|
+
if (parseFailed) {
|
|
335
|
+
return { ok: false, reason: "parse-failed" };
|
|
336
|
+
}
|
|
273
337
|
let text;
|
|
274
338
|
try {
|
|
275
339
|
text = serialize(doc2);
|
|
@@ -289,6 +353,7 @@ function createYjsBridge(config, options) {
|
|
|
289
353
|
},
|
|
290
354
|
dispose() {
|
|
291
355
|
sharedText.unobserve(textObserver);
|
|
356
|
+
if (pendingSkipCleanup) pendingSkipCleanup();
|
|
292
357
|
}
|
|
293
358
|
};
|
|
294
359
|
}
|
|
@@ -340,7 +405,6 @@ var wiredBridges = /* @__PURE__ */ new WeakMap();
|
|
|
340
405
|
var defaultOnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
341
406
|
function createBridgeSyncPlugin(bridge, options = {}) {
|
|
342
407
|
const warn = options.onWarning ?? defaultOnWarning;
|
|
343
|
-
let yjsBatchSeen = false;
|
|
344
408
|
let needsSync = false;
|
|
345
409
|
return new import_prosemirror_state.Plugin({
|
|
346
410
|
key: bridgeSyncPluginKey,
|
|
@@ -351,10 +415,6 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
351
415
|
apply(tr, _prev) {
|
|
352
416
|
if (!tr.docChanged) return { needsSync };
|
|
353
417
|
if (bridge.isYjsSyncChange(tr)) {
|
|
354
|
-
yjsBatchSeen = true;
|
|
355
|
-
return { needsSync };
|
|
356
|
-
}
|
|
357
|
-
if (yjsBatchSeen && tr.getMeta("appendedTransaction")) {
|
|
358
418
|
return { needsSync };
|
|
359
419
|
}
|
|
360
420
|
needsSync = true;
|
|
@@ -372,6 +432,14 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
372
432
|
if (needsSync) {
|
|
373
433
|
const result = bridge.syncToSharedText(view.state.doc);
|
|
374
434
|
if (!result.ok) {
|
|
435
|
+
if (result.reason === "skip-pending") {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (result.reason === "parse-failed") {
|
|
439
|
+
options.onSyncFailure?.(result, view);
|
|
440
|
+
warn({ code: "sync-failed", message: `bridge sync failed: ${result.reason}` });
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
375
443
|
if (result.reason !== "unchanged") {
|
|
376
444
|
options.onSyncFailure?.(result, view);
|
|
377
445
|
warn({ code: "sync-failed", message: `bridge sync failed: ${result.reason}` });
|
|
@@ -379,12 +447,15 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
379
447
|
}
|
|
380
448
|
}
|
|
381
449
|
needsSync = false;
|
|
382
|
-
yjsBatchSeen = false;
|
|
383
450
|
},
|
|
384
451
|
destroy() {
|
|
385
452
|
const remaining = (wiredBridges.get(bridge) ?? 1) - 1;
|
|
386
|
-
if (remaining <= 0)
|
|
387
|
-
|
|
453
|
+
if (remaining <= 0) {
|
|
454
|
+
wiredBridges.delete(bridge);
|
|
455
|
+
if (options.autoDispose) bridge.dispose();
|
|
456
|
+
} else {
|
|
457
|
+
wiredBridges.set(bridge, remaining);
|
|
458
|
+
}
|
|
388
459
|
}
|
|
389
460
|
};
|
|
390
461
|
}
|
|
@@ -430,6 +501,8 @@ function broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAncho
|
|
|
430
501
|
awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead });
|
|
431
502
|
}
|
|
432
503
|
var defaultOnWarning2 = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
504
|
+
var awarenessRefCounts = /* @__PURE__ */ new WeakMap();
|
|
505
|
+
var awarenessFieldNames = /* @__PURE__ */ new WeakMap();
|
|
433
506
|
function createCursorSyncPlugin(options) {
|
|
434
507
|
const { awareness, serialize, sharedText } = options;
|
|
435
508
|
const warn = options.onWarning ?? defaultOnWarning2;
|
|
@@ -479,53 +552,80 @@ function createCursorSyncPlugin(options) {
|
|
|
479
552
|
}
|
|
480
553
|
},
|
|
481
554
|
view(editorView) {
|
|
555
|
+
const viewCount = awarenessRefCounts.get(awareness) ?? 0;
|
|
556
|
+
awarenessRefCounts.set(awareness, viewCount + 1);
|
|
557
|
+
let fieldNames = awarenessFieldNames.get(awareness);
|
|
558
|
+
if (!fieldNames) {
|
|
559
|
+
fieldNames = /* @__PURE__ */ new Set();
|
|
560
|
+
awarenessFieldNames.set(awareness, fieldNames);
|
|
561
|
+
}
|
|
562
|
+
fieldNames.add(cursorFieldName);
|
|
563
|
+
if (sharedText) {
|
|
564
|
+
fieldNames.add(cmCursorFieldName);
|
|
565
|
+
}
|
|
482
566
|
let suppressCmReaction = false;
|
|
483
567
|
let lastCmAbsAnchor = -1;
|
|
484
568
|
let lastCmAbsHead = -1;
|
|
485
569
|
let cmCursorHandledByListener = false;
|
|
570
|
+
let inAwarenessHandler = false;
|
|
486
571
|
const handleAwarenessUpdate = ({ added, updated }) => {
|
|
572
|
+
if (inAwarenessHandler) return;
|
|
487
573
|
if (suppressCmReaction) return;
|
|
488
574
|
if (!sharedText?.doc) return;
|
|
489
575
|
const localId = awareness.clientID;
|
|
490
576
|
if (!updated.includes(localId) && !added.includes(localId)) return;
|
|
491
577
|
const localState = awareness.getLocalState();
|
|
492
578
|
if (!localState) return;
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
579
|
+
inAwarenessHandler = true;
|
|
580
|
+
try {
|
|
581
|
+
const cmCursor = localState[cmCursorFieldName];
|
|
582
|
+
if (!cmCursor?.anchor || !cmCursor?.head) {
|
|
583
|
+
if (lastCmAbsAnchor !== -1) {
|
|
584
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
585
|
+
lastCmAbsAnchor = -1;
|
|
586
|
+
lastCmAbsHead = -1;
|
|
587
|
+
}
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const absAnchor = (0, import_yjs.createAbsolutePositionFromRelativePosition)(cmCursor.anchor, sharedText.doc);
|
|
591
|
+
const absHead = (0, import_yjs.createAbsolutePositionFromRelativePosition)(cmCursor.head, sharedText.doc);
|
|
592
|
+
if (!absAnchor || !absHead) {
|
|
496
593
|
awareness.setLocalStateField(cursorFieldName, null);
|
|
497
594
|
lastCmAbsAnchor = -1;
|
|
498
595
|
lastCmAbsHead = -1;
|
|
596
|
+
return;
|
|
499
597
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
598
|
+
if (absAnchor.type !== sharedText || absHead.type !== sharedText) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return;
|
|
602
|
+
const map = getOrBuildMap(editorView.state.doc);
|
|
603
|
+
if (!map) {
|
|
604
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const pmAnchor = (0, import_core.reverseCursorMapLookup)(map, absAnchor.index);
|
|
608
|
+
const pmHead = (0, import_core.reverseCursorMapLookup)(map, absHead.index);
|
|
609
|
+
if (pmAnchor === null || pmHead === null) {
|
|
610
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
lastCmAbsAnchor = absAnchor.index;
|
|
614
|
+
lastCmAbsHead = absHead.index;
|
|
615
|
+
if (pendingCmCursor != null) {
|
|
616
|
+
cmCursorHandledByListener = true;
|
|
617
|
+
}
|
|
618
|
+
const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead);
|
|
619
|
+
if (!ok && !warnedSyncPluginMissing) {
|
|
620
|
+
warnedSyncPluginMissing = true;
|
|
621
|
+
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
622
|
+
}
|
|
623
|
+
} catch {
|
|
505
624
|
awareness.setLocalStateField(cursorFieldName, null);
|
|
506
625
|
lastCmAbsAnchor = -1;
|
|
507
626
|
lastCmAbsHead = -1;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return;
|
|
511
|
-
const map = getOrBuildMap(editorView.state.doc);
|
|
512
|
-
if (!map) {
|
|
513
|
-
awareness.setLocalStateField(cursorFieldName, null);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const pmAnchor = (0, import_core.reverseCursorMapLookup)(map, absAnchor.index);
|
|
517
|
-
const pmHead = (0, import_core.reverseCursorMapLookup)(map, absHead.index);
|
|
518
|
-
if (pmAnchor === null || pmHead === null) {
|
|
519
|
-
awareness.setLocalStateField(cursorFieldName, null);
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
lastCmAbsAnchor = absAnchor.index;
|
|
523
|
-
lastCmAbsHead = absHead.index;
|
|
524
|
-
cmCursorHandledByListener = true;
|
|
525
|
-
const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead);
|
|
526
|
-
if (!ok && !warnedSyncPluginMissing) {
|
|
527
|
-
warnedSyncPluginMissing = true;
|
|
528
|
-
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
627
|
+
} finally {
|
|
628
|
+
inAwarenessHandler = false;
|
|
529
629
|
}
|
|
530
630
|
};
|
|
531
631
|
if (sharedText) {
|
|
@@ -564,6 +664,9 @@ function createCursorSyncPlugin(options) {
|
|
|
564
664
|
}
|
|
565
665
|
} else {
|
|
566
666
|
awareness.setLocalStateField(cursorFieldName, null);
|
|
667
|
+
if (sharedText?.doc) {
|
|
668
|
+
awareness.setLocalStateField(cmCursorFieldName, null);
|
|
669
|
+
}
|
|
567
670
|
}
|
|
568
671
|
}
|
|
569
672
|
cmCursorHandledByListener = false;
|
|
@@ -598,9 +701,18 @@ function createCursorSyncPlugin(options) {
|
|
|
598
701
|
if (sharedText) {
|
|
599
702
|
awareness.off("update", handleAwarenessUpdate);
|
|
600
703
|
}
|
|
601
|
-
|
|
602
|
-
if (
|
|
603
|
-
|
|
704
|
+
const remaining = (awarenessRefCounts.get(awareness) ?? 1) - 1;
|
|
705
|
+
if (remaining <= 0) {
|
|
706
|
+
awarenessRefCounts.delete(awareness);
|
|
707
|
+
const fields = awarenessFieldNames.get(awareness);
|
|
708
|
+
if (fields) {
|
|
709
|
+
for (const field of fields) {
|
|
710
|
+
awareness.setLocalStateField(field, null);
|
|
711
|
+
}
|
|
712
|
+
awarenessFieldNames.delete(awareness);
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
awarenessRefCounts.set(awareness, remaining);
|
|
604
716
|
}
|
|
605
717
|
}
|
|
606
718
|
};
|
|
@@ -641,7 +753,10 @@ function createCollabPlugins(schema, options) {
|
|
|
641
753
|
(0, import_y_prosemirror3.yUndoPlugin)(options.yUndoPluginOpts)
|
|
642
754
|
];
|
|
643
755
|
if (options.bridge) {
|
|
644
|
-
plugins.push(createBridgeSyncPlugin(options.bridge, {
|
|
756
|
+
plugins.push(createBridgeSyncPlugin(options.bridge, {
|
|
757
|
+
onWarning: options.onWarning,
|
|
758
|
+
autoDispose: options.autoDisposeBridge
|
|
759
|
+
}));
|
|
645
760
|
}
|
|
646
761
|
if (enableCursorSync && options.serialize) {
|
|
647
762
|
plugins.push(
|