@pm-cm/yjs 0.0.1 → 0.0.2
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/README.md +2 -0
- package/dist/index.cjs +144 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -10
- package/dist/index.d.ts +20 -10
- package/dist/index.js +138 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Collaborative split-editor sync between ProseMirror and CodeMirror over Yjs.
|
|
4
4
|
|
|
5
|
+
[Demo](https://munenick.github.io/prosemirror-codemirror-sync/#/yjs)
|
|
6
|
+
|
|
5
7
|
Extends `@pm-cm/core` with real-time collaboration: synchronizes a ProseMirror `XmlFragment` and a CodeMirror-friendly `Y.Text` through a single Yjs `Doc`, with collaborative cursor support. The serialization format is pluggable — you provide `serialize` and `parse` functions.
|
|
6
8
|
|
|
7
9
|
## Install
|
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
createAwarenessProxy: () => createAwarenessProxy,
|
|
29
29
|
createBridgeSyncPlugin: () => createBridgeSyncPlugin,
|
|
30
30
|
createCollabPlugins: () => createCollabPlugins,
|
|
31
|
+
createCursorMapWriter: () => import_core3.createCursorMapWriter,
|
|
31
32
|
createCursorSyncPlugin: () => createCursorSyncPlugin,
|
|
32
33
|
createYjsBridge: () => createYjsBridge,
|
|
33
34
|
cursorMapLookup: () => import_core2.cursorMapLookup,
|
|
@@ -35,12 +36,14 @@ __export(index_exports, {
|
|
|
35
36
|
replaceSharedProseMirror: () => replaceSharedProseMirror,
|
|
36
37
|
replaceSharedText: () => replaceSharedText,
|
|
37
38
|
reverseCursorMapLookup: () => import_core2.reverseCursorMapLookup,
|
|
38
|
-
syncCmCursor: () => syncCmCursor
|
|
39
|
+
syncCmCursor: () => syncCmCursor,
|
|
40
|
+
wrapSerialize: () => import_core3.wrapSerialize
|
|
39
41
|
});
|
|
40
42
|
module.exports = __toCommonJS(index_exports);
|
|
41
43
|
|
|
42
44
|
// src/bridge.ts
|
|
43
45
|
var import_y_prosemirror = require("y-prosemirror");
|
|
46
|
+
var import_yjs = require("yjs");
|
|
44
47
|
|
|
45
48
|
// src/types.ts
|
|
46
49
|
var ORIGIN_TEXT_TO_PM = "bridge:text-to-prosemirror";
|
|
@@ -98,11 +101,121 @@ function replaceSharedProseMirror(doc, fragment, text, origin, config) {
|
|
|
98
101
|
onError({ code: "parse-error", message: "failed to parse text into ProseMirror document", cause: error });
|
|
99
102
|
return { ok: false, reason: "parse-error" };
|
|
100
103
|
}
|
|
101
|
-
|
|
102
|
-
(0, import_y_prosemirror.
|
|
103
|
-
|
|
104
|
+
try {
|
|
105
|
+
const currentDoc = (0, import_y_prosemirror.yXmlFragmentToProseMirrorRootNode)(fragment, config.schema);
|
|
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
|
+
}
|
|
104
116
|
return { ok: true };
|
|
105
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
|
+
}
|
|
216
|
+
}, origin);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
106
219
|
function createYjsBridge(config, options) {
|
|
107
220
|
const {
|
|
108
221
|
doc,
|
|
@@ -138,10 +251,10 @@ function createYjsBridge(config, options) {
|
|
|
138
251
|
normalize,
|
|
139
252
|
onError
|
|
140
253
|
});
|
|
141
|
-
if (result.ok) {
|
|
254
|
+
if (result.ok || result.reason === "unchanged") {
|
|
142
255
|
lastBridgedText = text;
|
|
143
256
|
}
|
|
144
|
-
return result.ok;
|
|
257
|
+
return result.ok || result.reason === "unchanged";
|
|
145
258
|
};
|
|
146
259
|
const sharedProseMirrorToText = (fragment) => {
|
|
147
260
|
try {
|
|
@@ -255,15 +368,28 @@ function createAwarenessProxy(awareness, cursorField = "pmCursor") {
|
|
|
255
368
|
if (prop === "getLocalState") {
|
|
256
369
|
return () => {
|
|
257
370
|
const state = target.getLocalState();
|
|
258
|
-
return state ? { ...state,
|
|
371
|
+
return state ? { ...state, cursor: null } : state;
|
|
259
372
|
};
|
|
260
373
|
}
|
|
261
374
|
if (prop === "setLocalStateField") {
|
|
262
375
|
return (field, value2) => {
|
|
263
|
-
if (field ===
|
|
376
|
+
if (field === "cursor") return;
|
|
264
377
|
target.setLocalStateField(field, value2);
|
|
265
378
|
};
|
|
266
379
|
}
|
|
380
|
+
if (prop === "getStates") {
|
|
381
|
+
return () => {
|
|
382
|
+
const states = target.getStates();
|
|
383
|
+
const remapped = /* @__PURE__ */ new Map();
|
|
384
|
+
states.forEach((state, clientId) => {
|
|
385
|
+
remapped.set(clientId, {
|
|
386
|
+
...state,
|
|
387
|
+
cursor: state[cursorField] ?? null
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
return remapped;
|
|
391
|
+
};
|
|
392
|
+
}
|
|
267
393
|
const value = Reflect.get(target, prop, receiver);
|
|
268
394
|
return typeof value === "function" ? value.bind(target) : value;
|
|
269
395
|
}
|
|
@@ -321,7 +447,7 @@ function createBridgeSyncPlugin(bridge, options = {}) {
|
|
|
321
447
|
// src/cursor-sync-plugin.ts
|
|
322
448
|
var import_prosemirror_state2 = require("prosemirror-state");
|
|
323
449
|
var import_y_prosemirror2 = require("y-prosemirror");
|
|
324
|
-
var
|
|
450
|
+
var import_yjs2 = require("yjs");
|
|
325
451
|
var import_core = require("@pm-cm/core");
|
|
326
452
|
var cursorSyncPluginKey = new import_prosemirror_state2.PluginKey("pm-cm-cursor-sync");
|
|
327
453
|
function getYSyncState(view) {
|
|
@@ -352,13 +478,13 @@ function broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead) {
|
|
|
352
478
|
function broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead) {
|
|
353
479
|
const len = sharedText.length;
|
|
354
480
|
const clamp = (v) => Math.max(0, Math.min(v, len));
|
|
355
|
-
const relAnchor = (0,
|
|
356
|
-
const relHead = (0,
|
|
481
|
+
const relAnchor = (0, import_yjs2.createRelativePositionFromTypeIndex)(sharedText, clamp(textAnchor));
|
|
482
|
+
const relHead = (0, import_yjs2.createRelativePositionFromTypeIndex)(sharedText, clamp(textHead));
|
|
357
483
|
awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead });
|
|
358
484
|
}
|
|
359
485
|
var defaultOnWarning2 = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
360
486
|
function createCursorSyncPlugin(options) {
|
|
361
|
-
const { awareness, serialize,
|
|
487
|
+
const { awareness, serialize, sharedText } = options;
|
|
362
488
|
const warn = options.onWarning ?? defaultOnWarning2;
|
|
363
489
|
const cursorFieldName = options.cursorFieldName ?? "pmCursor";
|
|
364
490
|
const cmCursorFieldName = options.cmCursorFieldName ?? "cursor";
|
|
@@ -367,7 +493,7 @@ function createCursorSyncPlugin(options) {
|
|
|
367
493
|
let cachedMapDoc = null;
|
|
368
494
|
function getOrBuildMap(doc) {
|
|
369
495
|
if (cachedMapDoc !== doc || !cachedMap) {
|
|
370
|
-
cachedMap = (0, import_core.buildCursorMap)(doc, serialize
|
|
496
|
+
cachedMap = (0, import_core.buildCursorMap)(doc, serialize);
|
|
371
497
|
cachedMapDoc = doc;
|
|
372
498
|
}
|
|
373
499
|
return cachedMap;
|
|
@@ -469,7 +595,7 @@ function createCollabPlugins(schema, options) {
|
|
|
469
595
|
const pmAwareness = enableCursorSync ? createAwarenessProxy(options.awareness, cursorFieldName) : options.awareness;
|
|
470
596
|
const plugins = [
|
|
471
597
|
(0, import_y_prosemirror3.ySyncPlugin)(sharedProseMirror, { mapping: rawMapping }),
|
|
472
|
-
(0, import_y_prosemirror3.yCursorPlugin)(pmAwareness, options.yCursorPluginOpts ?? {}
|
|
598
|
+
(0, import_y_prosemirror3.yCursorPlugin)(pmAwareness, options.yCursorPluginOpts ?? {}),
|
|
473
599
|
(0, import_y_prosemirror3.yUndoPlugin)(options.yUndoPluginOpts)
|
|
474
600
|
];
|
|
475
601
|
if (options.bridge) {
|
|
@@ -482,7 +608,6 @@ function createCollabPlugins(schema, options) {
|
|
|
482
608
|
serialize: options.serialize,
|
|
483
609
|
cursorFieldName,
|
|
484
610
|
cmCursorFieldName: options.cmCursorFieldName,
|
|
485
|
-
locate: options.locate,
|
|
486
611
|
sharedText: options.sharedText,
|
|
487
612
|
onWarning: options.onWarning
|
|
488
613
|
})
|
|
@@ -493,6 +618,7 @@ function createCollabPlugins(schema, options) {
|
|
|
493
618
|
|
|
494
619
|
// src/index.ts
|
|
495
620
|
var import_core2 = require("@pm-cm/core");
|
|
621
|
+
var import_core3 = require("@pm-cm/core");
|
|
496
622
|
// Annotate the CommonJS export names for ESM import in node:
|
|
497
623
|
0 && (module.exports = {
|
|
498
624
|
ORIGIN_INIT,
|
|
@@ -503,6 +629,7 @@ var import_core2 = require("@pm-cm/core");
|
|
|
503
629
|
createAwarenessProxy,
|
|
504
630
|
createBridgeSyncPlugin,
|
|
505
631
|
createCollabPlugins,
|
|
632
|
+
createCursorMapWriter,
|
|
506
633
|
createCursorSyncPlugin,
|
|
507
634
|
createYjsBridge,
|
|
508
635
|
cursorMapLookup,
|
|
@@ -510,6 +637,7 @@ var import_core2 = require("@pm-cm/core");
|
|
|
510
637
|
replaceSharedProseMirror,
|
|
511
638
|
replaceSharedText,
|
|
512
639
|
reverseCursorMapLookup,
|
|
513
|
-
syncCmCursor
|
|
640
|
+
syncCmCursor,
|
|
641
|
+
wrapSerialize
|
|
514
642
|
});
|
|
515
643
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/bridge.ts","../src/types.ts","../src/awareness-proxy.ts","../src/collab-plugins.ts","../src/bridge-sync-plugin.ts","../src/cursor-sync-plugin.ts"],"sourcesContent":["export { createYjsBridge, replaceSharedText, replaceSharedProseMirror } from './bridge.js'\nexport type { YjsBridgeOptions, ReplaceResult, ReplaceTextResult, ReplaceProseMirrorResult } from './bridge.js'\nexport { createAwarenessProxy } from './awareness-proxy.js'\nexport { createCollabPlugins } from './collab-plugins.js'\nexport type { CollabPluginsOptions, ProseMirrorMapping, YCursorPluginOpts, YUndoPluginOpts } from './collab-plugins.js'\nexport {\n ORIGIN_TEXT_TO_PM,\n ORIGIN_PM_TO_TEXT,\n ORIGIN_INIT,\n} from './types.js'\nexport type {\n BootstrapResult,\n YjsBridgeConfig,\n YjsBridgeHandle,\n WarningCode,\n WarningEvent,\n OnWarning,\n} from './types.js'\n\n// Cursor mapping re-exported from @pm-cm/core\nexport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nexport type { TextSegment, CursorMap, LocateText } from '@pm-cm/core'\n\n// Bridge sync plugin (auto PM→Y.Text wiring)\nexport { createBridgeSyncPlugin, bridgeSyncPluginKey } from './bridge-sync-plugin.js'\nexport type { BridgeSyncPluginOptions } from './bridge-sync-plugin.js'\n\n// Cursor sync plugin\nexport { createCursorSyncPlugin, cursorSyncPluginKey, syncCmCursor } from './cursor-sync-plugin.js'\nexport type { CursorSyncState, CursorSyncPluginOptions } from './cursor-sync-plugin.js'\n\n// Re-export types from @pm-cm/core\nexport type { Serialize, Parse, Normalize, OnError, ErrorCode, ErrorEvent } from '@pm-cm/core'\n","import type { Node } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport { prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, ySyncPluginKey } from 'y-prosemirror'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { Normalize, OnError } from '@pm-cm/core'\nimport type { BootstrapResult, YjsBridgeConfig, YjsBridgeHandle } from './types.js'\nimport { ORIGIN_INIT, ORIGIN_TEXT_TO_PM, ORIGIN_PM_TO_TEXT } from './types.js'\n\nconst defaultNormalize: Normalize = (s) => s.replace(/\\r\\n?/g, '\\n')\nconst defaultOnError: OnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause)\n\n/** Result of {@link replaceSharedText}. */\nexport type ReplaceTextResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' }\n | { ok: false; reason: 'detached' }\n\n/** Result of {@link replaceSharedProseMirror}. */\nexport type ReplaceProseMirrorResult =\n | { ok: true }\n | { ok: false; reason: 'parse-error' }\n | { ok: false; reason: 'detached' }\n\n/**\n * Union of all replace-result types. Kept for backward compatibility.\n * Prefer the narrower {@link ReplaceTextResult} / {@link ReplaceProseMirrorResult}.\n */\nexport type ReplaceResult = ReplaceTextResult | ReplaceProseMirrorResult\n\n/**\n * Replace `Y.Text` content using a minimal diff (common prefix/suffix trimming).\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedText(\n sharedText: YText,\n next: string,\n origin: unknown,\n normalize: Normalize = defaultNormalize,\n): ReplaceTextResult {\n if (!sharedText.doc) {\n return { ok: false, reason: 'detached' }\n }\n\n const normalized = normalize(next)\n const current = sharedText.toString()\n if (current === normalized) {\n return { ok: false, reason: 'unchanged' }\n }\n\n // Minimal diff: find common prefix and suffix, replace only the changed middle.\n let start = 0\n const minLen = Math.min(current.length, normalized.length)\n while (start < minLen && current.charCodeAt(start) === normalized.charCodeAt(start)) {\n start++\n }\n\n let endCurrent = current.length\n let endNext = normalized.length\n while (endCurrent > start && endNext > start && current.charCodeAt(endCurrent - 1) === normalized.charCodeAt(endNext - 1)) {\n endCurrent--\n endNext--\n }\n\n sharedText.doc.transact(() => {\n const deleteCount = endCurrent - start\n if (deleteCount > 0) {\n sharedText.delete(start, deleteCount)\n }\n const insertStr = normalized.slice(start, endNext)\n if (insertStr.length > 0) {\n sharedText.insert(start, insertStr)\n }\n }, origin)\n\n return { ok: true }\n}\n\n/**\n * Replace `Y.XmlFragment` by parsing serialized text into a ProseMirror document.\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedProseMirror(\n doc: Doc,\n fragment: YXmlFragment,\n text: string,\n origin: unknown,\n config: Pick<YjsBridgeConfig, 'schema' | 'parse' | 'normalize' | 'onError'>,\n): ReplaceProseMirrorResult {\n if (!fragment.doc) {\n return { ok: false, reason: 'detached' }\n }\n if (fragment.doc !== doc) {\n throw new Error('fragment belongs to a different Y.Doc than the provided doc')\n }\n\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n let nextDoc: Node\n try {\n nextDoc = config.parse(normalize(text), config.schema)\n } catch (error) {\n onError({ code: 'parse-error', message: 'failed to parse text into ProseMirror document', cause: error })\n return { ok: false, reason: 'parse-error' }\n }\n\n doc.transact(() => {\n prosemirrorToYXmlFragment(nextDoc, fragment)\n }, origin)\n return { ok: true }\n}\n\n/** Options for {@link createYjsBridge}. */\nexport type YjsBridgeOptions = {\n initialText?: string\n /** Which side wins when both sharedText and sharedProseMirror exist and differ. Default `'text'`. */\n prefer?: 'text' | 'prosemirror'\n}\n\n/**\n * Create a collaborative bridge that keeps `Y.Text` and `Y.XmlFragment` in sync.\n *\n * Runs a synchronous bootstrap to reconcile existing state, then installs a\n * `Y.Text` observer for the text → ProseMirror direction.\n *\n * @throws If `sharedText` or `sharedProseMirror` belong to a different `Y.Doc`.\n */\nexport function createYjsBridge(\n config: YjsBridgeConfig,\n options?: YjsBridgeOptions,\n): YjsBridgeHandle {\n const {\n doc,\n sharedText,\n sharedProseMirror,\n schema,\n serialize,\n parse,\n } = config\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n\n if (!sharedText.doc) {\n throw new Error('sharedText is not attached to any Y.Doc')\n }\n if (sharedText.doc !== doc) {\n throw new Error('sharedText belongs to a different Y.Doc than the provided doc')\n }\n if (!sharedProseMirror.doc) {\n throw new Error('sharedProseMirror is not attached to any Y.Doc')\n }\n if (sharedProseMirror.doc !== doc) {\n throw new Error('sharedProseMirror belongs to a different Y.Doc than the provided doc')\n }\n\n let lastBridgedText: string | null = null\n\n /** Returns `true` if the parse succeeded. */\n const syncTextToProsemirror = (origin: unknown): boolean => {\n const text = normalize(sharedText.toString())\n if (lastBridgedText === text) {\n return true\n }\n\n const result = replaceSharedProseMirror(doc, sharedProseMirror, text, origin, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (result.ok) {\n lastBridgedText = text\n }\n return result.ok\n }\n\n const sharedProseMirrorToText = (fragment: YXmlFragment): string | null => {\n try {\n const pmDoc = yXmlFragmentToProseMirrorRootNode(fragment, schema)\n return normalize(serialize(pmDoc))\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to convert ProseMirror fragment to text', cause: error })\n return null\n }\n }\n\n // Bootstrap\n const bootstrap = (): BootstrapResult => {\n const text = normalize(sharedText.toString())\n const hasText = text.length > 0\n const hasProsemirror = sharedProseMirror.length > 0\n\n if (!hasText && !hasProsemirror) {\n const initial = options?.initialText ?? ''\n if (initial.length > 0) {\n // Set Y.XmlFragment first, then derive Y.Text from serialize(parse(initial))\n // to ensure both shared types are in the same canonical form.\n const initResult = replaceSharedProseMirror(doc, sharedProseMirror, initial, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!initResult.ok) {\n return { source: 'initial', parseError: true }\n }\n const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema)\n const canonicalText = serialize(pmDoc)\n replaceSharedText(sharedText, canonicalText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(canonicalText)\n return { source: 'initial' }\n }\n return { source: 'empty' }\n }\n\n if (hasText && !hasProsemirror) {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n\n if (!hasText && hasProsemirror) {\n const textFromProsemirror = sharedProseMirrorToText(sharedProseMirror)\n if (textFromProsemirror !== null) {\n replaceSharedText(sharedText, textFromProsemirror, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(textFromProsemirror)\n return { source: 'prosemirror' }\n }\n return { source: 'prosemirror', parseError: true }\n }\n\n const prosemirrorText = sharedProseMirrorToText(sharedProseMirror)\n if (prosemirrorText === null) {\n const fallbackText = hasText ? text : (options?.initialText ?? '')\n let parseError = false\n if (fallbackText.length > 0) {\n replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize)\n const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!fallbackResult.ok) parseError = true\n }\n return { source: 'text', ...(parseError && { parseError: true }) }\n }\n\n if (prosemirrorText !== text) {\n const prefer = options?.prefer ?? 'text'\n if (prefer === 'prosemirror') {\n replaceSharedText(sharedText, prosemirrorText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(prosemirrorText)\n return { source: 'prosemirror' }\n } else {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n } else {\n lastBridgedText = text\n return { source: 'both-match' }\n }\n }\n\n const textObserver = (\n _: unknown,\n transaction: { origin: unknown },\n ) => {\n if (transaction.origin === ORIGIN_PM_TO_TEXT || transaction.origin === ORIGIN_INIT) {\n return\n }\n\n syncTextToProsemirror(ORIGIN_TEXT_TO_PM)\n }\n\n // Run bootstrap synchronously before installing the observer so that\n // an exception during bootstrap cannot leave a dangling observer.\n const bootstrapResult = bootstrap()\n\n sharedText.observe(textObserver)\n\n return {\n bootstrapResult,\n syncToSharedText(doc: Node): ReplaceTextResult {\n const text = serialize(doc)\n const result = replaceSharedText(sharedText, text, ORIGIN_PM_TO_TEXT, normalize)\n // Always update lastBridgedText unless truly failed (detached).\n // 'unchanged' means Y.Text already has this content — still need to\n // record it so the reverse observer doesn't trigger a redundant sync.\n if (result.ok || result.reason === 'unchanged') {\n lastBridgedText = normalize(text)\n }\n return result\n },\n isYjsSyncChange(tr: Transaction): boolean {\n // Internal meta shape from y-prosemirror's ySyncPlugin (tested against ^1.3.x).\n const meta = tr.getMeta(ySyncPluginKey)\n return (\n typeof meta === 'object' &&\n meta !== null &&\n 'isChangeOrigin' in meta &&\n (meta as Record<string, unknown>).isChangeOrigin === true\n )\n },\n dispose() {\n sharedText.unobserve(textObserver)\n },\n }\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { Serialize, Parse, Normalize, OnError } from '@pm-cm/core'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { ReplaceTextResult } from './bridge.js'\n\n/** Known warning codes emitted by the yjs bridge and plugins. */\nexport type WarningCode = 'bridge-already-wired' | 'sync-failed' | 'ysync-plugin-missing' | 'cursor-sync-not-installed'\n\n/** Structured warning event for non-fatal warnings. */\nexport type WarningEvent = {\n code: WarningCode\n message: string\n}\n\n/**\n * Warning handler callback for non-fatal warnings.\n *\n * Known codes:\n * - `'bridge-already-wired'` — the same bridge handle is wired to multiple plugin instances.\n * - `'sync-failed'` — `syncToSharedText` failed (e.g. Y.Text detached).\n * - `'ysync-plugin-missing'` — ySyncPlugin state is not available; cursor broadcast skipped.\n * - `'cursor-sync-not-installed'` — cursor sync plugin is not installed on the EditorView.\n */\nexport type OnWarning = (event: WarningEvent) => void\n\n/** Yjs transaction origin: text → ProseMirror direction. */\nexport const ORIGIN_TEXT_TO_PM = 'bridge:text-to-prosemirror'\n\n/** Yjs transaction origin: ProseMirror → text direction. */\nexport const ORIGIN_PM_TO_TEXT = 'bridge:prosemirror-to-text'\n\n/** Yjs transaction origin: bootstrap initialization. */\nexport const ORIGIN_INIT = 'bridge:init'\n\n/** Configuration for {@link createYjsBridge}. */\nexport type YjsBridgeConfig = {\n doc: Doc\n sharedText: YText\n sharedProseMirror: YXmlFragment\n schema: Schema\n serialize: Serialize\n parse: Parse\n normalize?: Normalize\n /** Called on non-fatal errors (e.g. parse failures). Defaults to `console.error`. */\n onError?: OnError\n}\n\n/**\n * Result of the bootstrap phase in {@link createYjsBridge}.\n * Indicates which source was used to initialize the shared types.\n */\nexport type BootstrapResult = {\n source: 'text' | 'prosemirror' | 'both-match' | 'empty' | 'initial'\n /** `true` when format conversion (parse or serialize) failed during bootstrap. The bridge is still usable but the affected shared type may be stale. */\n parseError?: boolean\n}\n\n/** Handle returned by {@link createYjsBridge}. */\nexport type YjsBridgeHandle = {\n /** Result of the synchronous bootstrap phase. */\n readonly bootstrapResult: BootstrapResult\n /** Serialize `doc` and push to `Y.Text` using minimal diff. */\n syncToSharedText(doc: Node): ReplaceTextResult\n /** Returns `true` if the transaction originated from `y-prosemirror` sync. */\n isYjsSyncChange(tr: Transaction): boolean\n /** Remove the Y.Text observer. Call when tearing down. */\n dispose(): void\n}\n","import type { Awareness } from 'y-protocols/awareness'\n\n/**\n * Create a Proxy around a Yjs {@link Awareness} that suppresses the specified\n * cursor field. This prevents y-prosemirror's built-in cursor management from\n * conflicting with the PM↔CM cursor sync plugin.\n *\n * Other `setLocalStateField` calls are passed through unchanged.\n */\nexport function createAwarenessProxy(awareness: Awareness, cursorField = 'pmCursor'): Awareness {\n return new Proxy(awareness, {\n get(target, prop, receiver) {\n if (prop === 'getLocalState') {\n return () => {\n const state = target.getLocalState()\n return state ? { ...state, [cursorField]: null } : state\n }\n }\n if (prop === 'setLocalStateField') {\n return (field: string, value: unknown) => {\n // Only suppress the cursor field; pass through other fields\n if (field === cursorField) return\n target.setLocalStateField(field, value)\n }\n }\n const value = Reflect.get(target, prop, receiver) as unknown\n return typeof value === 'function' ? (value as Function).bind(target) : value\n },\n }) as Awareness\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { EditorState, Plugin } from 'prosemirror-state'\nimport type { DecorationAttrs } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport type { Serialize, LocateText } from '@pm-cm/core'\nimport { initProseMirrorDoc, yCursorPlugin, ySyncPlugin, yUndoPlugin } from 'y-prosemirror'\nimport type { AbstractType, Text as YText, UndoManager } from 'yjs'\nimport type { XmlFragment as YXmlFragment } from 'yjs'\nimport { createAwarenessProxy } from './awareness-proxy.js'\nimport { createBridgeSyncPlugin } from './bridge-sync-plugin.js'\nimport { createCursorSyncPlugin } from './cursor-sync-plugin.js'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\n/** Yjs ↔ ProseMirror node mapping used by `y-prosemirror`. */\nexport type ProseMirrorMapping = Map<AbstractType<unknown>, Node | Node[]>\n\n/** Options forwarded to `yCursorPlugin` from y-prosemirror. */\nexport type YCursorPluginOpts = {\n awarenessStateFilter?: (currentClientId: number, userClientId: number, user: unknown) => boolean\n cursorBuilder?: (user: unknown, clientId: number) => HTMLElement\n selectionBuilder?: (user: unknown, clientId: number) => DecorationAttrs\n getSelection?: (state: EditorState) => unknown\n}\n\n/** Options forwarded to `yUndoPlugin` from y-prosemirror. */\nexport type YUndoPluginOpts = {\n protectedNodes?: Set<string>\n trackedOrigins?: unknown[]\n undoManager?: UndoManager | null\n}\n\n/** Options for {@link createCollabPlugins}. */\nexport type CollabPluginsOptions = {\n /** Shared ProseMirror document in Yjs. */\n sharedProseMirror: YXmlFragment\n awareness: Awareness\n cursorFieldName?: string\n serialize?: Serialize\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n locate?: LocateText\n /**\n * Enable PM↔CM cursor sync. Default `false`.\n *\n * When enabled, an {@link createAwarenessProxy | awareness proxy} is applied\n * to suppress y-prosemirror's built-in cursor management.\n */\n cursorSync?: boolean\n /**\n * The shared `Y.Text` instance. When provided, the cursor sync plugin also\n * broadcasts CM-format cursor positions so remote `yCollab` instances render them.\n */\n sharedText?: YText\n /**\n * When provided, a bridge sync plugin is inserted before the cursor sync plugin\n * to ensure Y.Text is synced before cursor positions are computed. This guarantees\n * that serialize-based offsets match Y.Text indices.\n */\n bridge?: YjsBridgeHandle\n /** Extra options forwarded to `yCursorPlugin`. */\n yCursorPluginOpts?: YCursorPluginOpts\n /** Extra options forwarded to `yUndoPlugin`. */\n yUndoPluginOpts?: YUndoPluginOpts\n /** Called for non-fatal warnings. Propagated to child plugins. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * Bundle `ySyncPlugin`, `yCursorPlugin`, `yUndoPlugin` from y-prosemirror,\n * plus an optional PM↔CM cursor sync plugin.\n *\n * @throws If `cursorSync: true` but `serialize` is not provided.\n */\nexport function createCollabPlugins(\n schema: Schema,\n options: CollabPluginsOptions,\n): { plugins: Plugin[]; doc: Node; mapping: ProseMirrorMapping } {\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const enableCursorSync = options.cursorSync ?? false\n const { sharedProseMirror } = options\n\n if (enableCursorSync && !options.serialize) {\n throw new Error('createCollabPlugins: cursorSync requires serialize to be provided')\n }\n const { doc, mapping: rawMapping } = initProseMirrorDoc(sharedProseMirror, schema)\n const mapping = rawMapping as ProseMirrorMapping\n const pmAwareness = enableCursorSync\n ? createAwarenessProxy(options.awareness, cursorFieldName)\n : options.awareness\n\n const plugins: Plugin[] = [\n ySyncPlugin(sharedProseMirror, { mapping: rawMapping }),\n yCursorPlugin(pmAwareness, options.yCursorPluginOpts ?? {}, cursorFieldName),\n yUndoPlugin(options.yUndoPluginOpts),\n ]\n\n // Bridge sync plugin must run before cursor sync plugin so that\n // Y.Text is updated before cursor positions are computed.\n if (options.bridge) {\n plugins.push(createBridgeSyncPlugin(options.bridge, { onWarning: options.onWarning }))\n }\n\n if (enableCursorSync && options.serialize) {\n plugins.push(\n createCursorSyncPlugin({\n awareness: options.awareness,\n serialize: options.serialize,\n cursorFieldName,\n cmCursorFieldName: options.cmCursorFieldName,\n locate: options.locate,\n sharedText: options.sharedText,\n onWarning: options.onWarning,\n }),\n )\n }\n\n return { plugins, doc, mapping }\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\ntype BridgeSyncState = { needsSync: boolean }\n\ntype BridgeSyncFailure = { ok: false; reason: 'detached' }\n\n/** Options for {@link createBridgeSyncPlugin}. */\nexport type BridgeSyncPluginOptions = {\n /** Called when `syncToSharedText` fails (excludes `reason: 'unchanged'`). */\n onSyncFailure?: (result: BridgeSyncFailure, view: EditorView) => void\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/** ProseMirror plugin key for {@link createBridgeSyncPlugin}. Use to read the plugin state. */\nexport const bridgeSyncPluginKey = new PluginKey<BridgeSyncState>('pm-cm-bridge-sync')\n\nconst wiredBridges = new WeakSet<YjsBridgeHandle>()\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/**\n * ProseMirror plugin that automatically syncs PM doc changes to Y.Text\n * via the bridge handle. Skips Yjs-originated changes to avoid loops.\n *\n * A warning is logged if the same bridge handle is wired more than once.\n * The guard is cleaned up when the plugin is destroyed.\n */\nexport function createBridgeSyncPlugin(\n bridge: YjsBridgeHandle,\n options: BridgeSyncPluginOptions = {},\n): Plugin {\n const warn = options.onWarning ?? defaultOnWarning\n if (wiredBridges.has(bridge)) {\n warn({ code: 'bridge-already-wired', message: 'this bridge is already wired to another plugin instance' })\n }\n wiredBridges.add(bridge)\n\n return new Plugin<BridgeSyncState>({\n key: bridgeSyncPluginKey,\n\n state: {\n init(): BridgeSyncState {\n return { needsSync: false }\n },\n apply(tr, _prev): BridgeSyncState {\n if (!tr.docChanged) return { needsSync: false }\n if (bridge.isYjsSyncChange(tr)) return { needsSync: false }\n return { needsSync: true }\n },\n },\n\n view() {\n return {\n update(view) {\n const state = bridgeSyncPluginKey.getState(view.state)\n if (state?.needsSync) {\n const result = bridge.syncToSharedText(view.state.doc)\n if (!result.ok) {\n if (result.reason === 'detached') {\n options.onSyncFailure?.(result, view)\n warn({ code: 'sync-failed', message: `bridge sync failed: ${result.reason}` })\n }\n }\n }\n },\n destroy() {\n wiredBridges.delete(bridge)\n },\n }\n },\n })\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { Node } from 'prosemirror-model'\nimport type { EditorView } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport { absolutePositionToRelativePosition, ySyncPluginKey } from 'y-prosemirror'\nimport { createRelativePositionFromTypeIndex } from 'yjs'\nimport type { Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { Serialize, LocateText, CursorMap } from '@pm-cm/core'\nimport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nimport type { OnWarning } from './types.js'\n\n/** Plugin state for the cursor sync plugin. Read via {@link cursorSyncPluginKey}. */\nexport type CursorSyncState = {\n /** Pending CodeMirror cursor to broadcast. Set by {@link syncCmCursor}. */\n pendingCm: { anchor: number; head: number } | null\n /** Text offset mapped from the current PM selection anchor. `null` when no mapping is available. */\n mappedTextOffset: number | null\n}\n\n/** ProseMirror plugin key for {@link createCursorSyncPlugin}. Use to read the plugin state. */\nexport const cursorSyncPluginKey = new PluginKey<CursorSyncState>('pm-cm-cursor-sync')\n\n/**\n * Internal shape of `ySyncPluginKey` state from y-prosemirror.\n * Not exported by upstream — kept here for explicit tracking.\n * Tested against y-prosemirror ^1.3.x.\n */\ntype YSyncPluginState = { type: YXmlFragment; binding: { mapping: Map<unknown, unknown> } }\n\nfunction getYSyncState(view: EditorView): YSyncPluginState | null {\n const raw = ySyncPluginKey.getState(view.state) as Record<string, unknown> | undefined\n if (!raw) return null\n if (\n typeof raw === 'object' &&\n 'type' in raw && raw.type &&\n 'binding' in raw && raw.binding &&\n typeof raw.binding === 'object' &&\n 'mapping' in (raw.binding as Record<string, unknown>) &&\n (raw.binding as Record<string, unknown>).mapping instanceof Map\n ) {\n return raw as unknown as YSyncPluginState\n }\n return null\n}\n\nfunction toRelativePosition(\n view: EditorView,\n pmPos: number,\n): unknown | null {\n const ySyncState = getYSyncState(view)\n if (!ySyncState) return null\n\n return absolutePositionToRelativePosition(\n pmPos,\n ySyncState.type,\n ySyncState.binding.mapping as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- y-prosemirror internal mapping type\n )\n}\n\n/** Returns `false` when ySyncPlugin state is unavailable (plugin not installed). */\nfunction broadcastPmCursor(\n awareness: Awareness,\n cursorFieldName: string,\n view: EditorView,\n pmAnchor: number,\n pmHead: number,\n): boolean {\n const relAnchor = toRelativePosition(view, pmAnchor)\n const relHead = toRelativePosition(view, pmHead)\n if (relAnchor === null || relHead === null) return false\n\n awareness.setLocalStateField(cursorFieldName, { anchor: relAnchor, head: relHead })\n return true\n}\n\nfunction broadcastTextCursor(\n awareness: Awareness,\n cmCursorFieldName: string,\n sharedText: YText,\n textAnchor: number,\n textHead: number,\n): void {\n const len = sharedText.length\n const clamp = (v: number) => Math.max(0, Math.min(v, len))\n const relAnchor = createRelativePositionFromTypeIndex(sharedText, clamp(textAnchor))\n const relHead = createRelativePositionFromTypeIndex(sharedText, clamp(textHead))\n awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead })\n}\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/** Options for {@link createCursorSyncPlugin}. */\nexport type CursorSyncPluginOptions = {\n awareness: Awareness\n serialize: Serialize\n cursorFieldName?: string\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n locate?: LocateText\n /**\n * When provided, the plugin also broadcasts CM-format cursor positions\n * (Y.Text relative positions) to the awareness field specified by\n * `cmCursorFieldName`, so that remote `yCollab` instances can render the cursor.\n */\n sharedText?: YText\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * ProseMirror plugin that synchronizes cursor positions between PM and CM via Yjs awareness.\n *\n * - PM → awareness: automatically broadcasts when the PM view is focused and selection changes.\n * - CM → awareness: triggered by dispatching {@link syncCmCursor}.\n */\nexport function createCursorSyncPlugin(options: CursorSyncPluginOptions): Plugin {\n const { awareness, serialize, locate, sharedText } = options\n const warn = options.onWarning ?? defaultOnWarning\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const cmCursorFieldName = options.cmCursorFieldName ?? 'cursor'\n\n let warnedSyncPluginMissing = false\n\n // Cached cursor map (serialize-based) — rebuilt only when doc changes\n let cachedMap: CursorMap | null = null\n let cachedMapDoc: Node | null = null\n\n function getOrBuildMap(doc: Node): CursorMap {\n if (cachedMapDoc !== doc || !cachedMap) {\n cachedMap = buildCursorMap(doc, serialize, locate)\n cachedMapDoc = doc\n }\n return cachedMap\n }\n\n return new Plugin<CursorSyncState>({\n key: cursorSyncPluginKey,\n\n state: {\n init(): CursorSyncState {\n return { pendingCm: null, mappedTextOffset: null }\n },\n apply(tr, prev, _oldState, newState): CursorSyncState {\n const cmMeta = tr.getMeta(cursorSyncPluginKey) as\n | { anchor: number; head: number }\n | undefined\n if (cmMeta) {\n return { pendingCm: cmMeta, mappedTextOffset: prev.mappedTextOffset }\n }\n\n // Compute PM → text offset when selection or doc changes\n let mappedTextOffset = prev.mappedTextOffset\n if (tr.selectionSet || tr.docChanged) {\n const map = getOrBuildMap(newState.doc)\n mappedTextOffset = cursorMapLookup(map, newState.selection.anchor)\n }\n\n return {\n pendingCm: prev.pendingCm !== null ? null : prev.pendingCm,\n mappedTextOffset,\n }\n },\n },\n\n view() {\n return {\n update(view, prevState) {\n const pluginState = cursorSyncPluginKey.getState(view.state)\n const prevPluginState = cursorSyncPluginKey.getState(prevState)\n\n // CM → awareness: broadcast when pendingCm is newly set\n if (\n pluginState?.pendingCm != null &&\n pluginState.pendingCm !== prevPluginState?.pendingCm\n ) {\n const map = getOrBuildMap(view.state.doc)\n const pmAnchor = reverseCursorMapLookup(map, pluginState.pendingCm.anchor)\n const pmHead = reverseCursorMapLookup(map, pluginState.pendingCm.head)\n if (pmAnchor !== null && pmHead !== null) {\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n }\n // Also broadcast CM-format cursor so remote yCollab can render it\n if (sharedText) {\n broadcastTextCursor(\n awareness,\n cmCursorFieldName,\n sharedText,\n pluginState.pendingCm.anchor,\n pluginState.pendingCm.head,\n )\n }\n return\n }\n\n // PM → awareness: auto-broadcast on selection/doc change when focused\n if (\n view.hasFocus() &&\n (view.state.selection !== prevState.selection ||\n view.state.doc !== prevState.doc)\n ) {\n const { anchor, head } = view.state.selection\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, anchor, head)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n // Also broadcast CM-format cursor so remote yCollab can render it.\n // When bridgeSyncPlugin runs before this plugin, Y.Text is already\n // synced so serialize-based offsets match Y.Text indices.\n if (sharedText) {\n const map = getOrBuildMap(view.state.doc)\n const textAnchor = cursorMapLookup(map, anchor)\n const textHead = cursorMapLookup(map, head)\n if (textAnchor !== null && textHead !== null) {\n broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead)\n }\n }\n }\n },\n }\n },\n })\n}\n\n/**\n * Dispatch a CodeMirror cursor offset (or range) to the cursor sync plugin.\n * The plugin will convert it to a ProseMirror position and broadcast via awareness.\n *\n * @param view - The ProseMirror EditorView that has the cursor sync plugin installed.\n * @param anchor - CodeMirror text offset for the anchor.\n * @param head - CodeMirror text offset for the head (defaults to `anchor` for a collapsed cursor).\n * @param onWarning - Optional warning callback. Default `console.warn`.\n */\nexport function syncCmCursor(view: EditorView, anchor: number, head?: number, onWarning?: OnWarning): void {\n if (!cursorSyncPluginKey.getState(view.state)) {\n (onWarning ?? defaultOnWarning)({ code: 'cursor-sync-not-installed', message: 'cursor sync plugin is not installed on this EditorView' })\n return\n }\n const sanitize = (v: number) => Math.max(0, Math.floor(v))\n view.dispatch(\n view.state.tr.setMeta(cursorSyncPluginKey, {\n anchor: sanitize(anchor),\n head: sanitize(head ?? anchor),\n }),\n )\n}\n"],"mappings":"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","names":["doc","value","import_y_prosemirror","import_prosemirror_state","import_y_prosemirror","defaultOnWarning","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/bridge.ts","../src/types.ts","../src/awareness-proxy.ts","../src/collab-plugins.ts","../src/bridge-sync-plugin.ts","../src/cursor-sync-plugin.ts"],"sourcesContent":["export { createYjsBridge, replaceSharedText, replaceSharedProseMirror } from './bridge.js'\nexport type { YjsBridgeOptions, ReplaceResult, ReplaceTextResult, ReplaceProseMirrorResult } from './bridge.js'\nexport { createAwarenessProxy } from './awareness-proxy.js'\nexport { createCollabPlugins } from './collab-plugins.js'\nexport type { CollabPluginsOptions, ProseMirrorMapping, YCursorPluginOpts, YUndoPluginOpts } from './collab-plugins.js'\nexport {\n ORIGIN_TEXT_TO_PM,\n ORIGIN_PM_TO_TEXT,\n ORIGIN_INIT,\n} from './types.js'\nexport type {\n BootstrapResult,\n YjsBridgeConfig,\n YjsBridgeHandle,\n WarningCode,\n WarningEvent,\n OnWarning,\n} from './types.js'\n\n// Cursor mapping re-exported from @pm-cm/core\nexport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nexport type { TextSegment, CursorMap, SerializeWithMap, CursorMapWriter, Matcher, MatchResult, MatchRun } from '@pm-cm/core'\n\n// Bridge sync plugin (auto PM→Y.Text wiring)\nexport { createBridgeSyncPlugin, bridgeSyncPluginKey } from './bridge-sync-plugin.js'\nexport type { BridgeSyncPluginOptions } from './bridge-sync-plugin.js'\n\n// Cursor sync plugin\nexport { createCursorSyncPlugin, cursorSyncPluginKey, syncCmCursor } from './cursor-sync-plugin.js'\nexport type { CursorSyncState, CursorSyncPluginOptions } from './cursor-sync-plugin.js'\n\n// Re-export types from @pm-cm/core\nexport { createCursorMapWriter, wrapSerialize } from '@pm-cm/core'\nexport type { Serialize, Parse, Normalize, OnError, ErrorCode, ErrorEvent } from '@pm-cm/core'\n","import type { Node } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport { prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, ySyncPluginKey } from 'y-prosemirror'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport { XmlElement as YXmlElement, XmlText as YXmlText } from 'yjs'\nimport type { Normalize, OnError } from '@pm-cm/core'\nimport type { BootstrapResult, YjsBridgeConfig, YjsBridgeHandle } from './types.js'\nimport { ORIGIN_INIT, ORIGIN_TEXT_TO_PM, ORIGIN_PM_TO_TEXT } from './types.js'\n\nconst defaultNormalize: Normalize = (s) => s.replace(/\\r\\n?/g, '\\n')\nconst defaultOnError: OnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause)\n\n/** Result of {@link replaceSharedText}. */\nexport type ReplaceTextResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' }\n | { ok: false; reason: 'detached' }\n\n/** Result of {@link replaceSharedProseMirror}. */\nexport type ReplaceProseMirrorResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' }\n | { ok: false; reason: 'parse-error' }\n | { ok: false; reason: 'detached' }\n\n/**\n * Union of all replace-result types. Kept for backward compatibility.\n * Prefer the narrower {@link ReplaceTextResult} / {@link ReplaceProseMirrorResult}.\n */\nexport type ReplaceResult = ReplaceTextResult | ReplaceProseMirrorResult\n\n/**\n * Replace `Y.Text` content using a minimal diff (common prefix/suffix trimming).\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedText(\n sharedText: YText,\n next: string,\n origin: unknown,\n normalize: Normalize = defaultNormalize,\n): ReplaceTextResult {\n if (!sharedText.doc) {\n return { ok: false, reason: 'detached' }\n }\n\n const normalized = normalize(next)\n const current = sharedText.toString()\n if (current === normalized) {\n return { ok: false, reason: 'unchanged' }\n }\n\n // Minimal diff: find common prefix and suffix, replace only the changed middle.\n let start = 0\n const minLen = Math.min(current.length, normalized.length)\n while (start < minLen && current.charCodeAt(start) === normalized.charCodeAt(start)) {\n start++\n }\n\n let endCurrent = current.length\n let endNext = normalized.length\n while (endCurrent > start && endNext > start && current.charCodeAt(endCurrent - 1) === normalized.charCodeAt(endNext - 1)) {\n endCurrent--\n endNext--\n }\n\n sharedText.doc.transact(() => {\n const deleteCount = endCurrent - start\n if (deleteCount > 0) {\n sharedText.delete(start, deleteCount)\n }\n const insertStr = normalized.slice(start, endNext)\n if (insertStr.length > 0) {\n sharedText.insert(start, insertStr)\n }\n }, origin)\n\n return { ok: true }\n}\n\n/**\n * Replace `Y.XmlFragment` by parsing serialized text into a ProseMirror document.\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedProseMirror(\n doc: Doc,\n fragment: YXmlFragment,\n text: string,\n origin: unknown,\n config: Pick<YjsBridgeConfig, 'schema' | 'parse' | 'normalize' | 'onError'>,\n): ReplaceProseMirrorResult {\n if (!fragment.doc) {\n return { ok: false, reason: 'detached' }\n }\n if (fragment.doc !== doc) {\n throw new Error('fragment belongs to a different Y.Doc than the provided doc')\n }\n\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n let nextDoc: Node\n try {\n nextDoc = config.parse(normalize(text), config.schema)\n } catch (error) {\n onError({ code: 'parse-error', message: 'failed to parse text into ProseMirror document', cause: error })\n return { ok: false, reason: 'parse-error' }\n }\n\n // Semantic equality check: skip if PM document is unchanged\n try {\n const currentDoc = yXmlFragmentToProseMirrorRootNode(fragment, config.schema)\n if (currentDoc.eq(nextDoc)) {\n return { ok: false, reason: 'unchanged' }\n }\n } catch {\n // If conversion fails, proceed with replacement\n }\n\n // Try incremental update first; fall back to full replacement on failure\n if (!tryIncrementalFragmentUpdate(doc, fragment, nextDoc, origin)) {\n doc.transact(() => {\n prosemirrorToYXmlFragment(nextDoc, fragment)\n }, origin)\n }\n return { ok: true }\n}\n\n/**\n * Convert a ProseMirror node to a new (unattached) Y.XmlElement.\n * Handles attributes and text content including marks.\n */\nfunction pmNodeToYXmlElement(node: Node): YXmlElement {\n const xmlEl = new YXmlElement(node.type.name)\n for (const [key, value] of Object.entries(node.attrs)) {\n if (value != null) xmlEl.setAttribute(key, value)\n }\n // Collect children first, then insert all at once to avoid\n // reading .length on unattached Yjs types\n const children: (YXmlElement | YXmlText)[] = []\n node.forEach((child) => {\n if (child.isText && child.text) {\n const xmlText = new YXmlText()\n const attrs: Record<string, unknown> = {}\n for (const mark of child.marks) {\n attrs[mark.type.name] = mark.attrs && Object.keys(mark.attrs).length > 0 ? mark.attrs : true\n }\n xmlText.insert(0, child.text, Object.keys(attrs).length > 0 ? attrs : undefined)\n children.push(xmlText)\n } else if (!child.isLeaf) {\n children.push(pmNodeToYXmlElement(child))\n }\n })\n if (children.length > 0) {\n xmlEl.insert(0, children)\n }\n return xmlEl\n}\n\n/** Get text content of a Y.XmlElement's children. */\nfunction getXmlElementText(xmlEl: YXmlElement): string {\n let text = ''\n for (let j = 0; j < xmlEl.length; j++) {\n const child = xmlEl.get(j)\n if (child instanceof YXmlText) text += child.toString()\n }\n return text\n}\n\n/**\n * Update a Y.XmlElement's text content in-place using minimal diff.\n * Preserves the XmlText identity when possible.\n */\nfunction updateXmlElementTextContent(xmlEl: YXmlElement, pmNode: Node): void {\n if (xmlEl.length === 0 || !(xmlEl.get(0) instanceof YXmlText)) return\n const xmlText = xmlEl.get(0) as YXmlText\n const oldText = xmlText.toString()\n const newText = pmNode.textContent\n if (oldText === newText) return\n\n // Minimal diff: find common prefix and suffix, replace only the changed middle\n let start = 0\n const minLen = Math.min(oldText.length, newText.length)\n while (start < minLen && oldText.charCodeAt(start) === newText.charCodeAt(start)) {\n start++\n }\n\n let oldEnd = oldText.length\n let newEnd = newText.length\n while (oldEnd > start && newEnd > start && oldText.charCodeAt(oldEnd - 1) === newText.charCodeAt(newEnd - 1)) {\n oldEnd--\n newEnd--\n }\n\n if (oldEnd > start) xmlText.delete(start, oldEnd - start)\n if (newEnd > start) xmlText.insert(start, newText.slice(start, newEnd))\n}\n\n/**\n * Try to update a Y.XmlFragment incrementally by diffing against a new PM document.\n * Uses prefix/suffix matching to preserve unchanged Yjs nodes, protecting\n * relative positions (remote cursors) and undo history.\n *\n * Returns `true` if the incremental update succeeded, `false` to fall back to full replacement.\n */\nfunction tryIncrementalFragmentUpdate(\n ydoc: Doc,\n fragment: YXmlFragment,\n nextDoc: Node,\n origin: unknown,\n): boolean {\n const oldCount = fragment.length\n const newCount = nextDoc.childCount\n\n // Validate all old children are XmlElements (expected from y-prosemirror)\n for (let i = 0; i < oldCount; i++) {\n if (!(fragment.get(i) instanceof YXmlElement)) return false\n }\n\n // Find common prefix: same node type AND same text content\n let prefix = 0\n while (prefix < Math.min(oldCount, newCount)) {\n const oldChild = fragment.get(prefix) as YXmlElement\n const newChild = nextDoc.child(prefix)\n if (oldChild.nodeName !== newChild.type.name) break\n if (getXmlElementText(oldChild) !== newChild.textContent) break\n prefix++\n }\n\n // Find common suffix (avoid overlapping with prefix)\n let suffix = 0\n while (suffix < Math.min(oldCount - prefix, newCount - prefix)) {\n const oi = oldCount - 1 - suffix\n const ni = newCount - 1 - suffix\n const oldChild = fragment.get(oi) as YXmlElement\n const newChild = nextDoc.child(ni)\n if (oldChild.nodeName !== newChild.type.name) break\n if (getXmlElementText(oldChild) !== newChild.textContent) break\n suffix++\n }\n\n const oldMiddleLen = oldCount - prefix - suffix\n const newMiddleLen = newCount - prefix - suffix\n\n // Nothing changed in the middle\n if (oldMiddleLen === 0 && newMiddleLen === 0) return true\n\n ydoc.transact(() => {\n // For overlapping middle items with matching types, update text in-place\n const updateCount = Math.min(oldMiddleLen, newMiddleLen)\n for (let i = 0; i < updateCount; i++) {\n const idx = prefix + i\n const xmlEl = fragment.get(idx) as YXmlElement\n const pmNode = nextDoc.child(idx)\n if (xmlEl.nodeName === pmNode.type.name) {\n updateXmlElementTextContent(xmlEl, pmNode)\n } else {\n // Type changed: delete and insert replacement\n fragment.delete(idx, 1)\n fragment.insert(idx, [pmNodeToYXmlElement(pmNode)])\n }\n }\n\n // Delete extra old items beyond what was updated in-place\n if (oldMiddleLen > updateCount) {\n fragment.delete(prefix + updateCount, oldMiddleLen - updateCount)\n }\n\n // Insert new items beyond what was updated in-place\n for (let i = updateCount; i < newMiddleLen; i++) {\n fragment.insert(prefix + i, [pmNodeToYXmlElement(nextDoc.child(prefix + i))])\n }\n }, origin)\n\n return true\n}\n\n/** Options for {@link createYjsBridge}. */\nexport type YjsBridgeOptions = {\n initialText?: string\n /** Which side wins when both sharedText and sharedProseMirror exist and differ. Default `'text'`. */\n prefer?: 'text' | 'prosemirror'\n}\n\n/**\n * Create a collaborative bridge that keeps `Y.Text` and `Y.XmlFragment` in sync.\n *\n * Runs a synchronous bootstrap to reconcile existing state, then installs a\n * `Y.Text` observer for the text → ProseMirror direction.\n *\n * @throws If `sharedText` or `sharedProseMirror` belong to a different `Y.Doc`.\n */\nexport function createYjsBridge(\n config: YjsBridgeConfig,\n options?: YjsBridgeOptions,\n): YjsBridgeHandle {\n const {\n doc,\n sharedText,\n sharedProseMirror,\n schema,\n serialize,\n parse,\n } = config\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n\n if (!sharedText.doc) {\n throw new Error('sharedText is not attached to any Y.Doc')\n }\n if (sharedText.doc !== doc) {\n throw new Error('sharedText belongs to a different Y.Doc than the provided doc')\n }\n if (!sharedProseMirror.doc) {\n throw new Error('sharedProseMirror is not attached to any Y.Doc')\n }\n if (sharedProseMirror.doc !== doc) {\n throw new Error('sharedProseMirror belongs to a different Y.Doc than the provided doc')\n }\n\n let lastBridgedText: string | null = null\n\n /** Returns `true` if the parse succeeded. */\n const syncTextToProsemirror = (origin: unknown): boolean => {\n const text = normalize(sharedText.toString())\n if (lastBridgedText === text) {\n return true\n }\n\n const result = replaceSharedProseMirror(doc, sharedProseMirror, text, origin, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (result.ok || result.reason === 'unchanged') {\n lastBridgedText = text\n }\n return result.ok || result.reason === 'unchanged'\n }\n\n const sharedProseMirrorToText = (fragment: YXmlFragment): string | null => {\n try {\n const pmDoc = yXmlFragmentToProseMirrorRootNode(fragment, schema)\n return normalize(serialize(pmDoc))\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to convert ProseMirror fragment to text', cause: error })\n return null\n }\n }\n\n // Bootstrap\n const bootstrap = (): BootstrapResult => {\n const text = normalize(sharedText.toString())\n const hasText = text.length > 0\n const hasProsemirror = sharedProseMirror.length > 0\n\n if (!hasText && !hasProsemirror) {\n const initial = options?.initialText ?? ''\n if (initial.length > 0) {\n // Set Y.XmlFragment first, then derive Y.Text from serialize(parse(initial))\n // to ensure both shared types are in the same canonical form.\n const initResult = replaceSharedProseMirror(doc, sharedProseMirror, initial, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!initResult.ok) {\n return { source: 'initial', parseError: true }\n }\n const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema)\n const canonicalText = serialize(pmDoc)\n replaceSharedText(sharedText, canonicalText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(canonicalText)\n return { source: 'initial' }\n }\n return { source: 'empty' }\n }\n\n if (hasText && !hasProsemirror) {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n\n if (!hasText && hasProsemirror) {\n const textFromProsemirror = sharedProseMirrorToText(sharedProseMirror)\n if (textFromProsemirror !== null) {\n replaceSharedText(sharedText, textFromProsemirror, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(textFromProsemirror)\n return { source: 'prosemirror' }\n }\n return { source: 'prosemirror', parseError: true }\n }\n\n const prosemirrorText = sharedProseMirrorToText(sharedProseMirror)\n if (prosemirrorText === null) {\n const fallbackText = hasText ? text : (options?.initialText ?? '')\n let parseError = false\n if (fallbackText.length > 0) {\n replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize)\n const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!fallbackResult.ok) parseError = true\n }\n return { source: 'text', ...(parseError && { parseError: true }) }\n }\n\n if (prosemirrorText !== text) {\n const prefer = options?.prefer ?? 'text'\n if (prefer === 'prosemirror') {\n replaceSharedText(sharedText, prosemirrorText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(prosemirrorText)\n return { source: 'prosemirror' }\n } else {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n } else {\n lastBridgedText = text\n return { source: 'both-match' }\n }\n }\n\n const textObserver = (\n _: unknown,\n transaction: { origin: unknown },\n ) => {\n if (transaction.origin === ORIGIN_PM_TO_TEXT || transaction.origin === ORIGIN_INIT) {\n return\n }\n\n syncTextToProsemirror(ORIGIN_TEXT_TO_PM)\n }\n\n // Run bootstrap synchronously before installing the observer so that\n // an exception during bootstrap cannot leave a dangling observer.\n const bootstrapResult = bootstrap()\n\n sharedText.observe(textObserver)\n\n return {\n bootstrapResult,\n syncToSharedText(doc: Node): ReplaceTextResult {\n const text = serialize(doc)\n const result = replaceSharedText(sharedText, text, ORIGIN_PM_TO_TEXT, normalize)\n // Always update lastBridgedText unless truly failed (detached).\n // 'unchanged' means Y.Text already has this content — still need to\n // record it so the reverse observer doesn't trigger a redundant sync.\n if (result.ok || result.reason === 'unchanged') {\n lastBridgedText = normalize(text)\n }\n return result\n },\n isYjsSyncChange(tr: Transaction): boolean {\n // Internal meta shape from y-prosemirror's ySyncPlugin (tested against ^1.3.x).\n const meta = tr.getMeta(ySyncPluginKey)\n return (\n typeof meta === 'object' &&\n meta !== null &&\n 'isChangeOrigin' in meta &&\n (meta as Record<string, unknown>).isChangeOrigin === true\n )\n },\n dispose() {\n sharedText.unobserve(textObserver)\n },\n }\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { Serialize, Parse, Normalize, OnError } from '@pm-cm/core'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { ReplaceTextResult } from './bridge.js'\n\n/** Known warning codes emitted by the yjs bridge and plugins. */\nexport type WarningCode = 'bridge-already-wired' | 'sync-failed' | 'ysync-plugin-missing' | 'cursor-sync-not-installed'\n\n/** Structured warning event for non-fatal warnings. */\nexport type WarningEvent = {\n code: WarningCode\n message: string\n}\n\n/**\n * Warning handler callback for non-fatal warnings.\n *\n * Known codes:\n * - `'bridge-already-wired'` — the same bridge handle is wired to multiple plugin instances.\n * - `'sync-failed'` — `syncToSharedText` failed (e.g. Y.Text detached).\n * - `'ysync-plugin-missing'` — ySyncPlugin state is not available; cursor broadcast skipped.\n * - `'cursor-sync-not-installed'` — cursor sync plugin is not installed on the EditorView.\n */\nexport type OnWarning = (event: WarningEvent) => void\n\n/** Yjs transaction origin: text → ProseMirror direction. */\nexport const ORIGIN_TEXT_TO_PM = 'bridge:text-to-prosemirror'\n\n/** Yjs transaction origin: ProseMirror → text direction. */\nexport const ORIGIN_PM_TO_TEXT = 'bridge:prosemirror-to-text'\n\n/** Yjs transaction origin: bootstrap initialization. */\nexport const ORIGIN_INIT = 'bridge:init'\n\n/** Configuration for {@link createYjsBridge}. */\nexport type YjsBridgeConfig = {\n doc: Doc\n sharedText: YText\n sharedProseMirror: YXmlFragment\n schema: Schema\n serialize: Serialize\n parse: Parse\n normalize?: Normalize\n /** Called on non-fatal errors (e.g. parse failures). Defaults to `console.error`. */\n onError?: OnError\n}\n\n/**\n * Result of the bootstrap phase in {@link createYjsBridge}.\n * Indicates which source was used to initialize the shared types.\n */\nexport type BootstrapResult = {\n source: 'text' | 'prosemirror' | 'both-match' | 'empty' | 'initial'\n /** `true` when format conversion (parse or serialize) failed during bootstrap. The bridge is still usable but the affected shared type may be stale. */\n parseError?: boolean\n}\n\n/** Handle returned by {@link createYjsBridge}. */\nexport type YjsBridgeHandle = {\n /** Result of the synchronous bootstrap phase. */\n readonly bootstrapResult: BootstrapResult\n /** Serialize `doc` and push to `Y.Text` using minimal diff. */\n syncToSharedText(doc: Node): ReplaceTextResult\n /** Returns `true` if the transaction originated from `y-prosemirror` sync. */\n isYjsSyncChange(tr: Transaction): boolean\n /** Remove the Y.Text observer. Call when tearing down. */\n dispose(): void\n}\n","import type { Awareness } from 'y-protocols/awareness'\n\n/**\n * Create a Proxy around a Yjs {@link Awareness} that adapts the cursor sync\n * plugin's awareness field for y-prosemirror's `yCursorPlugin`.\n *\n * y-prosemirror (npm 1.3.7) hardcodes `\"cursor\"` in `createDecorations`,\n * but the cursor sync plugin writes to a separate field (default `\"pmCursor\"`)\n * to avoid conflicts with y-codemirror.next's `\"cursor\"` (Y.Text-based).\n *\n * This proxy:\n * - **`getStates()`**: remaps `cursorField` → `\"cursor\"` so yCursorPlugin\n * finds the PM cursor data under the hardcoded `\"cursor\"` key.\n * - **`getLocalState()`**: returns `cursor: null` so yCursorPlugin's\n * `updateCursorInfo` never tries to broadcast its own cursor.\n * - **`setLocalStateField(\"cursor\", …)`**: suppressed (no-op) so\n * yCursorPlugin cannot overwrite the field managed by the sync plugin.\n */\nexport function createAwarenessProxy(awareness: Awareness, cursorField = 'pmCursor'): Awareness {\n return new Proxy(awareness, {\n get(target, prop, receiver) {\n if (prop === 'getLocalState') {\n return () => {\n const state = target.getLocalState()\n // Hide \"cursor\" so yCursorPlugin's updateCursorInfo sees null\n return state ? { ...state, cursor: null } : state\n }\n }\n if (prop === 'setLocalStateField') {\n return (field: string, value: unknown) => {\n // Block yCursorPlugin's writes to \"cursor\"\n if (field === 'cursor') return\n target.setLocalStateField(field, value)\n }\n }\n if (prop === 'getStates') {\n return () => {\n const states = target.getStates()\n // Remap cursorField → \"cursor\" so yCursorPlugin reads PM cursor data\n const remapped = new Map<number, Record<string, unknown>>()\n states.forEach((state, clientId) => {\n remapped.set(clientId, {\n ...state as Record<string, unknown>,\n cursor: (state as Record<string, unknown>)[cursorField] ?? null,\n })\n })\n return remapped\n }\n }\n const value = Reflect.get(target, prop, receiver) as unknown\n return typeof value === 'function' ? (value as Function).bind(target) : value\n },\n }) as Awareness\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { EditorState, Plugin } from 'prosemirror-state'\nimport type { DecorationAttrs } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport type { Serialize, SerializeWithMap } from '@pm-cm/core'\nimport { initProseMirrorDoc, yCursorPlugin, ySyncPlugin, yUndoPlugin } from 'y-prosemirror'\nimport type { AbstractType, Text as YText, UndoManager } from 'yjs'\nimport type { XmlFragment as YXmlFragment } from 'yjs'\nimport { createAwarenessProxy } from './awareness-proxy.js'\nimport { createBridgeSyncPlugin } from './bridge-sync-plugin.js'\nimport { createCursorSyncPlugin } from './cursor-sync-plugin.js'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\n/** Yjs ↔ ProseMirror node mapping used by `y-prosemirror`. */\nexport type ProseMirrorMapping = Map<AbstractType<unknown>, Node | Node[]>\n\n/** Options forwarded to `yCursorPlugin` from y-prosemirror. */\nexport type YCursorPluginOpts = {\n awarenessStateFilter?: (currentClientId: number, userClientId: number, user: unknown) => boolean\n cursorBuilder?: (user: unknown, clientId: number) => HTMLElement\n selectionBuilder?: (user: unknown, clientId: number) => DecorationAttrs\n getSelection?: (state: EditorState) => unknown\n}\n\n/** Options forwarded to `yUndoPlugin` from y-prosemirror. */\nexport type YUndoPluginOpts = {\n protectedNodes?: Set<string>\n trackedOrigins?: unknown[]\n undoManager?: UndoManager | null\n}\n\n/** Options for {@link createCollabPlugins}. */\nexport type CollabPluginsOptions = {\n /** Shared ProseMirror document in Yjs. */\n sharedProseMirror: YXmlFragment\n awareness: Awareness\n cursorFieldName?: string\n serialize?: Serialize | SerializeWithMap\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n /**\n * Enable PM↔CM cursor sync. Default `false`.\n *\n * When enabled, an {@link createAwarenessProxy | awareness proxy} is applied\n * to suppress y-prosemirror's built-in cursor management.\n */\n cursorSync?: boolean\n /**\n * The shared `Y.Text` instance. When provided, the cursor sync plugin also\n * broadcasts CM-format cursor positions so remote `yCollab` instances render them.\n */\n sharedText?: YText\n /**\n * When provided, a bridge sync plugin is inserted before the cursor sync plugin\n * to ensure Y.Text is synced before cursor positions are computed. This guarantees\n * that serialize-based offsets match Y.Text indices.\n */\n bridge?: YjsBridgeHandle\n /** Extra options forwarded to `yCursorPlugin`. */\n yCursorPluginOpts?: YCursorPluginOpts\n /** Extra options forwarded to `yUndoPlugin`. */\n yUndoPluginOpts?: YUndoPluginOpts\n /** Called for non-fatal warnings. Propagated to child plugins. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * Bundle `ySyncPlugin`, `yCursorPlugin`, `yUndoPlugin` from y-prosemirror,\n * plus an optional PM↔CM cursor sync plugin.\n *\n * @throws If `cursorSync: true` but `serialize` is not provided.\n */\nexport function createCollabPlugins(\n schema: Schema,\n options: CollabPluginsOptions,\n): { plugins: Plugin[]; doc: Node; mapping: ProseMirrorMapping } {\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const enableCursorSync = options.cursorSync ?? false\n const { sharedProseMirror } = options\n\n if (enableCursorSync && !options.serialize) {\n throw new Error('createCollabPlugins: cursorSync requires serialize to be provided')\n }\n const { doc, mapping: rawMapping } = initProseMirrorDoc(sharedProseMirror, schema)\n const mapping = rawMapping as ProseMirrorMapping\n const pmAwareness = enableCursorSync\n ? createAwarenessProxy(options.awareness, cursorFieldName)\n : options.awareness\n\n const plugins: Plugin[] = [\n ySyncPlugin(sharedProseMirror, { mapping: rawMapping }),\n yCursorPlugin(pmAwareness, options.yCursorPluginOpts ?? {}),\n yUndoPlugin(options.yUndoPluginOpts),\n ]\n\n // Bridge sync plugin must run before cursor sync plugin so that\n // Y.Text is updated before cursor positions are computed.\n if (options.bridge) {\n plugins.push(createBridgeSyncPlugin(options.bridge, { onWarning: options.onWarning }))\n }\n\n if (enableCursorSync && options.serialize) {\n plugins.push(\n createCursorSyncPlugin({\n awareness: options.awareness,\n serialize: options.serialize,\n cursorFieldName,\n cmCursorFieldName: options.cmCursorFieldName,\n sharedText: options.sharedText,\n onWarning: options.onWarning,\n }),\n )\n }\n\n return { plugins, doc, mapping }\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\ntype BridgeSyncState = { needsSync: boolean }\n\ntype BridgeSyncFailure = { ok: false; reason: 'detached' }\n\n/** Options for {@link createBridgeSyncPlugin}. */\nexport type BridgeSyncPluginOptions = {\n /** Called when `syncToSharedText` fails (excludes `reason: 'unchanged'`). */\n onSyncFailure?: (result: BridgeSyncFailure, view: EditorView) => void\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/** ProseMirror plugin key for {@link createBridgeSyncPlugin}. Use to read the plugin state. */\nexport const bridgeSyncPluginKey = new PluginKey<BridgeSyncState>('pm-cm-bridge-sync')\n\nconst wiredBridges = new WeakSet<YjsBridgeHandle>()\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/**\n * ProseMirror plugin that automatically syncs PM doc changes to Y.Text\n * via the bridge handle. Skips Yjs-originated changes to avoid loops.\n *\n * A warning is logged if the same bridge handle is wired more than once.\n * The guard is cleaned up when the plugin is destroyed.\n */\nexport function createBridgeSyncPlugin(\n bridge: YjsBridgeHandle,\n options: BridgeSyncPluginOptions = {},\n): Plugin {\n const warn = options.onWarning ?? defaultOnWarning\n if (wiredBridges.has(bridge)) {\n warn({ code: 'bridge-already-wired', message: 'this bridge is already wired to another plugin instance' })\n }\n wiredBridges.add(bridge)\n\n return new Plugin<BridgeSyncState>({\n key: bridgeSyncPluginKey,\n\n state: {\n init(): BridgeSyncState {\n return { needsSync: false }\n },\n apply(tr, _prev): BridgeSyncState {\n if (!tr.docChanged) return { needsSync: false }\n if (bridge.isYjsSyncChange(tr)) return { needsSync: false }\n return { needsSync: true }\n },\n },\n\n view() {\n return {\n update(view) {\n const state = bridgeSyncPluginKey.getState(view.state)\n if (state?.needsSync) {\n const result = bridge.syncToSharedText(view.state.doc)\n if (!result.ok) {\n if (result.reason === 'detached') {\n options.onSyncFailure?.(result, view)\n warn({ code: 'sync-failed', message: `bridge sync failed: ${result.reason}` })\n }\n }\n }\n },\n destroy() {\n wiredBridges.delete(bridge)\n },\n }\n },\n })\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { Node } from 'prosemirror-model'\nimport type { EditorView } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport { absolutePositionToRelativePosition, ySyncPluginKey } from 'y-prosemirror'\nimport { createRelativePositionFromTypeIndex } from 'yjs'\nimport type { Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { Serialize, SerializeWithMap, CursorMap } from '@pm-cm/core'\nimport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nimport type { OnWarning } from './types.js'\n\n/** Plugin state for the cursor sync plugin. Read via {@link cursorSyncPluginKey}. */\nexport type CursorSyncState = {\n /** Pending CodeMirror cursor to broadcast. Set by {@link syncCmCursor}. */\n pendingCm: { anchor: number; head: number } | null\n /** Text offset mapped from the current PM selection anchor. `null` when no mapping is available. */\n mappedTextOffset: number | null\n}\n\n/** ProseMirror plugin key for {@link createCursorSyncPlugin}. Use to read the plugin state. */\nexport const cursorSyncPluginKey = new PluginKey<CursorSyncState>('pm-cm-cursor-sync')\n\n/**\n * Internal shape of `ySyncPluginKey` state from y-prosemirror.\n * Not exported by upstream — kept here for explicit tracking.\n * Tested against y-prosemirror ^1.3.x.\n */\ntype YSyncPluginState = { type: YXmlFragment; binding: { mapping: Map<unknown, unknown> } }\n\nfunction getYSyncState(view: EditorView): YSyncPluginState | null {\n const raw = ySyncPluginKey.getState(view.state) as Record<string, unknown> | undefined\n if (!raw) return null\n if (\n typeof raw === 'object' &&\n 'type' in raw && raw.type &&\n 'binding' in raw && raw.binding &&\n typeof raw.binding === 'object' &&\n 'mapping' in (raw.binding as Record<string, unknown>) &&\n (raw.binding as Record<string, unknown>).mapping instanceof Map\n ) {\n return raw as unknown as YSyncPluginState\n }\n return null\n}\n\nfunction toRelativePosition(\n view: EditorView,\n pmPos: number,\n): unknown | null {\n const ySyncState = getYSyncState(view)\n if (!ySyncState) return null\n\n return absolutePositionToRelativePosition(\n pmPos,\n ySyncState.type,\n ySyncState.binding.mapping as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- y-prosemirror internal mapping type\n )\n}\n\n/** Returns `false` when ySyncPlugin state is unavailable (plugin not installed). */\nfunction broadcastPmCursor(\n awareness: Awareness,\n cursorFieldName: string,\n view: EditorView,\n pmAnchor: number,\n pmHead: number,\n): boolean {\n const relAnchor = toRelativePosition(view, pmAnchor)\n const relHead = toRelativePosition(view, pmHead)\n if (relAnchor === null || relHead === null) return false\n\n awareness.setLocalStateField(cursorFieldName, { anchor: relAnchor, head: relHead })\n return true\n}\n\nfunction broadcastTextCursor(\n awareness: Awareness,\n cmCursorFieldName: string,\n sharedText: YText,\n textAnchor: number,\n textHead: number,\n): void {\n const len = sharedText.length\n const clamp = (v: number) => Math.max(0, Math.min(v, len))\n const relAnchor = createRelativePositionFromTypeIndex(sharedText, clamp(textAnchor))\n const relHead = createRelativePositionFromTypeIndex(sharedText, clamp(textHead))\n awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead })\n}\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/** Options for {@link createCursorSyncPlugin}. */\nexport type CursorSyncPluginOptions = {\n awareness: Awareness\n serialize: Serialize | SerializeWithMap\n cursorFieldName?: string\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n /**\n * When provided, the plugin also broadcasts CM-format cursor positions\n * (Y.Text relative positions) to the awareness field specified by\n * `cmCursorFieldName`, so that remote `yCollab` instances can render the cursor.\n */\n sharedText?: YText\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * ProseMirror plugin that synchronizes cursor positions between PM and CM via Yjs awareness.\n *\n * - PM → awareness: automatically broadcasts when the PM view is focused and selection changes.\n * - CM → awareness: triggered by dispatching {@link syncCmCursor}.\n */\nexport function createCursorSyncPlugin(options: CursorSyncPluginOptions): Plugin {\n const { awareness, serialize, sharedText } = options\n const warn = options.onWarning ?? defaultOnWarning\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const cmCursorFieldName = options.cmCursorFieldName ?? 'cursor'\n\n let warnedSyncPluginMissing = false\n\n // Cached cursor map (serialize-based) — rebuilt only when doc changes\n let cachedMap: CursorMap | null = null\n let cachedMapDoc: Node | null = null\n\n function getOrBuildMap(doc: Node): CursorMap {\n if (cachedMapDoc !== doc || !cachedMap) {\n cachedMap = buildCursorMap(doc, serialize)\n cachedMapDoc = doc\n }\n return cachedMap\n }\n\n return new Plugin<CursorSyncState>({\n key: cursorSyncPluginKey,\n\n state: {\n init(): CursorSyncState {\n return { pendingCm: null, mappedTextOffset: null }\n },\n apply(tr, prev, _oldState, newState): CursorSyncState {\n const cmMeta = tr.getMeta(cursorSyncPluginKey) as\n | { anchor: number; head: number }\n | undefined\n if (cmMeta) {\n return { pendingCm: cmMeta, mappedTextOffset: prev.mappedTextOffset }\n }\n\n // Compute PM → text offset when selection or doc changes\n let mappedTextOffset = prev.mappedTextOffset\n if (tr.selectionSet || tr.docChanged) {\n const map = getOrBuildMap(newState.doc)\n mappedTextOffset = cursorMapLookup(map, newState.selection.anchor)\n }\n\n return {\n pendingCm: prev.pendingCm !== null ? null : prev.pendingCm,\n mappedTextOffset,\n }\n },\n },\n\n view() {\n return {\n update(view, prevState) {\n const pluginState = cursorSyncPluginKey.getState(view.state)\n const prevPluginState = cursorSyncPluginKey.getState(prevState)\n\n // CM → awareness: broadcast when pendingCm is newly set\n if (\n pluginState?.pendingCm != null &&\n pluginState.pendingCm !== prevPluginState?.pendingCm\n ) {\n const map = getOrBuildMap(view.state.doc)\n const pmAnchor = reverseCursorMapLookup(map, pluginState.pendingCm.anchor)\n const pmHead = reverseCursorMapLookup(map, pluginState.pendingCm.head)\n if (pmAnchor !== null && pmHead !== null) {\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n }\n // Also broadcast CM-format cursor so remote yCollab can render it\n if (sharedText) {\n broadcastTextCursor(\n awareness,\n cmCursorFieldName,\n sharedText,\n pluginState.pendingCm.anchor,\n pluginState.pendingCm.head,\n )\n }\n return\n }\n\n // PM → awareness: auto-broadcast on selection/doc change when focused\n if (\n view.hasFocus() &&\n (view.state.selection !== prevState.selection ||\n view.state.doc !== prevState.doc)\n ) {\n const { anchor, head } = view.state.selection\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, anchor, head)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n // Also broadcast CM-format cursor so remote yCollab can render it.\n // When bridgeSyncPlugin runs before this plugin, Y.Text is already\n // synced so serialize-based offsets match Y.Text indices.\n if (sharedText) {\n const map = getOrBuildMap(view.state.doc)\n const textAnchor = cursorMapLookup(map, anchor)\n const textHead = cursorMapLookup(map, head)\n if (textAnchor !== null && textHead !== null) {\n broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead)\n }\n }\n }\n },\n }\n },\n })\n}\n\n/**\n * Dispatch a CodeMirror cursor offset (or range) to the cursor sync plugin.\n * The plugin will convert it to a ProseMirror position and broadcast via awareness.\n *\n * @param view - The ProseMirror EditorView that has the cursor sync plugin installed.\n * @param anchor - CodeMirror text offset for the anchor.\n * @param head - CodeMirror text offset for the head (defaults to `anchor` for a collapsed cursor).\n * @param onWarning - Optional warning callback. Default `console.warn`.\n */\nexport function syncCmCursor(view: EditorView, anchor: number, head?: number, onWarning?: OnWarning): void {\n if (!cursorSyncPluginKey.getState(view.state)) {\n (onWarning ?? defaultOnWarning)({ code: 'cursor-sync-not-installed', message: 'cursor sync plugin is not installed on this EditorView' })\n return\n }\n const sanitize = (v: number) => Math.max(0, Math.floor(v))\n view.dispatch(\n view.state.tr.setMeta(cursorSyncPluginKey, {\n anchor: sanitize(anchor),\n head: sanitize(head ?? anchor),\n }),\n )\n}\n"],"mappings":"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","names":["YXmlElement","YXmlText","doc","value","import_y_prosemirror","import_prosemirror_state","import_y_prosemirror","import_yjs","defaultOnWarning","import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Doc, Text, XmlFragment, UndoManager, AbstractType } from 'yjs';
|
|
2
|
-
import { Serialize, Parse, Normalize, OnError,
|
|
3
|
-
export { CursorMap, ErrorCode, ErrorEvent,
|
|
2
|
+
import { Serialize, Parse, Normalize, OnError, SerializeWithMap } from '@pm-cm/core';
|
|
3
|
+
export { CursorMap, CursorMapWriter, ErrorCode, ErrorEvent, MatchResult, MatchRun, Matcher, Normalize, OnError, Parse, Serialize, SerializeWithMap, TextSegment, buildCursorMap, createCursorMapWriter, cursorMapLookup, reverseCursorMapLookup, wrapSerialize } from '@pm-cm/core';
|
|
4
4
|
import { Node, Schema } from 'prosemirror-model';
|
|
5
5
|
import { Transaction, EditorState, Plugin, PluginKey } from 'prosemirror-state';
|
|
6
6
|
import { Awareness } from 'y-protocols/awareness';
|
|
@@ -75,6 +75,9 @@ type ReplaceTextResult = {
|
|
|
75
75
|
/** Result of {@link replaceSharedProseMirror}. */
|
|
76
76
|
type ReplaceProseMirrorResult = {
|
|
77
77
|
ok: true;
|
|
78
|
+
} | {
|
|
79
|
+
ok: false;
|
|
80
|
+
reason: 'unchanged';
|
|
78
81
|
} | {
|
|
79
82
|
ok: false;
|
|
80
83
|
reason: 'parse-error';
|
|
@@ -114,11 +117,20 @@ type YjsBridgeOptions = {
|
|
|
114
117
|
declare function createYjsBridge(config: YjsBridgeConfig, options?: YjsBridgeOptions): YjsBridgeHandle;
|
|
115
118
|
|
|
116
119
|
/**
|
|
117
|
-
* Create a Proxy around a Yjs {@link Awareness} that
|
|
118
|
-
*
|
|
119
|
-
* conflicting with the PM↔CM cursor sync plugin.
|
|
120
|
+
* Create a Proxy around a Yjs {@link Awareness} that adapts the cursor sync
|
|
121
|
+
* plugin's awareness field for y-prosemirror's `yCursorPlugin`.
|
|
120
122
|
*
|
|
121
|
-
*
|
|
123
|
+
* y-prosemirror (npm 1.3.7) hardcodes `"cursor"` in `createDecorations`,
|
|
124
|
+
* but the cursor sync plugin writes to a separate field (default `"pmCursor"`)
|
|
125
|
+
* to avoid conflicts with y-codemirror.next's `"cursor"` (Y.Text-based).
|
|
126
|
+
*
|
|
127
|
+
* This proxy:
|
|
128
|
+
* - **`getStates()`**: remaps `cursorField` → `"cursor"` so yCursorPlugin
|
|
129
|
+
* finds the PM cursor data under the hardcoded `"cursor"` key.
|
|
130
|
+
* - **`getLocalState()`**: returns `cursor: null` so yCursorPlugin's
|
|
131
|
+
* `updateCursorInfo` never tries to broadcast its own cursor.
|
|
132
|
+
* - **`setLocalStateField("cursor", …)`**: suppressed (no-op) so
|
|
133
|
+
* yCursorPlugin cannot overwrite the field managed by the sync plugin.
|
|
122
134
|
*/
|
|
123
135
|
declare function createAwarenessProxy(awareness: Awareness, cursorField?: string): Awareness;
|
|
124
136
|
|
|
@@ -143,10 +155,9 @@ type CollabPluginsOptions = {
|
|
|
143
155
|
sharedProseMirror: XmlFragment;
|
|
144
156
|
awareness: Awareness;
|
|
145
157
|
cursorFieldName?: string;
|
|
146
|
-
serialize?: Serialize;
|
|
158
|
+
serialize?: Serialize | SerializeWithMap;
|
|
147
159
|
/** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */
|
|
148
160
|
cmCursorFieldName?: string;
|
|
149
|
-
locate?: LocateText;
|
|
150
161
|
/**
|
|
151
162
|
* Enable PM↔CM cursor sync. Default `false`.
|
|
152
163
|
*
|
|
@@ -224,11 +235,10 @@ declare const cursorSyncPluginKey: PluginKey<CursorSyncState>;
|
|
|
224
235
|
/** Options for {@link createCursorSyncPlugin}. */
|
|
225
236
|
type CursorSyncPluginOptions = {
|
|
226
237
|
awareness: Awareness;
|
|
227
|
-
serialize: Serialize;
|
|
238
|
+
serialize: Serialize | SerializeWithMap;
|
|
228
239
|
cursorFieldName?: string;
|
|
229
240
|
/** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */
|
|
230
241
|
cmCursorFieldName?: string;
|
|
231
|
-
locate?: LocateText;
|
|
232
242
|
/**
|
|
233
243
|
* When provided, the plugin also broadcasts CM-format cursor positions
|
|
234
244
|
* (Y.Text relative positions) to the awareness field specified by
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Doc, Text, XmlFragment, UndoManager, AbstractType } from 'yjs';
|
|
2
|
-
import { Serialize, Parse, Normalize, OnError,
|
|
3
|
-
export { CursorMap, ErrorCode, ErrorEvent,
|
|
2
|
+
import { Serialize, Parse, Normalize, OnError, SerializeWithMap } from '@pm-cm/core';
|
|
3
|
+
export { CursorMap, CursorMapWriter, ErrorCode, ErrorEvent, MatchResult, MatchRun, Matcher, Normalize, OnError, Parse, Serialize, SerializeWithMap, TextSegment, buildCursorMap, createCursorMapWriter, cursorMapLookup, reverseCursorMapLookup, wrapSerialize } from '@pm-cm/core';
|
|
4
4
|
import { Node, Schema } from 'prosemirror-model';
|
|
5
5
|
import { Transaction, EditorState, Plugin, PluginKey } from 'prosemirror-state';
|
|
6
6
|
import { Awareness } from 'y-protocols/awareness';
|
|
@@ -75,6 +75,9 @@ type ReplaceTextResult = {
|
|
|
75
75
|
/** Result of {@link replaceSharedProseMirror}. */
|
|
76
76
|
type ReplaceProseMirrorResult = {
|
|
77
77
|
ok: true;
|
|
78
|
+
} | {
|
|
79
|
+
ok: false;
|
|
80
|
+
reason: 'unchanged';
|
|
78
81
|
} | {
|
|
79
82
|
ok: false;
|
|
80
83
|
reason: 'parse-error';
|
|
@@ -114,11 +117,20 @@ type YjsBridgeOptions = {
|
|
|
114
117
|
declare function createYjsBridge(config: YjsBridgeConfig, options?: YjsBridgeOptions): YjsBridgeHandle;
|
|
115
118
|
|
|
116
119
|
/**
|
|
117
|
-
* Create a Proxy around a Yjs {@link Awareness} that
|
|
118
|
-
*
|
|
119
|
-
* conflicting with the PM↔CM cursor sync plugin.
|
|
120
|
+
* Create a Proxy around a Yjs {@link Awareness} that adapts the cursor sync
|
|
121
|
+
* plugin's awareness field for y-prosemirror's `yCursorPlugin`.
|
|
120
122
|
*
|
|
121
|
-
*
|
|
123
|
+
* y-prosemirror (npm 1.3.7) hardcodes `"cursor"` in `createDecorations`,
|
|
124
|
+
* but the cursor sync plugin writes to a separate field (default `"pmCursor"`)
|
|
125
|
+
* to avoid conflicts with y-codemirror.next's `"cursor"` (Y.Text-based).
|
|
126
|
+
*
|
|
127
|
+
* This proxy:
|
|
128
|
+
* - **`getStates()`**: remaps `cursorField` → `"cursor"` so yCursorPlugin
|
|
129
|
+
* finds the PM cursor data under the hardcoded `"cursor"` key.
|
|
130
|
+
* - **`getLocalState()`**: returns `cursor: null` so yCursorPlugin's
|
|
131
|
+
* `updateCursorInfo` never tries to broadcast its own cursor.
|
|
132
|
+
* - **`setLocalStateField("cursor", …)`**: suppressed (no-op) so
|
|
133
|
+
* yCursorPlugin cannot overwrite the field managed by the sync plugin.
|
|
122
134
|
*/
|
|
123
135
|
declare function createAwarenessProxy(awareness: Awareness, cursorField?: string): Awareness;
|
|
124
136
|
|
|
@@ -143,10 +155,9 @@ type CollabPluginsOptions = {
|
|
|
143
155
|
sharedProseMirror: XmlFragment;
|
|
144
156
|
awareness: Awareness;
|
|
145
157
|
cursorFieldName?: string;
|
|
146
|
-
serialize?: Serialize;
|
|
158
|
+
serialize?: Serialize | SerializeWithMap;
|
|
147
159
|
/** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */
|
|
148
160
|
cmCursorFieldName?: string;
|
|
149
|
-
locate?: LocateText;
|
|
150
161
|
/**
|
|
151
162
|
* Enable PM↔CM cursor sync. Default `false`.
|
|
152
163
|
*
|
|
@@ -224,11 +235,10 @@ declare const cursorSyncPluginKey: PluginKey<CursorSyncState>;
|
|
|
224
235
|
/** Options for {@link createCursorSyncPlugin}. */
|
|
225
236
|
type CursorSyncPluginOptions = {
|
|
226
237
|
awareness: Awareness;
|
|
227
|
-
serialize: Serialize;
|
|
238
|
+
serialize: Serialize | SerializeWithMap;
|
|
228
239
|
cursorFieldName?: string;
|
|
229
240
|
/** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */
|
|
230
241
|
cmCursorFieldName?: string;
|
|
231
|
-
locate?: LocateText;
|
|
232
242
|
/**
|
|
233
243
|
* When provided, the plugin also broadcasts CM-format cursor positions
|
|
234
244
|
* (Y.Text relative positions) to the awareness field specified by
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/bridge.ts
|
|
2
2
|
import { prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, ySyncPluginKey } from "y-prosemirror";
|
|
3
|
+
import { XmlElement as YXmlElement, XmlText as YXmlText } from "yjs";
|
|
3
4
|
|
|
4
5
|
// src/types.ts
|
|
5
6
|
var ORIGIN_TEXT_TO_PM = "bridge:text-to-prosemirror";
|
|
@@ -57,11 +58,121 @@ function replaceSharedProseMirror(doc, fragment, text, origin, config) {
|
|
|
57
58
|
onError({ code: "parse-error", message: "failed to parse text into ProseMirror document", cause: error });
|
|
58
59
|
return { ok: false, reason: "parse-error" };
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
try {
|
|
62
|
+
const currentDoc = yXmlFragmentToProseMirrorRootNode(fragment, config.schema);
|
|
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
|
+
}
|
|
63
73
|
return { ok: true };
|
|
64
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
|
+
}
|
|
173
|
+
}, origin);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
65
176
|
function createYjsBridge(config, options) {
|
|
66
177
|
const {
|
|
67
178
|
doc,
|
|
@@ -97,10 +208,10 @@ function createYjsBridge(config, options) {
|
|
|
97
208
|
normalize,
|
|
98
209
|
onError
|
|
99
210
|
});
|
|
100
|
-
if (result.ok) {
|
|
211
|
+
if (result.ok || result.reason === "unchanged") {
|
|
101
212
|
lastBridgedText = text;
|
|
102
213
|
}
|
|
103
|
-
return result.ok;
|
|
214
|
+
return result.ok || result.reason === "unchanged";
|
|
104
215
|
};
|
|
105
216
|
const sharedProseMirrorToText = (fragment) => {
|
|
106
217
|
try {
|
|
@@ -214,15 +325,28 @@ function createAwarenessProxy(awareness, cursorField = "pmCursor") {
|
|
|
214
325
|
if (prop === "getLocalState") {
|
|
215
326
|
return () => {
|
|
216
327
|
const state = target.getLocalState();
|
|
217
|
-
return state ? { ...state,
|
|
328
|
+
return state ? { ...state, cursor: null } : state;
|
|
218
329
|
};
|
|
219
330
|
}
|
|
220
331
|
if (prop === "setLocalStateField") {
|
|
221
332
|
return (field, value2) => {
|
|
222
|
-
if (field ===
|
|
333
|
+
if (field === "cursor") return;
|
|
223
334
|
target.setLocalStateField(field, value2);
|
|
224
335
|
};
|
|
225
336
|
}
|
|
337
|
+
if (prop === "getStates") {
|
|
338
|
+
return () => {
|
|
339
|
+
const states = target.getStates();
|
|
340
|
+
const remapped = /* @__PURE__ */ new Map();
|
|
341
|
+
states.forEach((state, clientId) => {
|
|
342
|
+
remapped.set(clientId, {
|
|
343
|
+
...state,
|
|
344
|
+
cursor: state[cursorField] ?? null
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
return remapped;
|
|
348
|
+
};
|
|
349
|
+
}
|
|
226
350
|
const value = Reflect.get(target, prop, receiver);
|
|
227
351
|
return typeof value === "function" ? value.bind(target) : value;
|
|
228
352
|
}
|
|
@@ -317,7 +441,7 @@ function broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAncho
|
|
|
317
441
|
}
|
|
318
442
|
var defaultOnWarning2 = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
|
|
319
443
|
function createCursorSyncPlugin(options) {
|
|
320
|
-
const { awareness, serialize,
|
|
444
|
+
const { awareness, serialize, sharedText } = options;
|
|
321
445
|
const warn = options.onWarning ?? defaultOnWarning2;
|
|
322
446
|
const cursorFieldName = options.cursorFieldName ?? "pmCursor";
|
|
323
447
|
const cmCursorFieldName = options.cmCursorFieldName ?? "cursor";
|
|
@@ -326,7 +450,7 @@ function createCursorSyncPlugin(options) {
|
|
|
326
450
|
let cachedMapDoc = null;
|
|
327
451
|
function getOrBuildMap(doc) {
|
|
328
452
|
if (cachedMapDoc !== doc || !cachedMap) {
|
|
329
|
-
cachedMap = buildCursorMap(doc, serialize
|
|
453
|
+
cachedMap = buildCursorMap(doc, serialize);
|
|
330
454
|
cachedMapDoc = doc;
|
|
331
455
|
}
|
|
332
456
|
return cachedMap;
|
|
@@ -428,7 +552,7 @@ function createCollabPlugins(schema, options) {
|
|
|
428
552
|
const pmAwareness = enableCursorSync ? createAwarenessProxy(options.awareness, cursorFieldName) : options.awareness;
|
|
429
553
|
const plugins = [
|
|
430
554
|
ySyncPlugin(sharedProseMirror, { mapping: rawMapping }),
|
|
431
|
-
yCursorPlugin(pmAwareness, options.yCursorPluginOpts ?? {}
|
|
555
|
+
yCursorPlugin(pmAwareness, options.yCursorPluginOpts ?? {}),
|
|
432
556
|
yUndoPlugin(options.yUndoPluginOpts)
|
|
433
557
|
];
|
|
434
558
|
if (options.bridge) {
|
|
@@ -441,7 +565,6 @@ function createCollabPlugins(schema, options) {
|
|
|
441
565
|
serialize: options.serialize,
|
|
442
566
|
cursorFieldName,
|
|
443
567
|
cmCursorFieldName: options.cmCursorFieldName,
|
|
444
|
-
locate: options.locate,
|
|
445
568
|
sharedText: options.sharedText,
|
|
446
569
|
onWarning: options.onWarning
|
|
447
570
|
})
|
|
@@ -452,6 +575,7 @@ function createCollabPlugins(schema, options) {
|
|
|
452
575
|
|
|
453
576
|
// src/index.ts
|
|
454
577
|
import { buildCursorMap as buildCursorMap2, cursorMapLookup as cursorMapLookup2, reverseCursorMapLookup as reverseCursorMapLookup2 } from "@pm-cm/core";
|
|
578
|
+
import { createCursorMapWriter, wrapSerialize } from "@pm-cm/core";
|
|
455
579
|
export {
|
|
456
580
|
ORIGIN_INIT,
|
|
457
581
|
ORIGIN_PM_TO_TEXT,
|
|
@@ -461,6 +585,7 @@ export {
|
|
|
461
585
|
createAwarenessProxy,
|
|
462
586
|
createBridgeSyncPlugin,
|
|
463
587
|
createCollabPlugins,
|
|
588
|
+
createCursorMapWriter,
|
|
464
589
|
createCursorSyncPlugin,
|
|
465
590
|
createYjsBridge,
|
|
466
591
|
cursorMapLookup2 as cursorMapLookup,
|
|
@@ -468,6 +593,7 @@ export {
|
|
|
468
593
|
replaceSharedProseMirror,
|
|
469
594
|
replaceSharedText,
|
|
470
595
|
reverseCursorMapLookup2 as reverseCursorMapLookup,
|
|
471
|
-
syncCmCursor
|
|
596
|
+
syncCmCursor,
|
|
597
|
+
wrapSerialize
|
|
472
598
|
};
|
|
473
599
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bridge.ts","../src/types.ts","../src/awareness-proxy.ts","../src/collab-plugins.ts","../src/bridge-sync-plugin.ts","../src/cursor-sync-plugin.ts","../src/index.ts"],"sourcesContent":["import type { Node } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport { prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, ySyncPluginKey } from 'y-prosemirror'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { Normalize, OnError } from '@pm-cm/core'\nimport type { BootstrapResult, YjsBridgeConfig, YjsBridgeHandle } from './types.js'\nimport { ORIGIN_INIT, ORIGIN_TEXT_TO_PM, ORIGIN_PM_TO_TEXT } from './types.js'\n\nconst defaultNormalize: Normalize = (s) => s.replace(/\\r\\n?/g, '\\n')\nconst defaultOnError: OnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause)\n\n/** Result of {@link replaceSharedText}. */\nexport type ReplaceTextResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' }\n | { ok: false; reason: 'detached' }\n\n/** Result of {@link replaceSharedProseMirror}. */\nexport type ReplaceProseMirrorResult =\n | { ok: true }\n | { ok: false; reason: 'parse-error' }\n | { ok: false; reason: 'detached' }\n\n/**\n * Union of all replace-result types. Kept for backward compatibility.\n * Prefer the narrower {@link ReplaceTextResult} / {@link ReplaceProseMirrorResult}.\n */\nexport type ReplaceResult = ReplaceTextResult | ReplaceProseMirrorResult\n\n/**\n * Replace `Y.Text` content using a minimal diff (common prefix/suffix trimming).\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedText(\n sharedText: YText,\n next: string,\n origin: unknown,\n normalize: Normalize = defaultNormalize,\n): ReplaceTextResult {\n if (!sharedText.doc) {\n return { ok: false, reason: 'detached' }\n }\n\n const normalized = normalize(next)\n const current = sharedText.toString()\n if (current === normalized) {\n return { ok: false, reason: 'unchanged' }\n }\n\n // Minimal diff: find common prefix and suffix, replace only the changed middle.\n let start = 0\n const minLen = Math.min(current.length, normalized.length)\n while (start < minLen && current.charCodeAt(start) === normalized.charCodeAt(start)) {\n start++\n }\n\n let endCurrent = current.length\n let endNext = normalized.length\n while (endCurrent > start && endNext > start && current.charCodeAt(endCurrent - 1) === normalized.charCodeAt(endNext - 1)) {\n endCurrent--\n endNext--\n }\n\n sharedText.doc.transact(() => {\n const deleteCount = endCurrent - start\n if (deleteCount > 0) {\n sharedText.delete(start, deleteCount)\n }\n const insertStr = normalized.slice(start, endNext)\n if (insertStr.length > 0) {\n sharedText.insert(start, insertStr)\n }\n }, origin)\n\n return { ok: true }\n}\n\n/**\n * Replace `Y.XmlFragment` by parsing serialized text into a ProseMirror document.\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedProseMirror(\n doc: Doc,\n fragment: YXmlFragment,\n text: string,\n origin: unknown,\n config: Pick<YjsBridgeConfig, 'schema' | 'parse' | 'normalize' | 'onError'>,\n): ReplaceProseMirrorResult {\n if (!fragment.doc) {\n return { ok: false, reason: 'detached' }\n }\n if (fragment.doc !== doc) {\n throw new Error('fragment belongs to a different Y.Doc than the provided doc')\n }\n\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n let nextDoc: Node\n try {\n nextDoc = config.parse(normalize(text), config.schema)\n } catch (error) {\n onError({ code: 'parse-error', message: 'failed to parse text into ProseMirror document', cause: error })\n return { ok: false, reason: 'parse-error' }\n }\n\n doc.transact(() => {\n prosemirrorToYXmlFragment(nextDoc, fragment)\n }, origin)\n return { ok: true }\n}\n\n/** Options for {@link createYjsBridge}. */\nexport type YjsBridgeOptions = {\n initialText?: string\n /** Which side wins when both sharedText and sharedProseMirror exist and differ. Default `'text'`. */\n prefer?: 'text' | 'prosemirror'\n}\n\n/**\n * Create a collaborative bridge that keeps `Y.Text` and `Y.XmlFragment` in sync.\n *\n * Runs a synchronous bootstrap to reconcile existing state, then installs a\n * `Y.Text` observer for the text → ProseMirror direction.\n *\n * @throws If `sharedText` or `sharedProseMirror` belong to a different `Y.Doc`.\n */\nexport function createYjsBridge(\n config: YjsBridgeConfig,\n options?: YjsBridgeOptions,\n): YjsBridgeHandle {\n const {\n doc,\n sharedText,\n sharedProseMirror,\n schema,\n serialize,\n parse,\n } = config\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n\n if (!sharedText.doc) {\n throw new Error('sharedText is not attached to any Y.Doc')\n }\n if (sharedText.doc !== doc) {\n throw new Error('sharedText belongs to a different Y.Doc than the provided doc')\n }\n if (!sharedProseMirror.doc) {\n throw new Error('sharedProseMirror is not attached to any Y.Doc')\n }\n if (sharedProseMirror.doc !== doc) {\n throw new Error('sharedProseMirror belongs to a different Y.Doc than the provided doc')\n }\n\n let lastBridgedText: string | null = null\n\n /** Returns `true` if the parse succeeded. */\n const syncTextToProsemirror = (origin: unknown): boolean => {\n const text = normalize(sharedText.toString())\n if (lastBridgedText === text) {\n return true\n }\n\n const result = replaceSharedProseMirror(doc, sharedProseMirror, text, origin, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (result.ok) {\n lastBridgedText = text\n }\n return result.ok\n }\n\n const sharedProseMirrorToText = (fragment: YXmlFragment): string | null => {\n try {\n const pmDoc = yXmlFragmentToProseMirrorRootNode(fragment, schema)\n return normalize(serialize(pmDoc))\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to convert ProseMirror fragment to text', cause: error })\n return null\n }\n }\n\n // Bootstrap\n const bootstrap = (): BootstrapResult => {\n const text = normalize(sharedText.toString())\n const hasText = text.length > 0\n const hasProsemirror = sharedProseMirror.length > 0\n\n if (!hasText && !hasProsemirror) {\n const initial = options?.initialText ?? ''\n if (initial.length > 0) {\n // Set Y.XmlFragment first, then derive Y.Text from serialize(parse(initial))\n // to ensure both shared types are in the same canonical form.\n const initResult = replaceSharedProseMirror(doc, sharedProseMirror, initial, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!initResult.ok) {\n return { source: 'initial', parseError: true }\n }\n const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema)\n const canonicalText = serialize(pmDoc)\n replaceSharedText(sharedText, canonicalText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(canonicalText)\n return { source: 'initial' }\n }\n return { source: 'empty' }\n }\n\n if (hasText && !hasProsemirror) {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n\n if (!hasText && hasProsemirror) {\n const textFromProsemirror = sharedProseMirrorToText(sharedProseMirror)\n if (textFromProsemirror !== null) {\n replaceSharedText(sharedText, textFromProsemirror, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(textFromProsemirror)\n return { source: 'prosemirror' }\n }\n return { source: 'prosemirror', parseError: true }\n }\n\n const prosemirrorText = sharedProseMirrorToText(sharedProseMirror)\n if (prosemirrorText === null) {\n const fallbackText = hasText ? text : (options?.initialText ?? '')\n let parseError = false\n if (fallbackText.length > 0) {\n replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize)\n const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!fallbackResult.ok) parseError = true\n }\n return { source: 'text', ...(parseError && { parseError: true }) }\n }\n\n if (prosemirrorText !== text) {\n const prefer = options?.prefer ?? 'text'\n if (prefer === 'prosemirror') {\n replaceSharedText(sharedText, prosemirrorText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(prosemirrorText)\n return { source: 'prosemirror' }\n } else {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n } else {\n lastBridgedText = text\n return { source: 'both-match' }\n }\n }\n\n const textObserver = (\n _: unknown,\n transaction: { origin: unknown },\n ) => {\n if (transaction.origin === ORIGIN_PM_TO_TEXT || transaction.origin === ORIGIN_INIT) {\n return\n }\n\n syncTextToProsemirror(ORIGIN_TEXT_TO_PM)\n }\n\n // Run bootstrap synchronously before installing the observer so that\n // an exception during bootstrap cannot leave a dangling observer.\n const bootstrapResult = bootstrap()\n\n sharedText.observe(textObserver)\n\n return {\n bootstrapResult,\n syncToSharedText(doc: Node): ReplaceTextResult {\n const text = serialize(doc)\n const result = replaceSharedText(sharedText, text, ORIGIN_PM_TO_TEXT, normalize)\n // Always update lastBridgedText unless truly failed (detached).\n // 'unchanged' means Y.Text already has this content — still need to\n // record it so the reverse observer doesn't trigger a redundant sync.\n if (result.ok || result.reason === 'unchanged') {\n lastBridgedText = normalize(text)\n }\n return result\n },\n isYjsSyncChange(tr: Transaction): boolean {\n // Internal meta shape from y-prosemirror's ySyncPlugin (tested against ^1.3.x).\n const meta = tr.getMeta(ySyncPluginKey)\n return (\n typeof meta === 'object' &&\n meta !== null &&\n 'isChangeOrigin' in meta &&\n (meta as Record<string, unknown>).isChangeOrigin === true\n )\n },\n dispose() {\n sharedText.unobserve(textObserver)\n },\n }\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { Serialize, Parse, Normalize, OnError } from '@pm-cm/core'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { ReplaceTextResult } from './bridge.js'\n\n/** Known warning codes emitted by the yjs bridge and plugins. */\nexport type WarningCode = 'bridge-already-wired' | 'sync-failed' | 'ysync-plugin-missing' | 'cursor-sync-not-installed'\n\n/** Structured warning event for non-fatal warnings. */\nexport type WarningEvent = {\n code: WarningCode\n message: string\n}\n\n/**\n * Warning handler callback for non-fatal warnings.\n *\n * Known codes:\n * - `'bridge-already-wired'` — the same bridge handle is wired to multiple plugin instances.\n * - `'sync-failed'` — `syncToSharedText` failed (e.g. Y.Text detached).\n * - `'ysync-plugin-missing'` — ySyncPlugin state is not available; cursor broadcast skipped.\n * - `'cursor-sync-not-installed'` — cursor sync plugin is not installed on the EditorView.\n */\nexport type OnWarning = (event: WarningEvent) => void\n\n/** Yjs transaction origin: text → ProseMirror direction. */\nexport const ORIGIN_TEXT_TO_PM = 'bridge:text-to-prosemirror'\n\n/** Yjs transaction origin: ProseMirror → text direction. */\nexport const ORIGIN_PM_TO_TEXT = 'bridge:prosemirror-to-text'\n\n/** Yjs transaction origin: bootstrap initialization. */\nexport const ORIGIN_INIT = 'bridge:init'\n\n/** Configuration for {@link createYjsBridge}. */\nexport type YjsBridgeConfig = {\n doc: Doc\n sharedText: YText\n sharedProseMirror: YXmlFragment\n schema: Schema\n serialize: Serialize\n parse: Parse\n normalize?: Normalize\n /** Called on non-fatal errors (e.g. parse failures). Defaults to `console.error`. */\n onError?: OnError\n}\n\n/**\n * Result of the bootstrap phase in {@link createYjsBridge}.\n * Indicates which source was used to initialize the shared types.\n */\nexport type BootstrapResult = {\n source: 'text' | 'prosemirror' | 'both-match' | 'empty' | 'initial'\n /** `true` when format conversion (parse or serialize) failed during bootstrap. The bridge is still usable but the affected shared type may be stale. */\n parseError?: boolean\n}\n\n/** Handle returned by {@link createYjsBridge}. */\nexport type YjsBridgeHandle = {\n /** Result of the synchronous bootstrap phase. */\n readonly bootstrapResult: BootstrapResult\n /** Serialize `doc` and push to `Y.Text` using minimal diff. */\n syncToSharedText(doc: Node): ReplaceTextResult\n /** Returns `true` if the transaction originated from `y-prosemirror` sync. */\n isYjsSyncChange(tr: Transaction): boolean\n /** Remove the Y.Text observer. Call when tearing down. */\n dispose(): void\n}\n","import type { Awareness } from 'y-protocols/awareness'\n\n/**\n * Create a Proxy around a Yjs {@link Awareness} that suppresses the specified\n * cursor field. This prevents y-prosemirror's built-in cursor management from\n * conflicting with the PM↔CM cursor sync plugin.\n *\n * Other `setLocalStateField` calls are passed through unchanged.\n */\nexport function createAwarenessProxy(awareness: Awareness, cursorField = 'pmCursor'): Awareness {\n return new Proxy(awareness, {\n get(target, prop, receiver) {\n if (prop === 'getLocalState') {\n return () => {\n const state = target.getLocalState()\n return state ? { ...state, [cursorField]: null } : state\n }\n }\n if (prop === 'setLocalStateField') {\n return (field: string, value: unknown) => {\n // Only suppress the cursor field; pass through other fields\n if (field === cursorField) return\n target.setLocalStateField(field, value)\n }\n }\n const value = Reflect.get(target, prop, receiver) as unknown\n return typeof value === 'function' ? (value as Function).bind(target) : value\n },\n }) as Awareness\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { EditorState, Plugin } from 'prosemirror-state'\nimport type { DecorationAttrs } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport type { Serialize, LocateText } from '@pm-cm/core'\nimport { initProseMirrorDoc, yCursorPlugin, ySyncPlugin, yUndoPlugin } from 'y-prosemirror'\nimport type { AbstractType, Text as YText, UndoManager } from 'yjs'\nimport type { XmlFragment as YXmlFragment } from 'yjs'\nimport { createAwarenessProxy } from './awareness-proxy.js'\nimport { createBridgeSyncPlugin } from './bridge-sync-plugin.js'\nimport { createCursorSyncPlugin } from './cursor-sync-plugin.js'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\n/** Yjs ↔ ProseMirror node mapping used by `y-prosemirror`. */\nexport type ProseMirrorMapping = Map<AbstractType<unknown>, Node | Node[]>\n\n/** Options forwarded to `yCursorPlugin` from y-prosemirror. */\nexport type YCursorPluginOpts = {\n awarenessStateFilter?: (currentClientId: number, userClientId: number, user: unknown) => boolean\n cursorBuilder?: (user: unknown, clientId: number) => HTMLElement\n selectionBuilder?: (user: unknown, clientId: number) => DecorationAttrs\n getSelection?: (state: EditorState) => unknown\n}\n\n/** Options forwarded to `yUndoPlugin` from y-prosemirror. */\nexport type YUndoPluginOpts = {\n protectedNodes?: Set<string>\n trackedOrigins?: unknown[]\n undoManager?: UndoManager | null\n}\n\n/** Options for {@link createCollabPlugins}. */\nexport type CollabPluginsOptions = {\n /** Shared ProseMirror document in Yjs. */\n sharedProseMirror: YXmlFragment\n awareness: Awareness\n cursorFieldName?: string\n serialize?: Serialize\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n locate?: LocateText\n /**\n * Enable PM↔CM cursor sync. Default `false`.\n *\n * When enabled, an {@link createAwarenessProxy | awareness proxy} is applied\n * to suppress y-prosemirror's built-in cursor management.\n */\n cursorSync?: boolean\n /**\n * The shared `Y.Text` instance. When provided, the cursor sync plugin also\n * broadcasts CM-format cursor positions so remote `yCollab` instances render them.\n */\n sharedText?: YText\n /**\n * When provided, a bridge sync plugin is inserted before the cursor sync plugin\n * to ensure Y.Text is synced before cursor positions are computed. This guarantees\n * that serialize-based offsets match Y.Text indices.\n */\n bridge?: YjsBridgeHandle\n /** Extra options forwarded to `yCursorPlugin`. */\n yCursorPluginOpts?: YCursorPluginOpts\n /** Extra options forwarded to `yUndoPlugin`. */\n yUndoPluginOpts?: YUndoPluginOpts\n /** Called for non-fatal warnings. Propagated to child plugins. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * Bundle `ySyncPlugin`, `yCursorPlugin`, `yUndoPlugin` from y-prosemirror,\n * plus an optional PM↔CM cursor sync plugin.\n *\n * @throws If `cursorSync: true` but `serialize` is not provided.\n */\nexport function createCollabPlugins(\n schema: Schema,\n options: CollabPluginsOptions,\n): { plugins: Plugin[]; doc: Node; mapping: ProseMirrorMapping } {\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const enableCursorSync = options.cursorSync ?? false\n const { sharedProseMirror } = options\n\n if (enableCursorSync && !options.serialize) {\n throw new Error('createCollabPlugins: cursorSync requires serialize to be provided')\n }\n const { doc, mapping: rawMapping } = initProseMirrorDoc(sharedProseMirror, schema)\n const mapping = rawMapping as ProseMirrorMapping\n const pmAwareness = enableCursorSync\n ? createAwarenessProxy(options.awareness, cursorFieldName)\n : options.awareness\n\n const plugins: Plugin[] = [\n ySyncPlugin(sharedProseMirror, { mapping: rawMapping }),\n yCursorPlugin(pmAwareness, options.yCursorPluginOpts ?? {}, cursorFieldName),\n yUndoPlugin(options.yUndoPluginOpts),\n ]\n\n // Bridge sync plugin must run before cursor sync plugin so that\n // Y.Text is updated before cursor positions are computed.\n if (options.bridge) {\n plugins.push(createBridgeSyncPlugin(options.bridge, { onWarning: options.onWarning }))\n }\n\n if (enableCursorSync && options.serialize) {\n plugins.push(\n createCursorSyncPlugin({\n awareness: options.awareness,\n serialize: options.serialize,\n cursorFieldName,\n cmCursorFieldName: options.cmCursorFieldName,\n locate: options.locate,\n sharedText: options.sharedText,\n onWarning: options.onWarning,\n }),\n )\n }\n\n return { plugins, doc, mapping }\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\ntype BridgeSyncState = { needsSync: boolean }\n\ntype BridgeSyncFailure = { ok: false; reason: 'detached' }\n\n/** Options for {@link createBridgeSyncPlugin}. */\nexport type BridgeSyncPluginOptions = {\n /** Called when `syncToSharedText` fails (excludes `reason: 'unchanged'`). */\n onSyncFailure?: (result: BridgeSyncFailure, view: EditorView) => void\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/** ProseMirror plugin key for {@link createBridgeSyncPlugin}. Use to read the plugin state. */\nexport const bridgeSyncPluginKey = new PluginKey<BridgeSyncState>('pm-cm-bridge-sync')\n\nconst wiredBridges = new WeakSet<YjsBridgeHandle>()\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/**\n * ProseMirror plugin that automatically syncs PM doc changes to Y.Text\n * via the bridge handle. Skips Yjs-originated changes to avoid loops.\n *\n * A warning is logged if the same bridge handle is wired more than once.\n * The guard is cleaned up when the plugin is destroyed.\n */\nexport function createBridgeSyncPlugin(\n bridge: YjsBridgeHandle,\n options: BridgeSyncPluginOptions = {},\n): Plugin {\n const warn = options.onWarning ?? defaultOnWarning\n if (wiredBridges.has(bridge)) {\n warn({ code: 'bridge-already-wired', message: 'this bridge is already wired to another plugin instance' })\n }\n wiredBridges.add(bridge)\n\n return new Plugin<BridgeSyncState>({\n key: bridgeSyncPluginKey,\n\n state: {\n init(): BridgeSyncState {\n return { needsSync: false }\n },\n apply(tr, _prev): BridgeSyncState {\n if (!tr.docChanged) return { needsSync: false }\n if (bridge.isYjsSyncChange(tr)) return { needsSync: false }\n return { needsSync: true }\n },\n },\n\n view() {\n return {\n update(view) {\n const state = bridgeSyncPluginKey.getState(view.state)\n if (state?.needsSync) {\n const result = bridge.syncToSharedText(view.state.doc)\n if (!result.ok) {\n if (result.reason === 'detached') {\n options.onSyncFailure?.(result, view)\n warn({ code: 'sync-failed', message: `bridge sync failed: ${result.reason}` })\n }\n }\n }\n },\n destroy() {\n wiredBridges.delete(bridge)\n },\n }\n },\n })\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { Node } from 'prosemirror-model'\nimport type { EditorView } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport { absolutePositionToRelativePosition, ySyncPluginKey } from 'y-prosemirror'\nimport { createRelativePositionFromTypeIndex } from 'yjs'\nimport type { Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { Serialize, LocateText, CursorMap } from '@pm-cm/core'\nimport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nimport type { OnWarning } from './types.js'\n\n/** Plugin state for the cursor sync plugin. Read via {@link cursorSyncPluginKey}. */\nexport type CursorSyncState = {\n /** Pending CodeMirror cursor to broadcast. Set by {@link syncCmCursor}. */\n pendingCm: { anchor: number; head: number } | null\n /** Text offset mapped from the current PM selection anchor. `null` when no mapping is available. */\n mappedTextOffset: number | null\n}\n\n/** ProseMirror plugin key for {@link createCursorSyncPlugin}. Use to read the plugin state. */\nexport const cursorSyncPluginKey = new PluginKey<CursorSyncState>('pm-cm-cursor-sync')\n\n/**\n * Internal shape of `ySyncPluginKey` state from y-prosemirror.\n * Not exported by upstream — kept here for explicit tracking.\n * Tested against y-prosemirror ^1.3.x.\n */\ntype YSyncPluginState = { type: YXmlFragment; binding: { mapping: Map<unknown, unknown> } }\n\nfunction getYSyncState(view: EditorView): YSyncPluginState | null {\n const raw = ySyncPluginKey.getState(view.state) as Record<string, unknown> | undefined\n if (!raw) return null\n if (\n typeof raw === 'object' &&\n 'type' in raw && raw.type &&\n 'binding' in raw && raw.binding &&\n typeof raw.binding === 'object' &&\n 'mapping' in (raw.binding as Record<string, unknown>) &&\n (raw.binding as Record<string, unknown>).mapping instanceof Map\n ) {\n return raw as unknown as YSyncPluginState\n }\n return null\n}\n\nfunction toRelativePosition(\n view: EditorView,\n pmPos: number,\n): unknown | null {\n const ySyncState = getYSyncState(view)\n if (!ySyncState) return null\n\n return absolutePositionToRelativePosition(\n pmPos,\n ySyncState.type,\n ySyncState.binding.mapping as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- y-prosemirror internal mapping type\n )\n}\n\n/** Returns `false` when ySyncPlugin state is unavailable (plugin not installed). */\nfunction broadcastPmCursor(\n awareness: Awareness,\n cursorFieldName: string,\n view: EditorView,\n pmAnchor: number,\n pmHead: number,\n): boolean {\n const relAnchor = toRelativePosition(view, pmAnchor)\n const relHead = toRelativePosition(view, pmHead)\n if (relAnchor === null || relHead === null) return false\n\n awareness.setLocalStateField(cursorFieldName, { anchor: relAnchor, head: relHead })\n return true\n}\n\nfunction broadcastTextCursor(\n awareness: Awareness,\n cmCursorFieldName: string,\n sharedText: YText,\n textAnchor: number,\n textHead: number,\n): void {\n const len = sharedText.length\n const clamp = (v: number) => Math.max(0, Math.min(v, len))\n const relAnchor = createRelativePositionFromTypeIndex(sharedText, clamp(textAnchor))\n const relHead = createRelativePositionFromTypeIndex(sharedText, clamp(textHead))\n awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead })\n}\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/** Options for {@link createCursorSyncPlugin}. */\nexport type CursorSyncPluginOptions = {\n awareness: Awareness\n serialize: Serialize\n cursorFieldName?: string\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n locate?: LocateText\n /**\n * When provided, the plugin also broadcasts CM-format cursor positions\n * (Y.Text relative positions) to the awareness field specified by\n * `cmCursorFieldName`, so that remote `yCollab` instances can render the cursor.\n */\n sharedText?: YText\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * ProseMirror plugin that synchronizes cursor positions between PM and CM via Yjs awareness.\n *\n * - PM → awareness: automatically broadcasts when the PM view is focused and selection changes.\n * - CM → awareness: triggered by dispatching {@link syncCmCursor}.\n */\nexport function createCursorSyncPlugin(options: CursorSyncPluginOptions): Plugin {\n const { awareness, serialize, locate, sharedText } = options\n const warn = options.onWarning ?? defaultOnWarning\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const cmCursorFieldName = options.cmCursorFieldName ?? 'cursor'\n\n let warnedSyncPluginMissing = false\n\n // Cached cursor map (serialize-based) — rebuilt only when doc changes\n let cachedMap: CursorMap | null = null\n let cachedMapDoc: Node | null = null\n\n function getOrBuildMap(doc: Node): CursorMap {\n if (cachedMapDoc !== doc || !cachedMap) {\n cachedMap = buildCursorMap(doc, serialize, locate)\n cachedMapDoc = doc\n }\n return cachedMap\n }\n\n return new Plugin<CursorSyncState>({\n key: cursorSyncPluginKey,\n\n state: {\n init(): CursorSyncState {\n return { pendingCm: null, mappedTextOffset: null }\n },\n apply(tr, prev, _oldState, newState): CursorSyncState {\n const cmMeta = tr.getMeta(cursorSyncPluginKey) as\n | { anchor: number; head: number }\n | undefined\n if (cmMeta) {\n return { pendingCm: cmMeta, mappedTextOffset: prev.mappedTextOffset }\n }\n\n // Compute PM → text offset when selection or doc changes\n let mappedTextOffset = prev.mappedTextOffset\n if (tr.selectionSet || tr.docChanged) {\n const map = getOrBuildMap(newState.doc)\n mappedTextOffset = cursorMapLookup(map, newState.selection.anchor)\n }\n\n return {\n pendingCm: prev.pendingCm !== null ? null : prev.pendingCm,\n mappedTextOffset,\n }\n },\n },\n\n view() {\n return {\n update(view, prevState) {\n const pluginState = cursorSyncPluginKey.getState(view.state)\n const prevPluginState = cursorSyncPluginKey.getState(prevState)\n\n // CM → awareness: broadcast when pendingCm is newly set\n if (\n pluginState?.pendingCm != null &&\n pluginState.pendingCm !== prevPluginState?.pendingCm\n ) {\n const map = getOrBuildMap(view.state.doc)\n const pmAnchor = reverseCursorMapLookup(map, pluginState.pendingCm.anchor)\n const pmHead = reverseCursorMapLookup(map, pluginState.pendingCm.head)\n if (pmAnchor !== null && pmHead !== null) {\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n }\n // Also broadcast CM-format cursor so remote yCollab can render it\n if (sharedText) {\n broadcastTextCursor(\n awareness,\n cmCursorFieldName,\n sharedText,\n pluginState.pendingCm.anchor,\n pluginState.pendingCm.head,\n )\n }\n return\n }\n\n // PM → awareness: auto-broadcast on selection/doc change when focused\n if (\n view.hasFocus() &&\n (view.state.selection !== prevState.selection ||\n view.state.doc !== prevState.doc)\n ) {\n const { anchor, head } = view.state.selection\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, anchor, head)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n // Also broadcast CM-format cursor so remote yCollab can render it.\n // When bridgeSyncPlugin runs before this plugin, Y.Text is already\n // synced so serialize-based offsets match Y.Text indices.\n if (sharedText) {\n const map = getOrBuildMap(view.state.doc)\n const textAnchor = cursorMapLookup(map, anchor)\n const textHead = cursorMapLookup(map, head)\n if (textAnchor !== null && textHead !== null) {\n broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead)\n }\n }\n }\n },\n }\n },\n })\n}\n\n/**\n * Dispatch a CodeMirror cursor offset (or range) to the cursor sync plugin.\n * The plugin will convert it to a ProseMirror position and broadcast via awareness.\n *\n * @param view - The ProseMirror EditorView that has the cursor sync plugin installed.\n * @param anchor - CodeMirror text offset for the anchor.\n * @param head - CodeMirror text offset for the head (defaults to `anchor` for a collapsed cursor).\n * @param onWarning - Optional warning callback. Default `console.warn`.\n */\nexport function syncCmCursor(view: EditorView, anchor: number, head?: number, onWarning?: OnWarning): void {\n if (!cursorSyncPluginKey.getState(view.state)) {\n (onWarning ?? defaultOnWarning)({ code: 'cursor-sync-not-installed', message: 'cursor sync plugin is not installed on this EditorView' })\n return\n }\n const sanitize = (v: number) => Math.max(0, Math.floor(v))\n view.dispatch(\n view.state.tr.setMeta(cursorSyncPluginKey, {\n anchor: sanitize(anchor),\n head: sanitize(head ?? anchor),\n }),\n )\n}\n","export { createYjsBridge, replaceSharedText, replaceSharedProseMirror } from './bridge.js'\nexport type { YjsBridgeOptions, ReplaceResult, ReplaceTextResult, ReplaceProseMirrorResult } from './bridge.js'\nexport { createAwarenessProxy } from './awareness-proxy.js'\nexport { createCollabPlugins } from './collab-plugins.js'\nexport type { CollabPluginsOptions, ProseMirrorMapping, YCursorPluginOpts, YUndoPluginOpts } from './collab-plugins.js'\nexport {\n ORIGIN_TEXT_TO_PM,\n ORIGIN_PM_TO_TEXT,\n ORIGIN_INIT,\n} from './types.js'\nexport type {\n BootstrapResult,\n YjsBridgeConfig,\n YjsBridgeHandle,\n WarningCode,\n WarningEvent,\n OnWarning,\n} from './types.js'\n\n// Cursor mapping re-exported from @pm-cm/core\nexport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nexport type { TextSegment, CursorMap, LocateText } from '@pm-cm/core'\n\n// Bridge sync plugin (auto PM→Y.Text wiring)\nexport { createBridgeSyncPlugin, bridgeSyncPluginKey } from './bridge-sync-plugin.js'\nexport type { BridgeSyncPluginOptions } from './bridge-sync-plugin.js'\n\n// Cursor sync plugin\nexport { createCursorSyncPlugin, cursorSyncPluginKey, syncCmCursor } from './cursor-sync-plugin.js'\nexport type { CursorSyncState, CursorSyncPluginOptions } from './cursor-sync-plugin.js'\n\n// Re-export types from @pm-cm/core\nexport type { Serialize, Parse, Normalize, OnError, ErrorCode, ErrorEvent } from '@pm-cm/core'\n"],"mappings":";AAEA,SAAS,2BAA2B,mCAAmC,sBAAsB;;;ACyBtF,IAAM,oBAAoB;AAG1B,IAAM,oBAAoB;AAG1B,IAAM,cAAc;;;ADzB3B,IAAM,mBAA8B,CAAC,MAAM,EAAE,QAAQ,UAAU,IAAI;AACnE,IAAM,iBAA0B,CAAC,UAAU,QAAQ,MAAM,YAAY,MAAM,IAAI,KAAK,MAAM,OAAO,IAAI,MAAM,KAAK;AAwBzG,SAAS,kBACd,YACA,MACA,QACA,YAAuB,kBACJ;AACnB,MAAI,CAAC,WAAW,KAAK;AACnB,WAAO,EAAE,IAAI,OAAO,QAAQ,WAAW;AAAA,EACzC;AAEA,QAAM,aAAa,UAAU,IAAI;AACjC,QAAM,UAAU,WAAW,SAAS;AACpC,MAAI,YAAY,YAAY;AAC1B,WAAO,EAAE,IAAI,OAAO,QAAQ,YAAY;AAAA,EAC1C;AAGA,MAAI,QAAQ;AACZ,QAAM,SAAS,KAAK,IAAI,QAAQ,QAAQ,WAAW,MAAM;AACzD,SAAO,QAAQ,UAAU,QAAQ,WAAW,KAAK,MAAM,WAAW,WAAW,KAAK,GAAG;AACnF;AAAA,EACF;AAEA,MAAI,aAAa,QAAQ;AACzB,MAAI,UAAU,WAAW;AACzB,SAAO,aAAa,SAAS,UAAU,SAAS,QAAQ,WAAW,aAAa,CAAC,MAAM,WAAW,WAAW,UAAU,CAAC,GAAG;AACzH;AACA;AAAA,EACF;AAEA,aAAW,IAAI,SAAS,MAAM;AAC5B,UAAM,cAAc,aAAa;AACjC,QAAI,cAAc,GAAG;AACnB,iBAAW,OAAO,OAAO,WAAW;AAAA,IACtC;AACA,UAAM,YAAY,WAAW,MAAM,OAAO,OAAO;AACjD,QAAI,UAAU,SAAS,GAAG;AACxB,iBAAW,OAAO,OAAO,SAAS;AAAA,IACpC;AAAA,EACF,GAAG,MAAM;AAET,SAAO,EAAE,IAAI,KAAK;AACpB;AAMO,SAAS,yBACd,KACA,UACA,MACA,QACA,QAC0B;AAC1B,MAAI,CAAC,SAAS,KAAK;AACjB,WAAO,EAAE,IAAI,OAAO,QAAQ,WAAW;AAAA,EACzC;AACA,MAAI,SAAS,QAAQ,KAAK;AACxB,UAAM,IAAI,MAAM,6DAA6D;AAAA,EAC/E;AAEA,QAAM,YAAY,OAAO,aAAa;AACtC,QAAM,UAAU,OAAO,WAAW;AAClC,MAAI;AACJ,MAAI;AACF,cAAU,OAAO,MAAM,UAAU,IAAI,GAAG,OAAO,MAAM;AAAA,EACvD,SAAS,OAAO;AACd,YAAQ,EAAE,MAAM,eAAe,SAAS,kDAAkD,OAAO,MAAM,CAAC;AACxG,WAAO,EAAE,IAAI,OAAO,QAAQ,cAAc;AAAA,EAC5C;AAEA,MAAI,SAAS,MAAM;AACjB,8BAA0B,SAAS,QAAQ;AAAA,EAC7C,GAAG,MAAM;AACT,SAAO,EAAE,IAAI,KAAK;AACpB;AAiBO,SAAS,gBACd,QACA,SACiB;AACjB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AACJ,QAAM,YAAY,OAAO,aAAa;AACtC,QAAM,UAAU,OAAO,WAAW;AAElC,MAAI,CAAC,WAAW,KAAK;AACnB,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,MAAI,WAAW,QAAQ,KAAK;AAC1B,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,MAAI,CAAC,kBAAkB,KAAK;AAC1B,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,MAAI,kBAAkB,QAAQ,KAAK;AACjC,UAAM,IAAI,MAAM,sEAAsE;AAAA,EACxF;AAEA,MAAI,kBAAiC;AAGrC,QAAM,wBAAwB,CAAC,WAA6B;AAC1D,UAAM,OAAO,UAAU,WAAW,SAAS,CAAC;AAC5C,QAAI,oBAAoB,MAAM;AAC5B,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,yBAAyB,KAAK,mBAAmB,MAAM,QAAQ;AAAA,MAC5E;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,OAAO,IAAI;AACb,wBAAkB;AAAA,IACpB;AACA,WAAO,OAAO;AAAA,EAChB;AAEA,QAAM,0BAA0B,CAAC,aAA0C;AACzE,QAAI;AACF,YAAM,QAAQ,kCAAkC,UAAU,MAAM;AAChE,aAAO,UAAU,UAAU,KAAK,CAAC;AAAA,IACnC,SAAS,OAAO;AACd,cAAQ,EAAE,MAAM,mBAAmB,SAAS,kDAAkD,OAAO,MAAM,CAAC;AAC5G,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,YAAY,MAAuB;AACvC,UAAM,OAAO,UAAU,WAAW,SAAS,CAAC;AAC5C,UAAM,UAAU,KAAK,SAAS;AAC9B,UAAM,iBAAiB,kBAAkB,SAAS;AAElD,QAAI,CAAC,WAAW,CAAC,gBAAgB;AAC/B,YAAM,UAAU,SAAS,eAAe;AACxC,UAAI,QAAQ,SAAS,GAAG;AAGtB,cAAM,aAAa,yBAAyB,KAAK,mBAAmB,SAAS,aAAa;AAAA,UACxF;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AACD,YAAI,CAAC,WAAW,IAAI;AAClB,iBAAO,EAAE,QAAQ,WAAW,YAAY,KAAK;AAAA,QAC/C;AACA,cAAM,QAAQ,kCAAkC,mBAAmB,MAAM;AACzE,cAAM,gBAAgB,UAAU,KAAK;AACrC,0BAAkB,YAAY,eAAe,aAAa,SAAS;AACnE,0BAAkB,UAAU,aAAa;AACzC,eAAO,EAAE,QAAQ,UAAU;AAAA,MAC7B;AACA,aAAO,EAAE,QAAQ,QAAQ;AAAA,IAC3B;AAEA,QAAI,WAAW,CAAC,gBAAgB;AAC9B,YAAM,KAAK,sBAAsB,WAAW;AAC5C,aAAO,EAAE,QAAQ,QAAQ,GAAI,CAAC,MAAM,EAAE,YAAY,KAAK,EAAG;AAAA,IAC5D;AAEA,QAAI,CAAC,WAAW,gBAAgB;AAC9B,YAAM,sBAAsB,wBAAwB,iBAAiB;AACrE,UAAI,wBAAwB,MAAM;AAChC,0BAAkB,YAAY,qBAAqB,aAAa,SAAS;AACzE,0BAAkB,UAAU,mBAAmB;AAC/C,eAAO,EAAE,QAAQ,cAAc;AAAA,MACjC;AACA,aAAO,EAAE,QAAQ,eAAe,YAAY,KAAK;AAAA,IACnD;AAEA,UAAM,kBAAkB,wBAAwB,iBAAiB;AACjE,QAAI,oBAAoB,MAAM;AAC5B,YAAM,eAAe,UAAU,OAAQ,SAAS,eAAe;AAC/D,UAAI,aAAa;AACjB,UAAI,aAAa,SAAS,GAAG;AAC3B,0BAAkB,YAAY,cAAc,aAAa,SAAS;AAClE,cAAM,iBAAiB,yBAAyB,KAAK,mBAAmB,cAAc,aAAa;AAAA,UACjG;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AACD,YAAI,CAAC,eAAe,GAAI,cAAa;AAAA,MACvC;AACA,aAAO,EAAE,QAAQ,QAAQ,GAAI,cAAc,EAAE,YAAY,KAAK,EAAG;AAAA,IACnE;AAEA,QAAI,oBAAoB,MAAM;AAC5B,YAAM,SAAS,SAAS,UAAU;AAClC,UAAI,WAAW,eAAe;AAC5B,0BAAkB,YAAY,iBAAiB,aAAa,SAAS;AACrE,0BAAkB,UAAU,eAAe;AAC3C,eAAO,EAAE,QAAQ,cAAc;AAAA,MACjC,OAAO;AACL,cAAM,KAAK,sBAAsB,WAAW;AAC5C,eAAO,EAAE,QAAQ,QAAQ,GAAI,CAAC,MAAM,EAAE,YAAY,KAAK,EAAG;AAAA,MAC5D;AAAA,IACF,OAAO;AACL,wBAAkB;AAClB,aAAO,EAAE,QAAQ,aAAa;AAAA,IAChC;AAAA,EACF;AAEA,QAAM,eAAe,CACnB,GACA,gBACG;AACH,QAAI,YAAY,WAAW,qBAAqB,YAAY,WAAW,aAAa;AAClF;AAAA,IACF;AAEA,0BAAsB,iBAAiB;AAAA,EACzC;AAIA,QAAM,kBAAkB,UAAU;AAElC,aAAW,QAAQ,YAAY;AAE/B,SAAO;AAAA,IACL;AAAA,IACA,iBAAiBA,MAA8B;AAC7C,YAAM,OAAO,UAAUA,IAAG;AAC1B,YAAM,SAAS,kBAAkB,YAAY,MAAM,mBAAmB,SAAS;AAI/E,UAAI,OAAO,MAAM,OAAO,WAAW,aAAa;AAC9C,0BAAkB,UAAU,IAAI;AAAA,MAClC;AACA,aAAO;AAAA,IACT;AAAA,IACA,gBAAgB,IAA0B;AAExC,YAAM,OAAO,GAAG,QAAQ,cAAc;AACtC,aACE,OAAO,SAAS,YAChB,SAAS,QACT,oBAAoB,QACnB,KAAiC,mBAAmB;AAAA,IAEzD;AAAA,IACA,UAAU;AACR,iBAAW,UAAU,YAAY;AAAA,IACnC;AAAA,EACF;AACF;;;AEzSO,SAAS,qBAAqB,WAAsB,cAAc,YAAuB;AAC9F,SAAO,IAAI,MAAM,WAAW;AAAA,IAC1B,IAAI,QAAQ,MAAM,UAAU;AAC1B,UAAI,SAAS,iBAAiB;AAC5B,eAAO,MAAM;AACX,gBAAM,QAAQ,OAAO,cAAc;AACnC,iBAAO,QAAQ,EAAE,GAAG,OAAO,CAAC,WAAW,GAAG,KAAK,IAAI;AAAA,QACrD;AAAA,MACF;AACA,UAAI,SAAS,sBAAsB;AACjC,eAAO,CAAC,OAAeC,WAAmB;AAExC,cAAI,UAAU,YAAa;AAC3B,iBAAO,mBAAmB,OAAOA,MAAK;AAAA,QACxC;AAAA,MACF;AACA,YAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AAChD,aAAO,OAAO,UAAU,aAAc,MAAmB,KAAK,MAAM,IAAI;AAAA,IAC1E;AAAA,EACF,CAAC;AACH;;;ACxBA,SAAS,oBAAoB,eAAe,aAAa,mBAAmB;;;ACL5E,SAAS,QAAQ,iBAAiB;AAiB3B,IAAM,sBAAsB,IAAI,UAA2B,mBAAmB;AAErF,IAAM,eAAe,oBAAI,QAAyB;AAElD,IAAM,mBAA8B,CAAC,UAAU,QAAQ,KAAK,WAAW,MAAM,IAAI,KAAK,MAAM,OAAO,EAAE;AAS9F,SAAS,uBACd,QACA,UAAmC,CAAC,GAC5B;AACR,QAAM,OAAO,QAAQ,aAAa;AAClC,MAAI,aAAa,IAAI,MAAM,GAAG;AAC5B,SAAK,EAAE,MAAM,wBAAwB,SAAS,0DAA0D,CAAC;AAAA,EAC3G;AACA,eAAa,IAAI,MAAM;AAEvB,SAAO,IAAI,OAAwB;AAAA,IACjC,KAAK;AAAA,IAEL,OAAO;AAAA,MACL,OAAwB;AACtB,eAAO,EAAE,WAAW,MAAM;AAAA,MAC5B;AAAA,MACA,MAAM,IAAI,OAAwB;AAChC,YAAI,CAAC,GAAG,WAAY,QAAO,EAAE,WAAW,MAAM;AAC9C,YAAI,OAAO,gBAAgB,EAAE,EAAG,QAAO,EAAE,WAAW,MAAM;AAC1D,eAAO,EAAE,WAAW,KAAK;AAAA,MAC3B;AAAA,IACF;AAAA,IAEA,OAAO;AACL,aAAO;AAAA,QACL,OAAO,MAAM;AACX,gBAAM,QAAQ,oBAAoB,SAAS,KAAK,KAAK;AACrD,cAAI,OAAO,WAAW;AACpB,kBAAM,SAAS,OAAO,iBAAiB,KAAK,MAAM,GAAG;AACrD,gBAAI,CAAC,OAAO,IAAI;AACd,kBAAI,OAAO,WAAW,YAAY;AAChC,wBAAQ,gBAAgB,QAAQ,IAAI;AACpC,qBAAK,EAAE,MAAM,eAAe,SAAS,uBAAuB,OAAO,MAAM,GAAG,CAAC;AAAA,cAC/E;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,QACA,UAAU;AACR,uBAAa,OAAO,MAAM;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH;;;AC1EA,SAAS,UAAAC,SAAQ,aAAAC,kBAAiB;AAIlC,SAAS,oCAAoC,kBAAAC,uBAAsB;AACnE,SAAS,2CAA2C;AAGpD,SAAS,gBAAgB,iBAAiB,8BAA8B;AAYjE,IAAM,sBAAsB,IAAID,WAA2B,mBAAmB;AASrF,SAAS,cAAc,MAA2C;AAChE,QAAM,MAAMC,gBAAe,SAAS,KAAK,KAAK;AAC9C,MAAI,CAAC,IAAK,QAAO;AACjB,MACE,OAAO,QAAQ,YACf,UAAU,OAAO,IAAI,QACrB,aAAa,OAAO,IAAI,WACxB,OAAO,IAAI,YAAY,YACvB,aAAc,IAAI,WACjB,IAAI,QAAoC,mBAAmB,KAC5D;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,mBACP,MACA,OACgB;AAChB,QAAM,aAAa,cAAc,IAAI;AACrC,MAAI,CAAC,WAAY,QAAO;AAExB,SAAO;AAAA,IACL;AAAA,IACA,WAAW;AAAA,IACX,WAAW,QAAQ;AAAA;AAAA,EACrB;AACF;AAGA,SAAS,kBACP,WACA,iBACA,MACA,UACA,QACS;AACT,QAAM,YAAY,mBAAmB,MAAM,QAAQ;AACnD,QAAM,UAAU,mBAAmB,MAAM,MAAM;AAC/C,MAAI,cAAc,QAAQ,YAAY,KAAM,QAAO;AAEnD,YAAU,mBAAmB,iBAAiB,EAAE,QAAQ,WAAW,MAAM,QAAQ,CAAC;AAClF,SAAO;AACT;AAEA,SAAS,oBACP,WACA,mBACA,YACA,YACA,UACM;AACN,QAAM,MAAM,WAAW;AACvB,QAAM,QAAQ,CAAC,MAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC;AACzD,QAAM,YAAY,oCAAoC,YAAY,MAAM,UAAU,CAAC;AACnF,QAAM,UAAU,oCAAoC,YAAY,MAAM,QAAQ,CAAC;AAC/E,YAAU,mBAAmB,mBAAmB,EAAE,QAAQ,WAAW,MAAM,QAAQ,CAAC;AACtF;AAEA,IAAMC,oBAA8B,CAAC,UAAU,QAAQ,KAAK,WAAW,MAAM,IAAI,KAAK,MAAM,OAAO,EAAE;AA0B9F,SAAS,uBAAuB,SAA0C;AAC/E,QAAM,EAAE,WAAW,WAAW,QAAQ,WAAW,IAAI;AACrD,QAAM,OAAO,QAAQ,aAAaA;AAClC,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,MAAI,0BAA0B;AAG9B,MAAI,YAA8B;AAClC,MAAI,eAA4B;AAEhC,WAAS,cAAc,KAAsB;AAC3C,QAAI,iBAAiB,OAAO,CAAC,WAAW;AACtC,kBAAY,eAAe,KAAK,WAAW,MAAM;AACjD,qBAAe;AAAA,IACjB;AACA,WAAO;AAAA,EACT;AAEA,SAAO,IAAIH,QAAwB;AAAA,IACjC,KAAK;AAAA,IAEL,OAAO;AAAA,MACL,OAAwB;AACtB,eAAO,EAAE,WAAW,MAAM,kBAAkB,KAAK;AAAA,MACnD;AAAA,MACA,MAAM,IAAI,MAAM,WAAW,UAA2B;AACpD,cAAM,SAAS,GAAG,QAAQ,mBAAmB;AAG7C,YAAI,QAAQ;AACV,iBAAO,EAAE,WAAW,QAAQ,kBAAkB,KAAK,iBAAiB;AAAA,QACtE;AAGA,YAAI,mBAAmB,KAAK;AAC5B,YAAI,GAAG,gBAAgB,GAAG,YAAY;AACpC,gBAAM,MAAM,cAAc,SAAS,GAAG;AACtC,6BAAmB,gBAAgB,KAAK,SAAS,UAAU,MAAM;AAAA,QACnE;AAEA,eAAO;AAAA,UACL,WAAW,KAAK,cAAc,OAAO,OAAO,KAAK;AAAA,UACjD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,OAAO;AACL,aAAO;AAAA,QACL,OAAO,MAAM,WAAW;AACtB,gBAAM,cAAc,oBAAoB,SAAS,KAAK,KAAK;AAC3D,gBAAM,kBAAkB,oBAAoB,SAAS,SAAS;AAG9D,cACE,aAAa,aAAa,QAC1B,YAAY,cAAc,iBAAiB,WAC3C;AACA,kBAAM,MAAM,cAAc,KAAK,MAAM,GAAG;AACxC,kBAAM,WAAW,uBAAuB,KAAK,YAAY,UAAU,MAAM;AACzE,kBAAM,SAAS,uBAAuB,KAAK,YAAY,UAAU,IAAI;AACrE,gBAAI,aAAa,QAAQ,WAAW,MAAM;AACxC,oBAAM,KAAK,kBAAkB,WAAW,iBAAiB,MAAM,UAAU,MAAM;AAC/E,kBAAI,CAAC,MAAM,CAAC,yBAAyB;AACnC,0CAA0B;AAC1B,qBAAK,EAAE,MAAM,wBAAwB,SAAS,kEAA6D,CAAC;AAAA,cAC9G;AAAA,YACF;AAEA,gBAAI,YAAY;AACd;AAAA,gBACE;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,YAAY,UAAU;AAAA,gBACtB,YAAY,UAAU;AAAA,cACxB;AAAA,YACF;AACA;AAAA,UACF;AAGA,cACE,KAAK,SAAS,MACb,KAAK,MAAM,cAAc,UAAU,aAClC,KAAK,MAAM,QAAQ,UAAU,MAC/B;AACA,kBAAM,EAAE,QAAQ,KAAK,IAAI,KAAK,MAAM;AACpC,kBAAM,KAAK,kBAAkB,WAAW,iBAAiB,MAAM,QAAQ,IAAI;AAC3E,gBAAI,CAAC,MAAM,CAAC,yBAAyB;AACnC,wCAA0B;AAC1B,mBAAK,EAAE,MAAM,wBAAwB,SAAS,kEAA6D,CAAC;AAAA,YAC9G;AAIA,gBAAI,YAAY;AACd,oBAAM,MAAM,cAAc,KAAK,MAAM,GAAG;AACxC,oBAAM,aAAa,gBAAgB,KAAK,MAAM;AAC9C,oBAAM,WAAW,gBAAgB,KAAK,IAAI;AAC1C,kBAAI,eAAe,QAAQ,aAAa,MAAM;AAC5C,oCAAoB,WAAW,mBAAmB,YAAY,YAAY,QAAQ;AAAA,cACpF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAWO,SAAS,aAAa,MAAkB,QAAgB,MAAe,WAA6B;AACzG,MAAI,CAAC,oBAAoB,SAAS,KAAK,KAAK,GAAG;AAC7C,KAAC,aAAaG,mBAAkB,EAAE,MAAM,6BAA6B,SAAS,yDAAyD,CAAC;AACxI;AAAA,EACF;AACA,QAAM,WAAW,CAAC,MAAc,KAAK,IAAI,GAAG,KAAK,MAAM,CAAC,CAAC;AACzD,OAAK;AAAA,IACH,KAAK,MAAM,GAAG,QAAQ,qBAAqB;AAAA,MACzC,QAAQ,SAAS,MAAM;AAAA,MACvB,MAAM,SAAS,QAAQ,MAAM;AAAA,IAC/B,CAAC;AAAA,EACH;AACF;;;AFhLO,SAAS,oBACd,QACA,SAC+D;AAC/D,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,mBAAmB,QAAQ,cAAc;AAC/C,QAAM,EAAE,kBAAkB,IAAI;AAE9B,MAAI,oBAAoB,CAAC,QAAQ,WAAW;AAC1C,UAAM,IAAI,MAAM,mEAAmE;AAAA,EACrF;AACA,QAAM,EAAE,KAAK,SAAS,WAAW,IAAI,mBAAmB,mBAAmB,MAAM;AACjF,QAAM,UAAU;AAChB,QAAM,cAAc,mBAChB,qBAAqB,QAAQ,WAAW,eAAe,IACvD,QAAQ;AAEZ,QAAM,UAAoB;AAAA,IACxB,YAAY,mBAAmB,EAAE,SAAS,WAAW,CAAC;AAAA,IACtD,cAAc,aAAa,QAAQ,qBAAqB,CAAC,GAAG,eAAe;AAAA,IAC3E,YAAY,QAAQ,eAAe;AAAA,EACrC;AAIA,MAAI,QAAQ,QAAQ;AAClB,YAAQ,KAAK,uBAAuB,QAAQ,QAAQ,EAAE,WAAW,QAAQ,UAAU,CAAC,CAAC;AAAA,EACvF;AAEA,MAAI,oBAAoB,QAAQ,WAAW;AACzC,YAAQ;AAAA,MACN,uBAAuB;AAAA,QACrB,WAAW,QAAQ;AAAA,QACnB,WAAW,QAAQ;AAAA,QACnB;AAAA,QACA,mBAAmB,QAAQ;AAAA,QAC3B,QAAQ,QAAQ;AAAA,QAChB,YAAY,QAAQ;AAAA,QACpB,WAAW,QAAQ;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,KAAK,QAAQ;AACjC;;;AGjGA,SAAS,kBAAAC,iBAAgB,mBAAAC,kBAAiB,0BAAAC,+BAA8B;","names":["doc","value","Plugin","PluginKey","ySyncPluginKey","defaultOnWarning","buildCursorMap","cursorMapLookup","reverseCursorMapLookup"]}
|
|
1
|
+
{"version":3,"sources":["../src/bridge.ts","../src/types.ts","../src/awareness-proxy.ts","../src/collab-plugins.ts","../src/bridge-sync-plugin.ts","../src/cursor-sync-plugin.ts","../src/index.ts"],"sourcesContent":["import type { Node } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport { prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, ySyncPluginKey } from 'y-prosemirror'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport { XmlElement as YXmlElement, XmlText as YXmlText } from 'yjs'\nimport type { Normalize, OnError } from '@pm-cm/core'\nimport type { BootstrapResult, YjsBridgeConfig, YjsBridgeHandle } from './types.js'\nimport { ORIGIN_INIT, ORIGIN_TEXT_TO_PM, ORIGIN_PM_TO_TEXT } from './types.js'\n\nconst defaultNormalize: Normalize = (s) => s.replace(/\\r\\n?/g, '\\n')\nconst defaultOnError: OnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause)\n\n/** Result of {@link replaceSharedText}. */\nexport type ReplaceTextResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' }\n | { ok: false; reason: 'detached' }\n\n/** Result of {@link replaceSharedProseMirror}. */\nexport type ReplaceProseMirrorResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' }\n | { ok: false; reason: 'parse-error' }\n | { ok: false; reason: 'detached' }\n\n/**\n * Union of all replace-result types. Kept for backward compatibility.\n * Prefer the narrower {@link ReplaceTextResult} / {@link ReplaceProseMirrorResult}.\n */\nexport type ReplaceResult = ReplaceTextResult | ReplaceProseMirrorResult\n\n/**\n * Replace `Y.Text` content using a minimal diff (common prefix/suffix trimming).\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedText(\n sharedText: YText,\n next: string,\n origin: unknown,\n normalize: Normalize = defaultNormalize,\n): ReplaceTextResult {\n if (!sharedText.doc) {\n return { ok: false, reason: 'detached' }\n }\n\n const normalized = normalize(next)\n const current = sharedText.toString()\n if (current === normalized) {\n return { ok: false, reason: 'unchanged' }\n }\n\n // Minimal diff: find common prefix and suffix, replace only the changed middle.\n let start = 0\n const minLen = Math.min(current.length, normalized.length)\n while (start < minLen && current.charCodeAt(start) === normalized.charCodeAt(start)) {\n start++\n }\n\n let endCurrent = current.length\n let endNext = normalized.length\n while (endCurrent > start && endNext > start && current.charCodeAt(endCurrent - 1) === normalized.charCodeAt(endNext - 1)) {\n endCurrent--\n endNext--\n }\n\n sharedText.doc.transact(() => {\n const deleteCount = endCurrent - start\n if (deleteCount > 0) {\n sharedText.delete(start, deleteCount)\n }\n const insertStr = normalized.slice(start, endNext)\n if (insertStr.length > 0) {\n sharedText.insert(start, insertStr)\n }\n }, origin)\n\n return { ok: true }\n}\n\n/**\n * Replace `Y.XmlFragment` by parsing serialized text into a ProseMirror document.\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedProseMirror(\n doc: Doc,\n fragment: YXmlFragment,\n text: string,\n origin: unknown,\n config: Pick<YjsBridgeConfig, 'schema' | 'parse' | 'normalize' | 'onError'>,\n): ReplaceProseMirrorResult {\n if (!fragment.doc) {\n return { ok: false, reason: 'detached' }\n }\n if (fragment.doc !== doc) {\n throw new Error('fragment belongs to a different Y.Doc than the provided doc')\n }\n\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n let nextDoc: Node\n try {\n nextDoc = config.parse(normalize(text), config.schema)\n } catch (error) {\n onError({ code: 'parse-error', message: 'failed to parse text into ProseMirror document', cause: error })\n return { ok: false, reason: 'parse-error' }\n }\n\n // Semantic equality check: skip if PM document is unchanged\n try {\n const currentDoc = yXmlFragmentToProseMirrorRootNode(fragment, config.schema)\n if (currentDoc.eq(nextDoc)) {\n return { ok: false, reason: 'unchanged' }\n }\n } catch {\n // If conversion fails, proceed with replacement\n }\n\n // Try incremental update first; fall back to full replacement on failure\n if (!tryIncrementalFragmentUpdate(doc, fragment, nextDoc, origin)) {\n doc.transact(() => {\n prosemirrorToYXmlFragment(nextDoc, fragment)\n }, origin)\n }\n return { ok: true }\n}\n\n/**\n * Convert a ProseMirror node to a new (unattached) Y.XmlElement.\n * Handles attributes and text content including marks.\n */\nfunction pmNodeToYXmlElement(node: Node): YXmlElement {\n const xmlEl = new YXmlElement(node.type.name)\n for (const [key, value] of Object.entries(node.attrs)) {\n if (value != null) xmlEl.setAttribute(key, value)\n }\n // Collect children first, then insert all at once to avoid\n // reading .length on unattached Yjs types\n const children: (YXmlElement | YXmlText)[] = []\n node.forEach((child) => {\n if (child.isText && child.text) {\n const xmlText = new YXmlText()\n const attrs: Record<string, unknown> = {}\n for (const mark of child.marks) {\n attrs[mark.type.name] = mark.attrs && Object.keys(mark.attrs).length > 0 ? mark.attrs : true\n }\n xmlText.insert(0, child.text, Object.keys(attrs).length > 0 ? attrs : undefined)\n children.push(xmlText)\n } else if (!child.isLeaf) {\n children.push(pmNodeToYXmlElement(child))\n }\n })\n if (children.length > 0) {\n xmlEl.insert(0, children)\n }\n return xmlEl\n}\n\n/** Get text content of a Y.XmlElement's children. */\nfunction getXmlElementText(xmlEl: YXmlElement): string {\n let text = ''\n for (let j = 0; j < xmlEl.length; j++) {\n const child = xmlEl.get(j)\n if (child instanceof YXmlText) text += child.toString()\n }\n return text\n}\n\n/**\n * Update a Y.XmlElement's text content in-place using minimal diff.\n * Preserves the XmlText identity when possible.\n */\nfunction updateXmlElementTextContent(xmlEl: YXmlElement, pmNode: Node): void {\n if (xmlEl.length === 0 || !(xmlEl.get(0) instanceof YXmlText)) return\n const xmlText = xmlEl.get(0) as YXmlText\n const oldText = xmlText.toString()\n const newText = pmNode.textContent\n if (oldText === newText) return\n\n // Minimal diff: find common prefix and suffix, replace only the changed middle\n let start = 0\n const minLen = Math.min(oldText.length, newText.length)\n while (start < minLen && oldText.charCodeAt(start) === newText.charCodeAt(start)) {\n start++\n }\n\n let oldEnd = oldText.length\n let newEnd = newText.length\n while (oldEnd > start && newEnd > start && oldText.charCodeAt(oldEnd - 1) === newText.charCodeAt(newEnd - 1)) {\n oldEnd--\n newEnd--\n }\n\n if (oldEnd > start) xmlText.delete(start, oldEnd - start)\n if (newEnd > start) xmlText.insert(start, newText.slice(start, newEnd))\n}\n\n/**\n * Try to update a Y.XmlFragment incrementally by diffing against a new PM document.\n * Uses prefix/suffix matching to preserve unchanged Yjs nodes, protecting\n * relative positions (remote cursors) and undo history.\n *\n * Returns `true` if the incremental update succeeded, `false` to fall back to full replacement.\n */\nfunction tryIncrementalFragmentUpdate(\n ydoc: Doc,\n fragment: YXmlFragment,\n nextDoc: Node,\n origin: unknown,\n): boolean {\n const oldCount = fragment.length\n const newCount = nextDoc.childCount\n\n // Validate all old children are XmlElements (expected from y-prosemirror)\n for (let i = 0; i < oldCount; i++) {\n if (!(fragment.get(i) instanceof YXmlElement)) return false\n }\n\n // Find common prefix: same node type AND same text content\n let prefix = 0\n while (prefix < Math.min(oldCount, newCount)) {\n const oldChild = fragment.get(prefix) as YXmlElement\n const newChild = nextDoc.child(prefix)\n if (oldChild.nodeName !== newChild.type.name) break\n if (getXmlElementText(oldChild) !== newChild.textContent) break\n prefix++\n }\n\n // Find common suffix (avoid overlapping with prefix)\n let suffix = 0\n while (suffix < Math.min(oldCount - prefix, newCount - prefix)) {\n const oi = oldCount - 1 - suffix\n const ni = newCount - 1 - suffix\n const oldChild = fragment.get(oi) as YXmlElement\n const newChild = nextDoc.child(ni)\n if (oldChild.nodeName !== newChild.type.name) break\n if (getXmlElementText(oldChild) !== newChild.textContent) break\n suffix++\n }\n\n const oldMiddleLen = oldCount - prefix - suffix\n const newMiddleLen = newCount - prefix - suffix\n\n // Nothing changed in the middle\n if (oldMiddleLen === 0 && newMiddleLen === 0) return true\n\n ydoc.transact(() => {\n // For overlapping middle items with matching types, update text in-place\n const updateCount = Math.min(oldMiddleLen, newMiddleLen)\n for (let i = 0; i < updateCount; i++) {\n const idx = prefix + i\n const xmlEl = fragment.get(idx) as YXmlElement\n const pmNode = nextDoc.child(idx)\n if (xmlEl.nodeName === pmNode.type.name) {\n updateXmlElementTextContent(xmlEl, pmNode)\n } else {\n // Type changed: delete and insert replacement\n fragment.delete(idx, 1)\n fragment.insert(idx, [pmNodeToYXmlElement(pmNode)])\n }\n }\n\n // Delete extra old items beyond what was updated in-place\n if (oldMiddleLen > updateCount) {\n fragment.delete(prefix + updateCount, oldMiddleLen - updateCount)\n }\n\n // Insert new items beyond what was updated in-place\n for (let i = updateCount; i < newMiddleLen; i++) {\n fragment.insert(prefix + i, [pmNodeToYXmlElement(nextDoc.child(prefix + i))])\n }\n }, origin)\n\n return true\n}\n\n/** Options for {@link createYjsBridge}. */\nexport type YjsBridgeOptions = {\n initialText?: string\n /** Which side wins when both sharedText and sharedProseMirror exist and differ. Default `'text'`. */\n prefer?: 'text' | 'prosemirror'\n}\n\n/**\n * Create a collaborative bridge that keeps `Y.Text` and `Y.XmlFragment` in sync.\n *\n * Runs a synchronous bootstrap to reconcile existing state, then installs a\n * `Y.Text` observer for the text → ProseMirror direction.\n *\n * @throws If `sharedText` or `sharedProseMirror` belong to a different `Y.Doc`.\n */\nexport function createYjsBridge(\n config: YjsBridgeConfig,\n options?: YjsBridgeOptions,\n): YjsBridgeHandle {\n const {\n doc,\n sharedText,\n sharedProseMirror,\n schema,\n serialize,\n parse,\n } = config\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n\n if (!sharedText.doc) {\n throw new Error('sharedText is not attached to any Y.Doc')\n }\n if (sharedText.doc !== doc) {\n throw new Error('sharedText belongs to a different Y.Doc than the provided doc')\n }\n if (!sharedProseMirror.doc) {\n throw new Error('sharedProseMirror is not attached to any Y.Doc')\n }\n if (sharedProseMirror.doc !== doc) {\n throw new Error('sharedProseMirror belongs to a different Y.Doc than the provided doc')\n }\n\n let lastBridgedText: string | null = null\n\n /** Returns `true` if the parse succeeded. */\n const syncTextToProsemirror = (origin: unknown): boolean => {\n const text = normalize(sharedText.toString())\n if (lastBridgedText === text) {\n return true\n }\n\n const result = replaceSharedProseMirror(doc, sharedProseMirror, text, origin, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (result.ok || result.reason === 'unchanged') {\n lastBridgedText = text\n }\n return result.ok || result.reason === 'unchanged'\n }\n\n const sharedProseMirrorToText = (fragment: YXmlFragment): string | null => {\n try {\n const pmDoc = yXmlFragmentToProseMirrorRootNode(fragment, schema)\n return normalize(serialize(pmDoc))\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to convert ProseMirror fragment to text', cause: error })\n return null\n }\n }\n\n // Bootstrap\n const bootstrap = (): BootstrapResult => {\n const text = normalize(sharedText.toString())\n const hasText = text.length > 0\n const hasProsemirror = sharedProseMirror.length > 0\n\n if (!hasText && !hasProsemirror) {\n const initial = options?.initialText ?? ''\n if (initial.length > 0) {\n // Set Y.XmlFragment first, then derive Y.Text from serialize(parse(initial))\n // to ensure both shared types are in the same canonical form.\n const initResult = replaceSharedProseMirror(doc, sharedProseMirror, initial, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!initResult.ok) {\n return { source: 'initial', parseError: true }\n }\n const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema)\n const canonicalText = serialize(pmDoc)\n replaceSharedText(sharedText, canonicalText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(canonicalText)\n return { source: 'initial' }\n }\n return { source: 'empty' }\n }\n\n if (hasText && !hasProsemirror) {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n\n if (!hasText && hasProsemirror) {\n const textFromProsemirror = sharedProseMirrorToText(sharedProseMirror)\n if (textFromProsemirror !== null) {\n replaceSharedText(sharedText, textFromProsemirror, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(textFromProsemirror)\n return { source: 'prosemirror' }\n }\n return { source: 'prosemirror', parseError: true }\n }\n\n const prosemirrorText = sharedProseMirrorToText(sharedProseMirror)\n if (prosemirrorText === null) {\n const fallbackText = hasText ? text : (options?.initialText ?? '')\n let parseError = false\n if (fallbackText.length > 0) {\n replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize)\n const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!fallbackResult.ok) parseError = true\n }\n return { source: 'text', ...(parseError && { parseError: true }) }\n }\n\n if (prosemirrorText !== text) {\n const prefer = options?.prefer ?? 'text'\n if (prefer === 'prosemirror') {\n replaceSharedText(sharedText, prosemirrorText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(prosemirrorText)\n return { source: 'prosemirror' }\n } else {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n } else {\n lastBridgedText = text\n return { source: 'both-match' }\n }\n }\n\n const textObserver = (\n _: unknown,\n transaction: { origin: unknown },\n ) => {\n if (transaction.origin === ORIGIN_PM_TO_TEXT || transaction.origin === ORIGIN_INIT) {\n return\n }\n\n syncTextToProsemirror(ORIGIN_TEXT_TO_PM)\n }\n\n // Run bootstrap synchronously before installing the observer so that\n // an exception during bootstrap cannot leave a dangling observer.\n const bootstrapResult = bootstrap()\n\n sharedText.observe(textObserver)\n\n return {\n bootstrapResult,\n syncToSharedText(doc: Node): ReplaceTextResult {\n const text = serialize(doc)\n const result = replaceSharedText(sharedText, text, ORIGIN_PM_TO_TEXT, normalize)\n // Always update lastBridgedText unless truly failed (detached).\n // 'unchanged' means Y.Text already has this content — still need to\n // record it so the reverse observer doesn't trigger a redundant sync.\n if (result.ok || result.reason === 'unchanged') {\n lastBridgedText = normalize(text)\n }\n return result\n },\n isYjsSyncChange(tr: Transaction): boolean {\n // Internal meta shape from y-prosemirror's ySyncPlugin (tested against ^1.3.x).\n const meta = tr.getMeta(ySyncPluginKey)\n return (\n typeof meta === 'object' &&\n meta !== null &&\n 'isChangeOrigin' in meta &&\n (meta as Record<string, unknown>).isChangeOrigin === true\n )\n },\n dispose() {\n sharedText.unobserve(textObserver)\n },\n }\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { Serialize, Parse, Normalize, OnError } from '@pm-cm/core'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { ReplaceTextResult } from './bridge.js'\n\n/** Known warning codes emitted by the yjs bridge and plugins. */\nexport type WarningCode = 'bridge-already-wired' | 'sync-failed' | 'ysync-plugin-missing' | 'cursor-sync-not-installed'\n\n/** Structured warning event for non-fatal warnings. */\nexport type WarningEvent = {\n code: WarningCode\n message: string\n}\n\n/**\n * Warning handler callback for non-fatal warnings.\n *\n * Known codes:\n * - `'bridge-already-wired'` — the same bridge handle is wired to multiple plugin instances.\n * - `'sync-failed'` — `syncToSharedText` failed (e.g. Y.Text detached).\n * - `'ysync-plugin-missing'` — ySyncPlugin state is not available; cursor broadcast skipped.\n * - `'cursor-sync-not-installed'` — cursor sync plugin is not installed on the EditorView.\n */\nexport type OnWarning = (event: WarningEvent) => void\n\n/** Yjs transaction origin: text → ProseMirror direction. */\nexport const ORIGIN_TEXT_TO_PM = 'bridge:text-to-prosemirror'\n\n/** Yjs transaction origin: ProseMirror → text direction. */\nexport const ORIGIN_PM_TO_TEXT = 'bridge:prosemirror-to-text'\n\n/** Yjs transaction origin: bootstrap initialization. */\nexport const ORIGIN_INIT = 'bridge:init'\n\n/** Configuration for {@link createYjsBridge}. */\nexport type YjsBridgeConfig = {\n doc: Doc\n sharedText: YText\n sharedProseMirror: YXmlFragment\n schema: Schema\n serialize: Serialize\n parse: Parse\n normalize?: Normalize\n /** Called on non-fatal errors (e.g. parse failures). Defaults to `console.error`. */\n onError?: OnError\n}\n\n/**\n * Result of the bootstrap phase in {@link createYjsBridge}.\n * Indicates which source was used to initialize the shared types.\n */\nexport type BootstrapResult = {\n source: 'text' | 'prosemirror' | 'both-match' | 'empty' | 'initial'\n /** `true` when format conversion (parse or serialize) failed during bootstrap. The bridge is still usable but the affected shared type may be stale. */\n parseError?: boolean\n}\n\n/** Handle returned by {@link createYjsBridge}. */\nexport type YjsBridgeHandle = {\n /** Result of the synchronous bootstrap phase. */\n readonly bootstrapResult: BootstrapResult\n /** Serialize `doc` and push to `Y.Text` using minimal diff. */\n syncToSharedText(doc: Node): ReplaceTextResult\n /** Returns `true` if the transaction originated from `y-prosemirror` sync. */\n isYjsSyncChange(tr: Transaction): boolean\n /** Remove the Y.Text observer. Call when tearing down. */\n dispose(): void\n}\n","import type { Awareness } from 'y-protocols/awareness'\n\n/**\n * Create a Proxy around a Yjs {@link Awareness} that adapts the cursor sync\n * plugin's awareness field for y-prosemirror's `yCursorPlugin`.\n *\n * y-prosemirror (npm 1.3.7) hardcodes `\"cursor\"` in `createDecorations`,\n * but the cursor sync plugin writes to a separate field (default `\"pmCursor\"`)\n * to avoid conflicts with y-codemirror.next's `\"cursor\"` (Y.Text-based).\n *\n * This proxy:\n * - **`getStates()`**: remaps `cursorField` → `\"cursor\"` so yCursorPlugin\n * finds the PM cursor data under the hardcoded `\"cursor\"` key.\n * - **`getLocalState()`**: returns `cursor: null` so yCursorPlugin's\n * `updateCursorInfo` never tries to broadcast its own cursor.\n * - **`setLocalStateField(\"cursor\", …)`**: suppressed (no-op) so\n * yCursorPlugin cannot overwrite the field managed by the sync plugin.\n */\nexport function createAwarenessProxy(awareness: Awareness, cursorField = 'pmCursor'): Awareness {\n return new Proxy(awareness, {\n get(target, prop, receiver) {\n if (prop === 'getLocalState') {\n return () => {\n const state = target.getLocalState()\n // Hide \"cursor\" so yCursorPlugin's updateCursorInfo sees null\n return state ? { ...state, cursor: null } : state\n }\n }\n if (prop === 'setLocalStateField') {\n return (field: string, value: unknown) => {\n // Block yCursorPlugin's writes to \"cursor\"\n if (field === 'cursor') return\n target.setLocalStateField(field, value)\n }\n }\n if (prop === 'getStates') {\n return () => {\n const states = target.getStates()\n // Remap cursorField → \"cursor\" so yCursorPlugin reads PM cursor data\n const remapped = new Map<number, Record<string, unknown>>()\n states.forEach((state, clientId) => {\n remapped.set(clientId, {\n ...state as Record<string, unknown>,\n cursor: (state as Record<string, unknown>)[cursorField] ?? null,\n })\n })\n return remapped\n }\n }\n const value = Reflect.get(target, prop, receiver) as unknown\n return typeof value === 'function' ? (value as Function).bind(target) : value\n },\n }) as Awareness\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { EditorState, Plugin } from 'prosemirror-state'\nimport type { DecorationAttrs } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport type { Serialize, SerializeWithMap } from '@pm-cm/core'\nimport { initProseMirrorDoc, yCursorPlugin, ySyncPlugin, yUndoPlugin } from 'y-prosemirror'\nimport type { AbstractType, Text as YText, UndoManager } from 'yjs'\nimport type { XmlFragment as YXmlFragment } from 'yjs'\nimport { createAwarenessProxy } from './awareness-proxy.js'\nimport { createBridgeSyncPlugin } from './bridge-sync-plugin.js'\nimport { createCursorSyncPlugin } from './cursor-sync-plugin.js'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\n/** Yjs ↔ ProseMirror node mapping used by `y-prosemirror`. */\nexport type ProseMirrorMapping = Map<AbstractType<unknown>, Node | Node[]>\n\n/** Options forwarded to `yCursorPlugin` from y-prosemirror. */\nexport type YCursorPluginOpts = {\n awarenessStateFilter?: (currentClientId: number, userClientId: number, user: unknown) => boolean\n cursorBuilder?: (user: unknown, clientId: number) => HTMLElement\n selectionBuilder?: (user: unknown, clientId: number) => DecorationAttrs\n getSelection?: (state: EditorState) => unknown\n}\n\n/** Options forwarded to `yUndoPlugin` from y-prosemirror. */\nexport type YUndoPluginOpts = {\n protectedNodes?: Set<string>\n trackedOrigins?: unknown[]\n undoManager?: UndoManager | null\n}\n\n/** Options for {@link createCollabPlugins}. */\nexport type CollabPluginsOptions = {\n /** Shared ProseMirror document in Yjs. */\n sharedProseMirror: YXmlFragment\n awareness: Awareness\n cursorFieldName?: string\n serialize?: Serialize | SerializeWithMap\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n /**\n * Enable PM↔CM cursor sync. Default `false`.\n *\n * When enabled, an {@link createAwarenessProxy | awareness proxy} is applied\n * to suppress y-prosemirror's built-in cursor management.\n */\n cursorSync?: boolean\n /**\n * The shared `Y.Text` instance. When provided, the cursor sync plugin also\n * broadcasts CM-format cursor positions so remote `yCollab` instances render them.\n */\n sharedText?: YText\n /**\n * When provided, a bridge sync plugin is inserted before the cursor sync plugin\n * to ensure Y.Text is synced before cursor positions are computed. This guarantees\n * that serialize-based offsets match Y.Text indices.\n */\n bridge?: YjsBridgeHandle\n /** Extra options forwarded to `yCursorPlugin`. */\n yCursorPluginOpts?: YCursorPluginOpts\n /** Extra options forwarded to `yUndoPlugin`. */\n yUndoPluginOpts?: YUndoPluginOpts\n /** Called for non-fatal warnings. Propagated to child plugins. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * Bundle `ySyncPlugin`, `yCursorPlugin`, `yUndoPlugin` from y-prosemirror,\n * plus an optional PM↔CM cursor sync plugin.\n *\n * @throws If `cursorSync: true` but `serialize` is not provided.\n */\nexport function createCollabPlugins(\n schema: Schema,\n options: CollabPluginsOptions,\n): { plugins: Plugin[]; doc: Node; mapping: ProseMirrorMapping } {\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const enableCursorSync = options.cursorSync ?? false\n const { sharedProseMirror } = options\n\n if (enableCursorSync && !options.serialize) {\n throw new Error('createCollabPlugins: cursorSync requires serialize to be provided')\n }\n const { doc, mapping: rawMapping } = initProseMirrorDoc(sharedProseMirror, schema)\n const mapping = rawMapping as ProseMirrorMapping\n const pmAwareness = enableCursorSync\n ? createAwarenessProxy(options.awareness, cursorFieldName)\n : options.awareness\n\n const plugins: Plugin[] = [\n ySyncPlugin(sharedProseMirror, { mapping: rawMapping }),\n yCursorPlugin(pmAwareness, options.yCursorPluginOpts ?? {}),\n yUndoPlugin(options.yUndoPluginOpts),\n ]\n\n // Bridge sync plugin must run before cursor sync plugin so that\n // Y.Text is updated before cursor positions are computed.\n if (options.bridge) {\n plugins.push(createBridgeSyncPlugin(options.bridge, { onWarning: options.onWarning }))\n }\n\n if (enableCursorSync && options.serialize) {\n plugins.push(\n createCursorSyncPlugin({\n awareness: options.awareness,\n serialize: options.serialize,\n cursorFieldName,\n cmCursorFieldName: options.cmCursorFieldName,\n sharedText: options.sharedText,\n onWarning: options.onWarning,\n }),\n )\n }\n\n return { plugins, doc, mapping }\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\ntype BridgeSyncState = { needsSync: boolean }\n\ntype BridgeSyncFailure = { ok: false; reason: 'detached' }\n\n/** Options for {@link createBridgeSyncPlugin}. */\nexport type BridgeSyncPluginOptions = {\n /** Called when `syncToSharedText` fails (excludes `reason: 'unchanged'`). */\n onSyncFailure?: (result: BridgeSyncFailure, view: EditorView) => void\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/** ProseMirror plugin key for {@link createBridgeSyncPlugin}. Use to read the plugin state. */\nexport const bridgeSyncPluginKey = new PluginKey<BridgeSyncState>('pm-cm-bridge-sync')\n\nconst wiredBridges = new WeakSet<YjsBridgeHandle>()\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/**\n * ProseMirror plugin that automatically syncs PM doc changes to Y.Text\n * via the bridge handle. Skips Yjs-originated changes to avoid loops.\n *\n * A warning is logged if the same bridge handle is wired more than once.\n * The guard is cleaned up when the plugin is destroyed.\n */\nexport function createBridgeSyncPlugin(\n bridge: YjsBridgeHandle,\n options: BridgeSyncPluginOptions = {},\n): Plugin {\n const warn = options.onWarning ?? defaultOnWarning\n if (wiredBridges.has(bridge)) {\n warn({ code: 'bridge-already-wired', message: 'this bridge is already wired to another plugin instance' })\n }\n wiredBridges.add(bridge)\n\n return new Plugin<BridgeSyncState>({\n key: bridgeSyncPluginKey,\n\n state: {\n init(): BridgeSyncState {\n return { needsSync: false }\n },\n apply(tr, _prev): BridgeSyncState {\n if (!tr.docChanged) return { needsSync: false }\n if (bridge.isYjsSyncChange(tr)) return { needsSync: false }\n return { needsSync: true }\n },\n },\n\n view() {\n return {\n update(view) {\n const state = bridgeSyncPluginKey.getState(view.state)\n if (state?.needsSync) {\n const result = bridge.syncToSharedText(view.state.doc)\n if (!result.ok) {\n if (result.reason === 'detached') {\n options.onSyncFailure?.(result, view)\n warn({ code: 'sync-failed', message: `bridge sync failed: ${result.reason}` })\n }\n }\n }\n },\n destroy() {\n wiredBridges.delete(bridge)\n },\n }\n },\n })\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { Node } from 'prosemirror-model'\nimport type { EditorView } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport { absolutePositionToRelativePosition, ySyncPluginKey } from 'y-prosemirror'\nimport { createRelativePositionFromTypeIndex } from 'yjs'\nimport type { Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { Serialize, SerializeWithMap, CursorMap } from '@pm-cm/core'\nimport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nimport type { OnWarning } from './types.js'\n\n/** Plugin state for the cursor sync plugin. Read via {@link cursorSyncPluginKey}. */\nexport type CursorSyncState = {\n /** Pending CodeMirror cursor to broadcast. Set by {@link syncCmCursor}. */\n pendingCm: { anchor: number; head: number } | null\n /** Text offset mapped from the current PM selection anchor. `null` when no mapping is available. */\n mappedTextOffset: number | null\n}\n\n/** ProseMirror plugin key for {@link createCursorSyncPlugin}. Use to read the plugin state. */\nexport const cursorSyncPluginKey = new PluginKey<CursorSyncState>('pm-cm-cursor-sync')\n\n/**\n * Internal shape of `ySyncPluginKey` state from y-prosemirror.\n * Not exported by upstream — kept here for explicit tracking.\n * Tested against y-prosemirror ^1.3.x.\n */\ntype YSyncPluginState = { type: YXmlFragment; binding: { mapping: Map<unknown, unknown> } }\n\nfunction getYSyncState(view: EditorView): YSyncPluginState | null {\n const raw = ySyncPluginKey.getState(view.state) as Record<string, unknown> | undefined\n if (!raw) return null\n if (\n typeof raw === 'object' &&\n 'type' in raw && raw.type &&\n 'binding' in raw && raw.binding &&\n typeof raw.binding === 'object' &&\n 'mapping' in (raw.binding as Record<string, unknown>) &&\n (raw.binding as Record<string, unknown>).mapping instanceof Map\n ) {\n return raw as unknown as YSyncPluginState\n }\n return null\n}\n\nfunction toRelativePosition(\n view: EditorView,\n pmPos: number,\n): unknown | null {\n const ySyncState = getYSyncState(view)\n if (!ySyncState) return null\n\n return absolutePositionToRelativePosition(\n pmPos,\n ySyncState.type,\n ySyncState.binding.mapping as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- y-prosemirror internal mapping type\n )\n}\n\n/** Returns `false` when ySyncPlugin state is unavailable (plugin not installed). */\nfunction broadcastPmCursor(\n awareness: Awareness,\n cursorFieldName: string,\n view: EditorView,\n pmAnchor: number,\n pmHead: number,\n): boolean {\n const relAnchor = toRelativePosition(view, pmAnchor)\n const relHead = toRelativePosition(view, pmHead)\n if (relAnchor === null || relHead === null) return false\n\n awareness.setLocalStateField(cursorFieldName, { anchor: relAnchor, head: relHead })\n return true\n}\n\nfunction broadcastTextCursor(\n awareness: Awareness,\n cmCursorFieldName: string,\n sharedText: YText,\n textAnchor: number,\n textHead: number,\n): void {\n const len = sharedText.length\n const clamp = (v: number) => Math.max(0, Math.min(v, len))\n const relAnchor = createRelativePositionFromTypeIndex(sharedText, clamp(textAnchor))\n const relHead = createRelativePositionFromTypeIndex(sharedText, clamp(textHead))\n awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead })\n}\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/** Options for {@link createCursorSyncPlugin}. */\nexport type CursorSyncPluginOptions = {\n awareness: Awareness\n serialize: Serialize | SerializeWithMap\n cursorFieldName?: string\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n /**\n * When provided, the plugin also broadcasts CM-format cursor positions\n * (Y.Text relative positions) to the awareness field specified by\n * `cmCursorFieldName`, so that remote `yCollab` instances can render the cursor.\n */\n sharedText?: YText\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * ProseMirror plugin that synchronizes cursor positions between PM and CM via Yjs awareness.\n *\n * - PM → awareness: automatically broadcasts when the PM view is focused and selection changes.\n * - CM → awareness: triggered by dispatching {@link syncCmCursor}.\n */\nexport function createCursorSyncPlugin(options: CursorSyncPluginOptions): Plugin {\n const { awareness, serialize, sharedText } = options\n const warn = options.onWarning ?? defaultOnWarning\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const cmCursorFieldName = options.cmCursorFieldName ?? 'cursor'\n\n let warnedSyncPluginMissing = false\n\n // Cached cursor map (serialize-based) — rebuilt only when doc changes\n let cachedMap: CursorMap | null = null\n let cachedMapDoc: Node | null = null\n\n function getOrBuildMap(doc: Node): CursorMap {\n if (cachedMapDoc !== doc || !cachedMap) {\n cachedMap = buildCursorMap(doc, serialize)\n cachedMapDoc = doc\n }\n return cachedMap\n }\n\n return new Plugin<CursorSyncState>({\n key: cursorSyncPluginKey,\n\n state: {\n init(): CursorSyncState {\n return { pendingCm: null, mappedTextOffset: null }\n },\n apply(tr, prev, _oldState, newState): CursorSyncState {\n const cmMeta = tr.getMeta(cursorSyncPluginKey) as\n | { anchor: number; head: number }\n | undefined\n if (cmMeta) {\n return { pendingCm: cmMeta, mappedTextOffset: prev.mappedTextOffset }\n }\n\n // Compute PM → text offset when selection or doc changes\n let mappedTextOffset = prev.mappedTextOffset\n if (tr.selectionSet || tr.docChanged) {\n const map = getOrBuildMap(newState.doc)\n mappedTextOffset = cursorMapLookup(map, newState.selection.anchor)\n }\n\n return {\n pendingCm: prev.pendingCm !== null ? null : prev.pendingCm,\n mappedTextOffset,\n }\n },\n },\n\n view() {\n return {\n update(view, prevState) {\n const pluginState = cursorSyncPluginKey.getState(view.state)\n const prevPluginState = cursorSyncPluginKey.getState(prevState)\n\n // CM → awareness: broadcast when pendingCm is newly set\n if (\n pluginState?.pendingCm != null &&\n pluginState.pendingCm !== prevPluginState?.pendingCm\n ) {\n const map = getOrBuildMap(view.state.doc)\n const pmAnchor = reverseCursorMapLookup(map, pluginState.pendingCm.anchor)\n const pmHead = reverseCursorMapLookup(map, pluginState.pendingCm.head)\n if (pmAnchor !== null && pmHead !== null) {\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n }\n // Also broadcast CM-format cursor so remote yCollab can render it\n if (sharedText) {\n broadcastTextCursor(\n awareness,\n cmCursorFieldName,\n sharedText,\n pluginState.pendingCm.anchor,\n pluginState.pendingCm.head,\n )\n }\n return\n }\n\n // PM → awareness: auto-broadcast on selection/doc change when focused\n if (\n view.hasFocus() &&\n (view.state.selection !== prevState.selection ||\n view.state.doc !== prevState.doc)\n ) {\n const { anchor, head } = view.state.selection\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, anchor, head)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n // Also broadcast CM-format cursor so remote yCollab can render it.\n // When bridgeSyncPlugin runs before this plugin, Y.Text is already\n // synced so serialize-based offsets match Y.Text indices.\n if (sharedText) {\n const map = getOrBuildMap(view.state.doc)\n const textAnchor = cursorMapLookup(map, anchor)\n const textHead = cursorMapLookup(map, head)\n if (textAnchor !== null && textHead !== null) {\n broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead)\n }\n }\n }\n },\n }\n },\n })\n}\n\n/**\n * Dispatch a CodeMirror cursor offset (or range) to the cursor sync plugin.\n * The plugin will convert it to a ProseMirror position and broadcast via awareness.\n *\n * @param view - The ProseMirror EditorView that has the cursor sync plugin installed.\n * @param anchor - CodeMirror text offset for the anchor.\n * @param head - CodeMirror text offset for the head (defaults to `anchor` for a collapsed cursor).\n * @param onWarning - Optional warning callback. Default `console.warn`.\n */\nexport function syncCmCursor(view: EditorView, anchor: number, head?: number, onWarning?: OnWarning): void {\n if (!cursorSyncPluginKey.getState(view.state)) {\n (onWarning ?? defaultOnWarning)({ code: 'cursor-sync-not-installed', message: 'cursor sync plugin is not installed on this EditorView' })\n return\n }\n const sanitize = (v: number) => Math.max(0, Math.floor(v))\n view.dispatch(\n view.state.tr.setMeta(cursorSyncPluginKey, {\n anchor: sanitize(anchor),\n head: sanitize(head ?? anchor),\n }),\n )\n}\n","export { createYjsBridge, replaceSharedText, replaceSharedProseMirror } from './bridge.js'\nexport type { YjsBridgeOptions, ReplaceResult, ReplaceTextResult, ReplaceProseMirrorResult } from './bridge.js'\nexport { createAwarenessProxy } from './awareness-proxy.js'\nexport { createCollabPlugins } from './collab-plugins.js'\nexport type { CollabPluginsOptions, ProseMirrorMapping, YCursorPluginOpts, YUndoPluginOpts } from './collab-plugins.js'\nexport {\n ORIGIN_TEXT_TO_PM,\n ORIGIN_PM_TO_TEXT,\n ORIGIN_INIT,\n} from './types.js'\nexport type {\n BootstrapResult,\n YjsBridgeConfig,\n YjsBridgeHandle,\n WarningCode,\n WarningEvent,\n OnWarning,\n} from './types.js'\n\n// Cursor mapping re-exported from @pm-cm/core\nexport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nexport type { TextSegment, CursorMap, SerializeWithMap, CursorMapWriter, Matcher, MatchResult, MatchRun } from '@pm-cm/core'\n\n// Bridge sync plugin (auto PM→Y.Text wiring)\nexport { createBridgeSyncPlugin, bridgeSyncPluginKey } from './bridge-sync-plugin.js'\nexport type { BridgeSyncPluginOptions } from './bridge-sync-plugin.js'\n\n// Cursor sync plugin\nexport { createCursorSyncPlugin, cursorSyncPluginKey, syncCmCursor } from './cursor-sync-plugin.js'\nexport type { CursorSyncState, CursorSyncPluginOptions } from './cursor-sync-plugin.js'\n\n// Re-export types from @pm-cm/core\nexport { createCursorMapWriter, wrapSerialize } from '@pm-cm/core'\nexport type { Serialize, Parse, Normalize, OnError, ErrorCode, ErrorEvent } from '@pm-cm/core'\n"],"mappings":"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","names":["doc","value","Plugin","PluginKey","ySyncPluginKey","defaultOnWarning","buildCursorMap","cursorMapLookup","reverseCursorMapLookup"]}
|