@pm-cm/yjs 0.0.2 → 0.0.4
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 +185 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +183 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/bridge.ts
|
|
2
2
|
import { prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, ySyncPluginKey } from "y-prosemirror";
|
|
3
|
-
import { XmlElement as YXmlElement, XmlText as YXmlText } from "yjs";
|
|
4
3
|
|
|
5
4
|
// src/types.ts
|
|
6
5
|
var ORIGIN_TEXT_TO_PM = "bridge:text-to-prosemirror";
|
|
@@ -58,120 +57,10 @@ function replaceSharedProseMirror(doc, fragment, text, origin, config) {
|
|
|
58
57
|
onError({ code: "parse-error", message: "failed to parse text into ProseMirror document", cause: error });
|
|
59
58
|
return { ok: false, reason: "parse-error" };
|
|
60
59
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (currentDoc.eq(nextDoc)) {
|
|
64
|
-
return { ok: false, reason: "unchanged" };
|
|
65
|
-
}
|
|
66
|
-
} catch {
|
|
67
|
-
}
|
|
68
|
-
if (!tryIncrementalFragmentUpdate(doc, fragment, nextDoc, origin)) {
|
|
69
|
-
doc.transact(() => {
|
|
70
|
-
prosemirrorToYXmlFragment(nextDoc, fragment);
|
|
71
|
-
}, origin);
|
|
72
|
-
}
|
|
73
|
-
return { ok: true };
|
|
74
|
-
}
|
|
75
|
-
function pmNodeToYXmlElement(node) {
|
|
76
|
-
const xmlEl = new YXmlElement(node.type.name);
|
|
77
|
-
for (const [key, value] of Object.entries(node.attrs)) {
|
|
78
|
-
if (value != null) xmlEl.setAttribute(key, value);
|
|
79
|
-
}
|
|
80
|
-
const children = [];
|
|
81
|
-
node.forEach((child) => {
|
|
82
|
-
if (child.isText && child.text) {
|
|
83
|
-
const xmlText = new YXmlText();
|
|
84
|
-
const attrs = {};
|
|
85
|
-
for (const mark of child.marks) {
|
|
86
|
-
attrs[mark.type.name] = mark.attrs && Object.keys(mark.attrs).length > 0 ? mark.attrs : true;
|
|
87
|
-
}
|
|
88
|
-
xmlText.insert(0, child.text, Object.keys(attrs).length > 0 ? attrs : void 0);
|
|
89
|
-
children.push(xmlText);
|
|
90
|
-
} else if (!child.isLeaf) {
|
|
91
|
-
children.push(pmNodeToYXmlElement(child));
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
if (children.length > 0) {
|
|
95
|
-
xmlEl.insert(0, children);
|
|
96
|
-
}
|
|
97
|
-
return xmlEl;
|
|
98
|
-
}
|
|
99
|
-
function getXmlElementText(xmlEl) {
|
|
100
|
-
let text = "";
|
|
101
|
-
for (let j = 0; j < xmlEl.length; j++) {
|
|
102
|
-
const child = xmlEl.get(j);
|
|
103
|
-
if (child instanceof YXmlText) text += child.toString();
|
|
104
|
-
}
|
|
105
|
-
return text;
|
|
106
|
-
}
|
|
107
|
-
function updateXmlElementTextContent(xmlEl, pmNode) {
|
|
108
|
-
if (xmlEl.length === 0 || !(xmlEl.get(0) instanceof YXmlText)) return;
|
|
109
|
-
const xmlText = xmlEl.get(0);
|
|
110
|
-
const oldText = xmlText.toString();
|
|
111
|
-
const newText = pmNode.textContent;
|
|
112
|
-
if (oldText === newText) return;
|
|
113
|
-
let start = 0;
|
|
114
|
-
const minLen = Math.min(oldText.length, newText.length);
|
|
115
|
-
while (start < minLen && oldText.charCodeAt(start) === newText.charCodeAt(start)) {
|
|
116
|
-
start++;
|
|
117
|
-
}
|
|
118
|
-
let oldEnd = oldText.length;
|
|
119
|
-
let newEnd = newText.length;
|
|
120
|
-
while (oldEnd > start && newEnd > start && oldText.charCodeAt(oldEnd - 1) === newText.charCodeAt(newEnd - 1)) {
|
|
121
|
-
oldEnd--;
|
|
122
|
-
newEnd--;
|
|
123
|
-
}
|
|
124
|
-
if (oldEnd > start) xmlText.delete(start, oldEnd - start);
|
|
125
|
-
if (newEnd > start) xmlText.insert(start, newText.slice(start, newEnd));
|
|
126
|
-
}
|
|
127
|
-
function tryIncrementalFragmentUpdate(ydoc, fragment, nextDoc, origin) {
|
|
128
|
-
const oldCount = fragment.length;
|
|
129
|
-
const newCount = nextDoc.childCount;
|
|
130
|
-
for (let i = 0; i < oldCount; i++) {
|
|
131
|
-
if (!(fragment.get(i) instanceof YXmlElement)) return false;
|
|
132
|
-
}
|
|
133
|
-
let prefix = 0;
|
|
134
|
-
while (prefix < Math.min(oldCount, newCount)) {
|
|
135
|
-
const oldChild = fragment.get(prefix);
|
|
136
|
-
const newChild = nextDoc.child(prefix);
|
|
137
|
-
if (oldChild.nodeName !== newChild.type.name) break;
|
|
138
|
-
if (getXmlElementText(oldChild) !== newChild.textContent) break;
|
|
139
|
-
prefix++;
|
|
140
|
-
}
|
|
141
|
-
let suffix = 0;
|
|
142
|
-
while (suffix < Math.min(oldCount - prefix, newCount - prefix)) {
|
|
143
|
-
const oi = oldCount - 1 - suffix;
|
|
144
|
-
const ni = newCount - 1 - suffix;
|
|
145
|
-
const oldChild = fragment.get(oi);
|
|
146
|
-
const newChild = nextDoc.child(ni);
|
|
147
|
-
if (oldChild.nodeName !== newChild.type.name) break;
|
|
148
|
-
if (getXmlElementText(oldChild) !== newChild.textContent) break;
|
|
149
|
-
suffix++;
|
|
150
|
-
}
|
|
151
|
-
const oldMiddleLen = oldCount - prefix - suffix;
|
|
152
|
-
const newMiddleLen = newCount - prefix - suffix;
|
|
153
|
-
if (oldMiddleLen === 0 && newMiddleLen === 0) return true;
|
|
154
|
-
ydoc.transact(() => {
|
|
155
|
-
const updateCount = Math.min(oldMiddleLen, newMiddleLen);
|
|
156
|
-
for (let i = 0; i < updateCount; i++) {
|
|
157
|
-
const idx = prefix + i;
|
|
158
|
-
const xmlEl = fragment.get(idx);
|
|
159
|
-
const pmNode = nextDoc.child(idx);
|
|
160
|
-
if (xmlEl.nodeName === pmNode.type.name) {
|
|
161
|
-
updateXmlElementTextContent(xmlEl, pmNode);
|
|
162
|
-
} else {
|
|
163
|
-
fragment.delete(idx, 1);
|
|
164
|
-
fragment.insert(idx, [pmNodeToYXmlElement(pmNode)]);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (oldMiddleLen > updateCount) {
|
|
168
|
-
fragment.delete(prefix + updateCount, oldMiddleLen - updateCount);
|
|
169
|
-
}
|
|
170
|
-
for (let i = updateCount; i < newMiddleLen; i++) {
|
|
171
|
-
fragment.insert(prefix + i, [pmNodeToYXmlElement(nextDoc.child(prefix + i))]);
|
|
172
|
-
}
|
|
60
|
+
doc.transact(() => {
|
|
61
|
+
prosemirrorToYXmlFragment(nextDoc, fragment);
|
|
173
62
|
}, origin);
|
|
174
|
-
return true;
|
|
63
|
+
return { ok: true };
|
|
175
64
|
}
|
|
176
65
|
function createYjsBridge(config, options) {
|
|
177
66
|
const {
|
|
@@ -208,10 +97,10 @@ function createYjsBridge(config, options) {
|
|
|
208
97
|
normalize,
|
|
209
98
|
onError
|
|
210
99
|
});
|
|
211
|
-
if (result.ok
|
|
100
|
+
if (result.ok) {
|
|
212
101
|
lastBridgedText = text;
|
|
213
102
|
}
|
|
214
|
-
return result.ok
|
|
103
|
+
return result.ok;
|
|
215
104
|
};
|
|
216
105
|
const sharedProseMirrorToText = (fragment) => {
|
|
217
106
|
try {
|
|
@@ -239,7 +128,13 @@ function createYjsBridge(config, options) {
|
|
|
239
128
|
return { source: "initial", parseError: true };
|
|
240
129
|
}
|
|
241
130
|
const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema);
|
|
242
|
-
|
|
131
|
+
let canonicalText;
|
|
132
|
+
try {
|
|
133
|
+
canonicalText = serialize(pmDoc);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
onError({ code: "serialize-error", message: "failed to serialize ProseMirror document during bootstrap", cause: error });
|
|
136
|
+
return { source: "initial", parseError: true };
|
|
137
|
+
}
|
|
243
138
|
replaceSharedText(sharedText, canonicalText, ORIGIN_INIT, normalize);
|
|
244
139
|
lastBridgedText = normalize(canonicalText);
|
|
245
140
|
return { source: "initial" };
|
|
@@ -301,7 +196,13 @@ function createYjsBridge(config, options) {
|
|
|
301
196
|
return {
|
|
302
197
|
bootstrapResult,
|
|
303
198
|
syncToSharedText(doc2) {
|
|
304
|
-
|
|
199
|
+
let text;
|
|
200
|
+
try {
|
|
201
|
+
text = serialize(doc2);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
onError({ code: "serialize-error", message: "failed to serialize ProseMirror document", cause: error });
|
|
204
|
+
return { ok: false, reason: "serialize-error" };
|
|
205
|
+
}
|
|
305
206
|
const result = replaceSharedText(sharedText, text, ORIGIN_PM_TO_TEXT, normalize);
|
|
306
207
|
if (result.ok || result.reason === "unchanged") {
|
|
307
208
|
lastBridgedText = normalize(text);
|
|
@@ -339,10 +240,12 @@ function createAwarenessProxy(awareness, cursorField = "pmCursor") {
|
|
|
339
240
|
const states = target.getStates();
|
|
340
241
|
const remapped = /* @__PURE__ */ new Map();
|
|
341
242
|
states.forEach((state, clientId) => {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
cursor:
|
|
345
|
-
}
|
|
243
|
+
const s = state;
|
|
244
|
+
if (cursorField in s) {
|
|
245
|
+
remapped.set(clientId, { ...s, cursor: s[cursorField] ?? null });
|
|
246
|
+
} else {
|
|
247
|
+
remapped.set(clientId, s);
|
|
248
|
+
}
|
|
346
249
|
});
|
|
347
250
|
return remapped;
|
|
348
251
|
};
|
|
@@ -359,14 +262,12 @@ import { initProseMirrorDoc, yCursorPlugin, ySyncPlugin, yUndoPlugin } from "y-p
|
|
|
359
262
|
// src/bridge-sync-plugin.ts
|
|
360
263
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
361
264
|
var bridgeSyncPluginKey = new PluginKey("pm-cm-bridge-sync");
|
|
362
|
-
var wiredBridges = /* @__PURE__ */ new
|
|
265
|
+
var wiredBridges = /* @__PURE__ */ new WeakMap();
|
|
363
266
|
var defaultOnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
364
267
|
function createBridgeSyncPlugin(bridge, options = {}) {
|
|
365
268
|
const warn = options.onWarning ?? defaultOnWarning;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
wiredBridges.add(bridge);
|
|
269
|
+
let yjsBatchSeen = false;
|
|
270
|
+
let needsSync = false;
|
|
370
271
|
return new Plugin({
|
|
371
272
|
key: bridgeSyncPluginKey,
|
|
372
273
|
state: {
|
|
@@ -374,27 +275,44 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
374
275
|
return { needsSync: false };
|
|
375
276
|
},
|
|
376
277
|
apply(tr, _prev) {
|
|
377
|
-
if (!tr.docChanged) return { needsSync
|
|
378
|
-
if (bridge.isYjsSyncChange(tr))
|
|
278
|
+
if (!tr.docChanged) return { needsSync };
|
|
279
|
+
if (bridge.isYjsSyncChange(tr)) {
|
|
280
|
+
yjsBatchSeen = true;
|
|
281
|
+
needsSync = false;
|
|
282
|
+
return { needsSync: false };
|
|
283
|
+
}
|
|
284
|
+
if (yjsBatchSeen) {
|
|
285
|
+
needsSync = false;
|
|
286
|
+
return { needsSync: false };
|
|
287
|
+
}
|
|
288
|
+
needsSync = true;
|
|
379
289
|
return { needsSync: true };
|
|
380
290
|
}
|
|
381
291
|
},
|
|
382
292
|
view() {
|
|
293
|
+
const count = wiredBridges.get(bridge) ?? 0;
|
|
294
|
+
if (count > 0) {
|
|
295
|
+
warn({ code: "bridge-already-wired", message: "this bridge is already wired to another plugin instance" });
|
|
296
|
+
}
|
|
297
|
+
wiredBridges.set(bridge, count + 1);
|
|
383
298
|
return {
|
|
384
299
|
update(view) {
|
|
385
|
-
|
|
386
|
-
if (state?.needsSync) {
|
|
300
|
+
if (needsSync) {
|
|
387
301
|
const result = bridge.syncToSharedText(view.state.doc);
|
|
388
302
|
if (!result.ok) {
|
|
389
|
-
if (result.reason
|
|
303
|
+
if (result.reason !== "unchanged") {
|
|
390
304
|
options.onSyncFailure?.(result, view);
|
|
391
305
|
warn({ code: "sync-failed", message: `bridge sync failed: ${result.reason}` });
|
|
392
306
|
}
|
|
393
307
|
}
|
|
394
308
|
}
|
|
309
|
+
needsSync = false;
|
|
310
|
+
yjsBatchSeen = false;
|
|
395
311
|
},
|
|
396
312
|
destroy() {
|
|
397
|
-
wiredBridges.
|
|
313
|
+
const remaining = (wiredBridges.get(bridge) ?? 1) - 1;
|
|
314
|
+
if (remaining <= 0) wiredBridges.delete(bridge);
|
|
315
|
+
else wiredBridges.set(bridge, remaining);
|
|
398
316
|
}
|
|
399
317
|
};
|
|
400
318
|
}
|
|
@@ -404,7 +322,7 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
404
322
|
// src/cursor-sync-plugin.ts
|
|
405
323
|
import { Plugin as Plugin2, PluginKey as PluginKey2 } from "prosemirror-state";
|
|
406
324
|
import { absolutePositionToRelativePosition, ySyncPluginKey as ySyncPluginKey2 } from "y-prosemirror";
|
|
407
|
-
import { createRelativePositionFromTypeIndex } from "yjs";
|
|
325
|
+
import { createRelativePositionFromTypeIndex, createAbsolutePositionFromRelativePosition } from "yjs";
|
|
408
326
|
import { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from "@pm-cm/core";
|
|
409
327
|
var cursorSyncPluginKey = new PluginKey2("pm-cm-cursor-sync");
|
|
410
328
|
function getYSyncState(view) {
|
|
@@ -445,12 +363,23 @@ function createCursorSyncPlugin(options) {
|
|
|
445
363
|
const warn = options.onWarning ?? defaultOnWarning2;
|
|
446
364
|
const cursorFieldName = options.cursorFieldName ?? "pmCursor";
|
|
447
365
|
const cmCursorFieldName = options.cmCursorFieldName ?? "cursor";
|
|
366
|
+
if (sharedText && cursorFieldName === cmCursorFieldName) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`createCursorSyncPlugin: cursorFieldName and cmCursorFieldName must differ when sharedText is provided (both are "${cursorFieldName}")`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
448
371
|
let warnedSyncPluginMissing = false;
|
|
372
|
+
let pendingCmCursor = null;
|
|
449
373
|
let cachedMap = null;
|
|
450
374
|
let cachedMapDoc = null;
|
|
451
375
|
function getOrBuildMap(doc) {
|
|
452
376
|
if (cachedMapDoc !== doc || !cachedMap) {
|
|
453
|
-
|
|
377
|
+
try {
|
|
378
|
+
cachedMap = buildCursorMap(doc, serialize);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
warn({ code: "cursor-map-error", message: "failed to build cursor map \u2014 cursor sync skipped" });
|
|
381
|
+
cachedMap = null;
|
|
382
|
+
}
|
|
454
383
|
cachedMapDoc = doc;
|
|
455
384
|
}
|
|
456
385
|
return cachedMap;
|
|
@@ -464,62 +393,141 @@ function createCursorSyncPlugin(options) {
|
|
|
464
393
|
apply(tr, prev, _oldState, newState) {
|
|
465
394
|
const cmMeta = tr.getMeta(cursorSyncPluginKey);
|
|
466
395
|
if (cmMeta) {
|
|
467
|
-
|
|
396
|
+
pendingCmCursor = cmMeta;
|
|
468
397
|
}
|
|
469
398
|
let mappedTextOffset = prev.mappedTextOffset;
|
|
470
399
|
if (tr.selectionSet || tr.docChanged) {
|
|
471
400
|
const map = getOrBuildMap(newState.doc);
|
|
472
|
-
mappedTextOffset = cursorMapLookup(map, newState.selection.anchor);
|
|
401
|
+
mappedTextOffset = map ? cursorMapLookup(map, newState.selection.anchor) : null;
|
|
473
402
|
}
|
|
474
403
|
return {
|
|
475
|
-
pendingCm:
|
|
404
|
+
pendingCm: pendingCmCursor,
|
|
476
405
|
mappedTextOffset
|
|
477
406
|
};
|
|
478
407
|
}
|
|
479
408
|
},
|
|
480
|
-
view() {
|
|
409
|
+
view(editorView) {
|
|
410
|
+
let suppressCmReaction = false;
|
|
411
|
+
let lastCmAbsAnchor = -1;
|
|
412
|
+
let lastCmAbsHead = -1;
|
|
413
|
+
let cmCursorHandledByListener = false;
|
|
414
|
+
const handleAwarenessUpdate = ({ added, updated }) => {
|
|
415
|
+
if (suppressCmReaction) return;
|
|
416
|
+
if (!sharedText?.doc) return;
|
|
417
|
+
const localId = awareness.clientID;
|
|
418
|
+
if (!updated.includes(localId) && !added.includes(localId)) return;
|
|
419
|
+
const localState = awareness.getLocalState();
|
|
420
|
+
if (!localState) return;
|
|
421
|
+
const cmCursor = localState[cmCursorFieldName];
|
|
422
|
+
if (!cmCursor?.anchor || !cmCursor?.head) {
|
|
423
|
+
if (lastCmAbsAnchor !== -1) {
|
|
424
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
425
|
+
lastCmAbsAnchor = -1;
|
|
426
|
+
lastCmAbsHead = -1;
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const absAnchor = createAbsolutePositionFromRelativePosition(cmCursor.anchor, sharedText.doc);
|
|
431
|
+
const absHead = createAbsolutePositionFromRelativePosition(cmCursor.head, sharedText.doc);
|
|
432
|
+
if (!absAnchor || !absHead) {
|
|
433
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
434
|
+
lastCmAbsAnchor = -1;
|
|
435
|
+
lastCmAbsHead = -1;
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return;
|
|
439
|
+
const map = getOrBuildMap(editorView.state.doc);
|
|
440
|
+
if (!map) {
|
|
441
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const pmAnchor = reverseCursorMapLookup(map, absAnchor.index);
|
|
445
|
+
const pmHead = reverseCursorMapLookup(map, absHead.index);
|
|
446
|
+
if (pmAnchor === null || pmHead === null) {
|
|
447
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
lastCmAbsAnchor = absAnchor.index;
|
|
451
|
+
lastCmAbsHead = absHead.index;
|
|
452
|
+
cmCursorHandledByListener = true;
|
|
453
|
+
const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead);
|
|
454
|
+
if (!ok && !warnedSyncPluginMissing) {
|
|
455
|
+
warnedSyncPluginMissing = true;
|
|
456
|
+
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
if (sharedText) {
|
|
460
|
+
awareness.on("update", handleAwarenessUpdate);
|
|
461
|
+
}
|
|
481
462
|
return {
|
|
482
463
|
update(view, prevState) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const pmHead = reverseCursorMapLookup(map, pluginState.pendingCm.head);
|
|
489
|
-
if (pmAnchor !== null && pmHead !== null) {
|
|
490
|
-
const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead);
|
|
491
|
-
if (!ok && !warnedSyncPluginMissing) {
|
|
492
|
-
warnedSyncPluginMissing = true;
|
|
493
|
-
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
494
|
-
}
|
|
464
|
+
if (view.state.doc !== prevState.doc) {
|
|
465
|
+
lastCmAbsAnchor = -1;
|
|
466
|
+
lastCmAbsHead = -1;
|
|
467
|
+
if (sharedText?.doc && !suppressCmReaction) {
|
|
468
|
+
handleAwarenessUpdate({ added: [awareness.clientID], updated: [], removed: [] });
|
|
495
469
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
);
|
|
470
|
+
}
|
|
471
|
+
if (pendingCmCursor != null) {
|
|
472
|
+
const cursor = pendingCmCursor;
|
|
473
|
+
pendingCmCursor = null;
|
|
474
|
+
if (!cmCursorHandledByListener) {
|
|
475
|
+
const map = getOrBuildMap(view.state.doc);
|
|
476
|
+
const pmAnchor = map ? reverseCursorMapLookup(map, cursor.anchor) : null;
|
|
477
|
+
const pmHead = map ? reverseCursorMapLookup(map, cursor.head) : null;
|
|
478
|
+
if (pmAnchor !== null && pmHead !== null) {
|
|
479
|
+
const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead);
|
|
480
|
+
if (!ok && !warnedSyncPluginMissing) {
|
|
481
|
+
warnedSyncPluginMissing = true;
|
|
482
|
+
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
483
|
+
}
|
|
484
|
+
if (sharedText?.doc) {
|
|
485
|
+
broadcastTextCursor(
|
|
486
|
+
awareness,
|
|
487
|
+
cmCursorFieldName,
|
|
488
|
+
sharedText,
|
|
489
|
+
cursor.anchor,
|
|
490
|
+
cursor.head
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
495
|
+
}
|
|
504
496
|
}
|
|
497
|
+
cmCursorHandledByListener = false;
|
|
505
498
|
return;
|
|
506
499
|
}
|
|
507
500
|
if (view.hasFocus() && (view.state.selection !== prevState.selection || view.state.doc !== prevState.doc)) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
501
|
+
suppressCmReaction = true;
|
|
502
|
+
try {
|
|
503
|
+
const { anchor, head } = view.state.selection;
|
|
504
|
+
const ok = broadcastPmCursor(awareness, cursorFieldName, view, anchor, head);
|
|
505
|
+
if (!ok && !warnedSyncPluginMissing) {
|
|
506
|
+
warnedSyncPluginMissing = true;
|
|
507
|
+
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
508
|
+
}
|
|
509
|
+
if (sharedText?.doc) {
|
|
510
|
+
const map = getOrBuildMap(view.state.doc);
|
|
511
|
+
const textAnchor = map ? cursorMapLookup(map, anchor) : null;
|
|
512
|
+
const textHead = map ? cursorMapLookup(map, head) : null;
|
|
513
|
+
if (textAnchor !== null && textHead !== null) {
|
|
514
|
+
broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead);
|
|
515
|
+
} else {
|
|
516
|
+
awareness.setLocalStateField(cmCursorFieldName, null);
|
|
517
|
+
}
|
|
520
518
|
}
|
|
519
|
+
} finally {
|
|
520
|
+
suppressCmReaction = false;
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
|
+
cmCursorHandledByListener = false;
|
|
524
|
+
},
|
|
525
|
+
destroy() {
|
|
526
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
527
|
+
if (sharedText) {
|
|
528
|
+
awareness.setLocalStateField(cmCursorFieldName, null);
|
|
529
|
+
awareness.off("update", handleAwarenessUpdate);
|
|
530
|
+
}
|
|
523
531
|
}
|
|
524
532
|
};
|
|
525
533
|
}
|
|
@@ -530,7 +538,10 @@ function syncCmCursor(view, anchor, head, onWarning) {
|
|
|
530
538
|
(onWarning ?? defaultOnWarning2)({ code: "cursor-sync-not-installed", message: "cursor sync plugin is not installed on this EditorView" });
|
|
531
539
|
return;
|
|
532
540
|
}
|
|
533
|
-
const sanitize = (v) =>
|
|
541
|
+
const sanitize = (v) => {
|
|
542
|
+
const n = Math.floor(v);
|
|
543
|
+
return Number.isFinite(n) ? Math.max(0, n) : 0;
|
|
544
|
+
};
|
|
534
545
|
view.dispatch(
|
|
535
546
|
view.state.tr.setMeta(cursorSyncPluginKey, {
|
|
536
547
|
anchor: sanitize(anchor),
|