@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.cjs
CHANGED
|
@@ -43,7 +43,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
43
43
|
|
|
44
44
|
// src/bridge.ts
|
|
45
45
|
var import_y_prosemirror = require("y-prosemirror");
|
|
46
|
-
var import_yjs = require("yjs");
|
|
47
46
|
|
|
48
47
|
// src/types.ts
|
|
49
48
|
var ORIGIN_TEXT_TO_PM = "bridge:text-to-prosemirror";
|
|
@@ -101,120 +100,10 @@ function replaceSharedProseMirror(doc, fragment, text, origin, config) {
|
|
|
101
100
|
onError({ code: "parse-error", message: "failed to parse text into ProseMirror document", cause: error });
|
|
102
101
|
return { ok: false, reason: "parse-error" };
|
|
103
102
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (currentDoc.eq(nextDoc)) {
|
|
107
|
-
return { ok: false, reason: "unchanged" };
|
|
108
|
-
}
|
|
109
|
-
} catch {
|
|
110
|
-
}
|
|
111
|
-
if (!tryIncrementalFragmentUpdate(doc, fragment, nextDoc, origin)) {
|
|
112
|
-
doc.transact(() => {
|
|
113
|
-
(0, import_y_prosemirror.prosemirrorToYXmlFragment)(nextDoc, fragment);
|
|
114
|
-
}, origin);
|
|
115
|
-
}
|
|
116
|
-
return { ok: true };
|
|
117
|
-
}
|
|
118
|
-
function pmNodeToYXmlElement(node) {
|
|
119
|
-
const xmlEl = new import_yjs.XmlElement(node.type.name);
|
|
120
|
-
for (const [key, value] of Object.entries(node.attrs)) {
|
|
121
|
-
if (value != null) xmlEl.setAttribute(key, value);
|
|
122
|
-
}
|
|
123
|
-
const children = [];
|
|
124
|
-
node.forEach((child) => {
|
|
125
|
-
if (child.isText && child.text) {
|
|
126
|
-
const xmlText = new import_yjs.XmlText();
|
|
127
|
-
const attrs = {};
|
|
128
|
-
for (const mark of child.marks) {
|
|
129
|
-
attrs[mark.type.name] = mark.attrs && Object.keys(mark.attrs).length > 0 ? mark.attrs : true;
|
|
130
|
-
}
|
|
131
|
-
xmlText.insert(0, child.text, Object.keys(attrs).length > 0 ? attrs : void 0);
|
|
132
|
-
children.push(xmlText);
|
|
133
|
-
} else if (!child.isLeaf) {
|
|
134
|
-
children.push(pmNodeToYXmlElement(child));
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
if (children.length > 0) {
|
|
138
|
-
xmlEl.insert(0, children);
|
|
139
|
-
}
|
|
140
|
-
return xmlEl;
|
|
141
|
-
}
|
|
142
|
-
function getXmlElementText(xmlEl) {
|
|
143
|
-
let text = "";
|
|
144
|
-
for (let j = 0; j < xmlEl.length; j++) {
|
|
145
|
-
const child = xmlEl.get(j);
|
|
146
|
-
if (child instanceof import_yjs.XmlText) text += child.toString();
|
|
147
|
-
}
|
|
148
|
-
return text;
|
|
149
|
-
}
|
|
150
|
-
function updateXmlElementTextContent(xmlEl, pmNode) {
|
|
151
|
-
if (xmlEl.length === 0 || !(xmlEl.get(0) instanceof import_yjs.XmlText)) return;
|
|
152
|
-
const xmlText = xmlEl.get(0);
|
|
153
|
-
const oldText = xmlText.toString();
|
|
154
|
-
const newText = pmNode.textContent;
|
|
155
|
-
if (oldText === newText) return;
|
|
156
|
-
let start = 0;
|
|
157
|
-
const minLen = Math.min(oldText.length, newText.length);
|
|
158
|
-
while (start < minLen && oldText.charCodeAt(start) === newText.charCodeAt(start)) {
|
|
159
|
-
start++;
|
|
160
|
-
}
|
|
161
|
-
let oldEnd = oldText.length;
|
|
162
|
-
let newEnd = newText.length;
|
|
163
|
-
while (oldEnd > start && newEnd > start && oldText.charCodeAt(oldEnd - 1) === newText.charCodeAt(newEnd - 1)) {
|
|
164
|
-
oldEnd--;
|
|
165
|
-
newEnd--;
|
|
166
|
-
}
|
|
167
|
-
if (oldEnd > start) xmlText.delete(start, oldEnd - start);
|
|
168
|
-
if (newEnd > start) xmlText.insert(start, newText.slice(start, newEnd));
|
|
169
|
-
}
|
|
170
|
-
function tryIncrementalFragmentUpdate(ydoc, fragment, nextDoc, origin) {
|
|
171
|
-
const oldCount = fragment.length;
|
|
172
|
-
const newCount = nextDoc.childCount;
|
|
173
|
-
for (let i = 0; i < oldCount; i++) {
|
|
174
|
-
if (!(fragment.get(i) instanceof import_yjs.XmlElement)) return false;
|
|
175
|
-
}
|
|
176
|
-
let prefix = 0;
|
|
177
|
-
while (prefix < Math.min(oldCount, newCount)) {
|
|
178
|
-
const oldChild = fragment.get(prefix);
|
|
179
|
-
const newChild = nextDoc.child(prefix);
|
|
180
|
-
if (oldChild.nodeName !== newChild.type.name) break;
|
|
181
|
-
if (getXmlElementText(oldChild) !== newChild.textContent) break;
|
|
182
|
-
prefix++;
|
|
183
|
-
}
|
|
184
|
-
let suffix = 0;
|
|
185
|
-
while (suffix < Math.min(oldCount - prefix, newCount - prefix)) {
|
|
186
|
-
const oi = oldCount - 1 - suffix;
|
|
187
|
-
const ni = newCount - 1 - suffix;
|
|
188
|
-
const oldChild = fragment.get(oi);
|
|
189
|
-
const newChild = nextDoc.child(ni);
|
|
190
|
-
if (oldChild.nodeName !== newChild.type.name) break;
|
|
191
|
-
if (getXmlElementText(oldChild) !== newChild.textContent) break;
|
|
192
|
-
suffix++;
|
|
193
|
-
}
|
|
194
|
-
const oldMiddleLen = oldCount - prefix - suffix;
|
|
195
|
-
const newMiddleLen = newCount - prefix - suffix;
|
|
196
|
-
if (oldMiddleLen === 0 && newMiddleLen === 0) return true;
|
|
197
|
-
ydoc.transact(() => {
|
|
198
|
-
const updateCount = Math.min(oldMiddleLen, newMiddleLen);
|
|
199
|
-
for (let i = 0; i < updateCount; i++) {
|
|
200
|
-
const idx = prefix + i;
|
|
201
|
-
const xmlEl = fragment.get(idx);
|
|
202
|
-
const pmNode = nextDoc.child(idx);
|
|
203
|
-
if (xmlEl.nodeName === pmNode.type.name) {
|
|
204
|
-
updateXmlElementTextContent(xmlEl, pmNode);
|
|
205
|
-
} else {
|
|
206
|
-
fragment.delete(idx, 1);
|
|
207
|
-
fragment.insert(idx, [pmNodeToYXmlElement(pmNode)]);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (oldMiddleLen > updateCount) {
|
|
211
|
-
fragment.delete(prefix + updateCount, oldMiddleLen - updateCount);
|
|
212
|
-
}
|
|
213
|
-
for (let i = updateCount; i < newMiddleLen; i++) {
|
|
214
|
-
fragment.insert(prefix + i, [pmNodeToYXmlElement(nextDoc.child(prefix + i))]);
|
|
215
|
-
}
|
|
103
|
+
doc.transact(() => {
|
|
104
|
+
(0, import_y_prosemirror.prosemirrorToYXmlFragment)(nextDoc, fragment);
|
|
216
105
|
}, origin);
|
|
217
|
-
return true;
|
|
106
|
+
return { ok: true };
|
|
218
107
|
}
|
|
219
108
|
function createYjsBridge(config, options) {
|
|
220
109
|
const {
|
|
@@ -251,10 +140,10 @@ function createYjsBridge(config, options) {
|
|
|
251
140
|
normalize,
|
|
252
141
|
onError
|
|
253
142
|
});
|
|
254
|
-
if (result.ok
|
|
143
|
+
if (result.ok) {
|
|
255
144
|
lastBridgedText = text;
|
|
256
145
|
}
|
|
257
|
-
return result.ok
|
|
146
|
+
return result.ok;
|
|
258
147
|
};
|
|
259
148
|
const sharedProseMirrorToText = (fragment) => {
|
|
260
149
|
try {
|
|
@@ -282,7 +171,13 @@ function createYjsBridge(config, options) {
|
|
|
282
171
|
return { source: "initial", parseError: true };
|
|
283
172
|
}
|
|
284
173
|
const pmDoc = (0, import_y_prosemirror.yXmlFragmentToProseMirrorRootNode)(sharedProseMirror, schema);
|
|
285
|
-
|
|
174
|
+
let canonicalText;
|
|
175
|
+
try {
|
|
176
|
+
canonicalText = serialize(pmDoc);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
onError({ code: "serialize-error", message: "failed to serialize ProseMirror document during bootstrap", cause: error });
|
|
179
|
+
return { source: "initial", parseError: true };
|
|
180
|
+
}
|
|
286
181
|
replaceSharedText(sharedText, canonicalText, ORIGIN_INIT, normalize);
|
|
287
182
|
lastBridgedText = normalize(canonicalText);
|
|
288
183
|
return { source: "initial" };
|
|
@@ -344,7 +239,13 @@ function createYjsBridge(config, options) {
|
|
|
344
239
|
return {
|
|
345
240
|
bootstrapResult,
|
|
346
241
|
syncToSharedText(doc2) {
|
|
347
|
-
|
|
242
|
+
let text;
|
|
243
|
+
try {
|
|
244
|
+
text = serialize(doc2);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
onError({ code: "serialize-error", message: "failed to serialize ProseMirror document", cause: error });
|
|
247
|
+
return { ok: false, reason: "serialize-error" };
|
|
248
|
+
}
|
|
348
249
|
const result = replaceSharedText(sharedText, text, ORIGIN_PM_TO_TEXT, normalize);
|
|
349
250
|
if (result.ok || result.reason === "unchanged") {
|
|
350
251
|
lastBridgedText = normalize(text);
|
|
@@ -382,10 +283,12 @@ function createAwarenessProxy(awareness, cursorField = "pmCursor") {
|
|
|
382
283
|
const states = target.getStates();
|
|
383
284
|
const remapped = /* @__PURE__ */ new Map();
|
|
384
285
|
states.forEach((state, clientId) => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
cursor:
|
|
388
|
-
}
|
|
286
|
+
const s = state;
|
|
287
|
+
if (cursorField in s) {
|
|
288
|
+
remapped.set(clientId, { ...s, cursor: s[cursorField] ?? null });
|
|
289
|
+
} else {
|
|
290
|
+
remapped.set(clientId, s);
|
|
291
|
+
}
|
|
389
292
|
});
|
|
390
293
|
return remapped;
|
|
391
294
|
};
|
|
@@ -402,14 +305,12 @@ var import_y_prosemirror3 = require("y-prosemirror");
|
|
|
402
305
|
// src/bridge-sync-plugin.ts
|
|
403
306
|
var import_prosemirror_state = require("prosemirror-state");
|
|
404
307
|
var bridgeSyncPluginKey = new import_prosemirror_state.PluginKey("pm-cm-bridge-sync");
|
|
405
|
-
var wiredBridges = /* @__PURE__ */ new
|
|
308
|
+
var wiredBridges = /* @__PURE__ */ new WeakMap();
|
|
406
309
|
var defaultOnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
407
310
|
function createBridgeSyncPlugin(bridge, options = {}) {
|
|
408
311
|
const warn = options.onWarning ?? defaultOnWarning;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
wiredBridges.add(bridge);
|
|
312
|
+
let yjsBatchSeen = false;
|
|
313
|
+
let needsSync = false;
|
|
413
314
|
return new import_prosemirror_state.Plugin({
|
|
414
315
|
key: bridgeSyncPluginKey,
|
|
415
316
|
state: {
|
|
@@ -417,27 +318,44 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
417
318
|
return { needsSync: false };
|
|
418
319
|
},
|
|
419
320
|
apply(tr, _prev) {
|
|
420
|
-
if (!tr.docChanged) return { needsSync
|
|
421
|
-
if (bridge.isYjsSyncChange(tr))
|
|
321
|
+
if (!tr.docChanged) return { needsSync };
|
|
322
|
+
if (bridge.isYjsSyncChange(tr)) {
|
|
323
|
+
yjsBatchSeen = true;
|
|
324
|
+
needsSync = false;
|
|
325
|
+
return { needsSync: false };
|
|
326
|
+
}
|
|
327
|
+
if (yjsBatchSeen) {
|
|
328
|
+
needsSync = false;
|
|
329
|
+
return { needsSync: false };
|
|
330
|
+
}
|
|
331
|
+
needsSync = true;
|
|
422
332
|
return { needsSync: true };
|
|
423
333
|
}
|
|
424
334
|
},
|
|
425
335
|
view() {
|
|
336
|
+
const count = wiredBridges.get(bridge) ?? 0;
|
|
337
|
+
if (count > 0) {
|
|
338
|
+
warn({ code: "bridge-already-wired", message: "this bridge is already wired to another plugin instance" });
|
|
339
|
+
}
|
|
340
|
+
wiredBridges.set(bridge, count + 1);
|
|
426
341
|
return {
|
|
427
342
|
update(view) {
|
|
428
|
-
|
|
429
|
-
if (state?.needsSync) {
|
|
343
|
+
if (needsSync) {
|
|
430
344
|
const result = bridge.syncToSharedText(view.state.doc);
|
|
431
345
|
if (!result.ok) {
|
|
432
|
-
if (result.reason
|
|
346
|
+
if (result.reason !== "unchanged") {
|
|
433
347
|
options.onSyncFailure?.(result, view);
|
|
434
348
|
warn({ code: "sync-failed", message: `bridge sync failed: ${result.reason}` });
|
|
435
349
|
}
|
|
436
350
|
}
|
|
437
351
|
}
|
|
352
|
+
needsSync = false;
|
|
353
|
+
yjsBatchSeen = false;
|
|
438
354
|
},
|
|
439
355
|
destroy() {
|
|
440
|
-
wiredBridges.
|
|
356
|
+
const remaining = (wiredBridges.get(bridge) ?? 1) - 1;
|
|
357
|
+
if (remaining <= 0) wiredBridges.delete(bridge);
|
|
358
|
+
else wiredBridges.set(bridge, remaining);
|
|
441
359
|
}
|
|
442
360
|
};
|
|
443
361
|
}
|
|
@@ -447,7 +365,7 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
447
365
|
// src/cursor-sync-plugin.ts
|
|
448
366
|
var import_prosemirror_state2 = require("prosemirror-state");
|
|
449
367
|
var import_y_prosemirror2 = require("y-prosemirror");
|
|
450
|
-
var
|
|
368
|
+
var import_yjs = require("yjs");
|
|
451
369
|
var import_core = require("@pm-cm/core");
|
|
452
370
|
var cursorSyncPluginKey = new import_prosemirror_state2.PluginKey("pm-cm-cursor-sync");
|
|
453
371
|
function getYSyncState(view) {
|
|
@@ -478,8 +396,8 @@ function broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead) {
|
|
|
478
396
|
function broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead) {
|
|
479
397
|
const len = sharedText.length;
|
|
480
398
|
const clamp = (v) => Math.max(0, Math.min(v, len));
|
|
481
|
-
const relAnchor = (0,
|
|
482
|
-
const relHead = (0,
|
|
399
|
+
const relAnchor = (0, import_yjs.createRelativePositionFromTypeIndex)(sharedText, clamp(textAnchor));
|
|
400
|
+
const relHead = (0, import_yjs.createRelativePositionFromTypeIndex)(sharedText, clamp(textHead));
|
|
483
401
|
awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead });
|
|
484
402
|
}
|
|
485
403
|
var defaultOnWarning2 = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
@@ -488,12 +406,23 @@ function createCursorSyncPlugin(options) {
|
|
|
488
406
|
const warn = options.onWarning ?? defaultOnWarning2;
|
|
489
407
|
const cursorFieldName = options.cursorFieldName ?? "pmCursor";
|
|
490
408
|
const cmCursorFieldName = options.cmCursorFieldName ?? "cursor";
|
|
409
|
+
if (sharedText && cursorFieldName === cmCursorFieldName) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`createCursorSyncPlugin: cursorFieldName and cmCursorFieldName must differ when sharedText is provided (both are "${cursorFieldName}")`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
491
414
|
let warnedSyncPluginMissing = false;
|
|
415
|
+
let pendingCmCursor = null;
|
|
492
416
|
let cachedMap = null;
|
|
493
417
|
let cachedMapDoc = null;
|
|
494
418
|
function getOrBuildMap(doc) {
|
|
495
419
|
if (cachedMapDoc !== doc || !cachedMap) {
|
|
496
|
-
|
|
420
|
+
try {
|
|
421
|
+
cachedMap = (0, import_core.buildCursorMap)(doc, serialize);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
warn({ code: "cursor-map-error", message: "failed to build cursor map \u2014 cursor sync skipped" });
|
|
424
|
+
cachedMap = null;
|
|
425
|
+
}
|
|
497
426
|
cachedMapDoc = doc;
|
|
498
427
|
}
|
|
499
428
|
return cachedMap;
|
|
@@ -507,62 +436,141 @@ function createCursorSyncPlugin(options) {
|
|
|
507
436
|
apply(tr, prev, _oldState, newState) {
|
|
508
437
|
const cmMeta = tr.getMeta(cursorSyncPluginKey);
|
|
509
438
|
if (cmMeta) {
|
|
510
|
-
|
|
439
|
+
pendingCmCursor = cmMeta;
|
|
511
440
|
}
|
|
512
441
|
let mappedTextOffset = prev.mappedTextOffset;
|
|
513
442
|
if (tr.selectionSet || tr.docChanged) {
|
|
514
443
|
const map = getOrBuildMap(newState.doc);
|
|
515
|
-
mappedTextOffset = (0, import_core.cursorMapLookup)(map, newState.selection.anchor);
|
|
444
|
+
mappedTextOffset = map ? (0, import_core.cursorMapLookup)(map, newState.selection.anchor) : null;
|
|
516
445
|
}
|
|
517
446
|
return {
|
|
518
|
-
pendingCm:
|
|
447
|
+
pendingCm: pendingCmCursor,
|
|
519
448
|
mappedTextOffset
|
|
520
449
|
};
|
|
521
450
|
}
|
|
522
451
|
},
|
|
523
|
-
view() {
|
|
452
|
+
view(editorView) {
|
|
453
|
+
let suppressCmReaction = false;
|
|
454
|
+
let lastCmAbsAnchor = -1;
|
|
455
|
+
let lastCmAbsHead = -1;
|
|
456
|
+
let cmCursorHandledByListener = false;
|
|
457
|
+
const handleAwarenessUpdate = ({ added, updated }) => {
|
|
458
|
+
if (suppressCmReaction) return;
|
|
459
|
+
if (!sharedText?.doc) return;
|
|
460
|
+
const localId = awareness.clientID;
|
|
461
|
+
if (!updated.includes(localId) && !added.includes(localId)) return;
|
|
462
|
+
const localState = awareness.getLocalState();
|
|
463
|
+
if (!localState) return;
|
|
464
|
+
const cmCursor = localState[cmCursorFieldName];
|
|
465
|
+
if (!cmCursor?.anchor || !cmCursor?.head) {
|
|
466
|
+
if (lastCmAbsAnchor !== -1) {
|
|
467
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
468
|
+
lastCmAbsAnchor = -1;
|
|
469
|
+
lastCmAbsHead = -1;
|
|
470
|
+
}
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const absAnchor = (0, import_yjs.createAbsolutePositionFromRelativePosition)(cmCursor.anchor, sharedText.doc);
|
|
474
|
+
const absHead = (0, import_yjs.createAbsolutePositionFromRelativePosition)(cmCursor.head, sharedText.doc);
|
|
475
|
+
if (!absAnchor || !absHead) {
|
|
476
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
477
|
+
lastCmAbsAnchor = -1;
|
|
478
|
+
lastCmAbsHead = -1;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return;
|
|
482
|
+
const map = getOrBuildMap(editorView.state.doc);
|
|
483
|
+
if (!map) {
|
|
484
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const pmAnchor = (0, import_core.reverseCursorMapLookup)(map, absAnchor.index);
|
|
488
|
+
const pmHead = (0, import_core.reverseCursorMapLookup)(map, absHead.index);
|
|
489
|
+
if (pmAnchor === null || pmHead === null) {
|
|
490
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
lastCmAbsAnchor = absAnchor.index;
|
|
494
|
+
lastCmAbsHead = absHead.index;
|
|
495
|
+
cmCursorHandledByListener = true;
|
|
496
|
+
const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead);
|
|
497
|
+
if (!ok && !warnedSyncPluginMissing) {
|
|
498
|
+
warnedSyncPluginMissing = true;
|
|
499
|
+
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
if (sharedText) {
|
|
503
|
+
awareness.on("update", handleAwarenessUpdate);
|
|
504
|
+
}
|
|
524
505
|
return {
|
|
525
506
|
update(view, prevState) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const pmHead = (0, import_core.reverseCursorMapLookup)(map, pluginState.pendingCm.head);
|
|
532
|
-
if (pmAnchor !== null && pmHead !== null) {
|
|
533
|
-
const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead);
|
|
534
|
-
if (!ok && !warnedSyncPluginMissing) {
|
|
535
|
-
warnedSyncPluginMissing = true;
|
|
536
|
-
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
537
|
-
}
|
|
507
|
+
if (view.state.doc !== prevState.doc) {
|
|
508
|
+
lastCmAbsAnchor = -1;
|
|
509
|
+
lastCmAbsHead = -1;
|
|
510
|
+
if (sharedText?.doc && !suppressCmReaction) {
|
|
511
|
+
handleAwarenessUpdate({ added: [awareness.clientID], updated: [], removed: [] });
|
|
538
512
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
);
|
|
513
|
+
}
|
|
514
|
+
if (pendingCmCursor != null) {
|
|
515
|
+
const cursor = pendingCmCursor;
|
|
516
|
+
pendingCmCursor = null;
|
|
517
|
+
if (!cmCursorHandledByListener) {
|
|
518
|
+
const map = getOrBuildMap(view.state.doc);
|
|
519
|
+
const pmAnchor = map ? (0, import_core.reverseCursorMapLookup)(map, cursor.anchor) : null;
|
|
520
|
+
const pmHead = map ? (0, import_core.reverseCursorMapLookup)(map, cursor.head) : null;
|
|
521
|
+
if (pmAnchor !== null && pmHead !== null) {
|
|
522
|
+
const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead);
|
|
523
|
+
if (!ok && !warnedSyncPluginMissing) {
|
|
524
|
+
warnedSyncPluginMissing = true;
|
|
525
|
+
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
526
|
+
}
|
|
527
|
+
if (sharedText?.doc) {
|
|
528
|
+
broadcastTextCursor(
|
|
529
|
+
awareness,
|
|
530
|
+
cmCursorFieldName,
|
|
531
|
+
sharedText,
|
|
532
|
+
cursor.anchor,
|
|
533
|
+
cursor.head
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
538
|
+
}
|
|
547
539
|
}
|
|
540
|
+
cmCursorHandledByListener = false;
|
|
548
541
|
return;
|
|
549
542
|
}
|
|
550
543
|
if (view.hasFocus() && (view.state.selection !== prevState.selection || view.state.doc !== prevState.doc)) {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
544
|
+
suppressCmReaction = true;
|
|
545
|
+
try {
|
|
546
|
+
const { anchor, head } = view.state.selection;
|
|
547
|
+
const ok = broadcastPmCursor(awareness, cursorFieldName, view, anchor, head);
|
|
548
|
+
if (!ok && !warnedSyncPluginMissing) {
|
|
549
|
+
warnedSyncPluginMissing = true;
|
|
550
|
+
warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
|
|
551
|
+
}
|
|
552
|
+
if (sharedText?.doc) {
|
|
553
|
+
const map = getOrBuildMap(view.state.doc);
|
|
554
|
+
const textAnchor = map ? (0, import_core.cursorMapLookup)(map, anchor) : null;
|
|
555
|
+
const textHead = map ? (0, import_core.cursorMapLookup)(map, head) : null;
|
|
556
|
+
if (textAnchor !== null && textHead !== null) {
|
|
557
|
+
broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead);
|
|
558
|
+
} else {
|
|
559
|
+
awareness.setLocalStateField(cmCursorFieldName, null);
|
|
560
|
+
}
|
|
563
561
|
}
|
|
562
|
+
} finally {
|
|
563
|
+
suppressCmReaction = false;
|
|
564
564
|
}
|
|
565
565
|
}
|
|
566
|
+
cmCursorHandledByListener = false;
|
|
567
|
+
},
|
|
568
|
+
destroy() {
|
|
569
|
+
awareness.setLocalStateField(cursorFieldName, null);
|
|
570
|
+
if (sharedText) {
|
|
571
|
+
awareness.setLocalStateField(cmCursorFieldName, null);
|
|
572
|
+
awareness.off("update", handleAwarenessUpdate);
|
|
573
|
+
}
|
|
566
574
|
}
|
|
567
575
|
};
|
|
568
576
|
}
|
|
@@ -573,7 +581,10 @@ function syncCmCursor(view, anchor, head, onWarning) {
|
|
|
573
581
|
(onWarning ?? defaultOnWarning2)({ code: "cursor-sync-not-installed", message: "cursor sync plugin is not installed on this EditorView" });
|
|
574
582
|
return;
|
|
575
583
|
}
|
|
576
|
-
const sanitize = (v) =>
|
|
584
|
+
const sanitize = (v) => {
|
|
585
|
+
const n = Math.floor(v);
|
|
586
|
+
return Number.isFinite(n) ? Math.max(0, n) : 0;
|
|
587
|
+
};
|
|
577
588
|
view.dispatch(
|
|
578
589
|
view.state.tr.setMeta(cursorSyncPluginKey, {
|
|
579
590
|
anchor: sanitize(anchor),
|