@axure/y-prosemirror 1.3.7-fork.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.
@@ -0,0 +1,2268 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var Y = require('yjs');
6
+ var prosemirrorView = require('prosemirror-view');
7
+ var prosemirrorState = require('prosemirror-state');
8
+ require('y-protocols/awareness');
9
+ var mutex = require('lib0/mutex');
10
+ var PModel = require('prosemirror-model');
11
+ var math = require('lib0/math');
12
+ var object = require('lib0/object');
13
+ var set = require('lib0/set');
14
+ var diff = require('lib0/diff');
15
+ var error = require('lib0/error');
16
+ var random = require('lib0/random');
17
+ var environment = require('lib0/environment');
18
+ var dom = require('lib0/dom');
19
+ var eventloop = require('lib0/eventloop');
20
+ var map = require('lib0/map');
21
+ var sha256 = require('lib0/hash/sha256');
22
+ var buf = require('lib0/buffer');
23
+
24
+ function _interopNamespace(e) {
25
+ if (e && e.__esModule) return e;
26
+ var n = Object.create(null);
27
+ if (e) {
28
+ Object.keys(e).forEach(function (k) {
29
+ if (k !== 'default') {
30
+ var d = Object.getOwnPropertyDescriptor(e, k);
31
+ Object.defineProperty(n, k, d.get ? d : {
32
+ enumerable: true,
33
+ get: function () { return e[k]; }
34
+ });
35
+ }
36
+ });
37
+ }
38
+ n["default"] = e;
39
+ return Object.freeze(n);
40
+ }
41
+
42
+ var Y__namespace = /*#__PURE__*/_interopNamespace(Y);
43
+ var PModel__namespace = /*#__PURE__*/_interopNamespace(PModel);
44
+ var math__namespace = /*#__PURE__*/_interopNamespace(math);
45
+ var object__namespace = /*#__PURE__*/_interopNamespace(object);
46
+ var set__namespace = /*#__PURE__*/_interopNamespace(set);
47
+ var error__namespace = /*#__PURE__*/_interopNamespace(error);
48
+ var random__namespace = /*#__PURE__*/_interopNamespace(random);
49
+ var environment__namespace = /*#__PURE__*/_interopNamespace(environment);
50
+ var dom__namespace = /*#__PURE__*/_interopNamespace(dom);
51
+ var eventloop__namespace = /*#__PURE__*/_interopNamespace(eventloop);
52
+ var map__namespace = /*#__PURE__*/_interopNamespace(map);
53
+ var sha256__namespace = /*#__PURE__*/_interopNamespace(sha256);
54
+ var buf__namespace = /*#__PURE__*/_interopNamespace(buf);
55
+
56
+ /**
57
+ * The unique prosemirror plugin key for syncPlugin
58
+ *
59
+ * @public
60
+ */
61
+ const ySyncPluginKey = new prosemirrorState.PluginKey('y-sync');
62
+
63
+ /**
64
+ * The unique prosemirror plugin key for undoPlugin
65
+ *
66
+ * @public
67
+ * @type {PluginKey<import('./undo-plugin').UndoPluginState>}
68
+ */
69
+ const yUndoPluginKey = new prosemirrorState.PluginKey('y-undo');
70
+
71
+ /**
72
+ * The unique prosemirror plugin key for cursorPlugin
73
+ *
74
+ * @public
75
+ */
76
+ const yCursorPluginKey = new prosemirrorState.PluginKey('yjs-cursor');
77
+
78
+ /**
79
+ * Custom function to transform sha256 hash to N byte
80
+ *
81
+ * @param {Uint8Array} digest
82
+ */
83
+ const _convolute = digest => {
84
+ const N = 6;
85
+ for (let i = N; i < digest.length; i++) {
86
+ digest[i % N] = digest[i % N] ^ digest[i];
87
+ }
88
+ return digest.slice(0, N)
89
+ };
90
+
91
+ /**
92
+ * @param {any} json
93
+ */
94
+ const hashOfJSON = (json) => buf__namespace.toBase64(_convolute(sha256__namespace.digest(buf__namespace.encodeAny(json))));
95
+
96
+ /**
97
+ * @module bindings/prosemirror
98
+ */
99
+
100
+ /**
101
+ * @typedef {Object} BindingMetadata
102
+ * @property {ProsemirrorMapping} BindingMetadata.mapping
103
+ * @property {Map<import('prosemirror-model').MarkType, boolean>} BindingMetadata.isOMark - is overlapping mark
104
+ */
105
+
106
+ /**
107
+ * @return {BindingMetadata}
108
+ */
109
+ const createEmptyMeta = () => ({
110
+ mapping: new Map(),
111
+ isOMark: new Map()
112
+ });
113
+
114
+ /**
115
+ * @param {Y.Item} item
116
+ * @param {Y.Snapshot} [snapshot]
117
+ */
118
+ const isVisible = (item, snapshot) =>
119
+ snapshot === undefined
120
+ ? !item.deleted
121
+ : (snapshot.sv.has(item.id.client) && /** @type {number} */
122
+ (snapshot.sv.get(item.id.client)) > item.id.clock &&
123
+ !Y__namespace.isDeleted(snapshot.ds, item.id));
124
+
125
+ /**
126
+ * Either a node if type is YXmlElement or an Array of text nodes if YXmlText
127
+ * @typedef {Map<Y.AbstractType<any>, PModel.Node | Array<PModel.Node>>} ProsemirrorMapping
128
+ */
129
+
130
+ /**
131
+ * @typedef {Object} ColorDef
132
+ * @property {string} ColorDef.light
133
+ * @property {string} ColorDef.dark
134
+ */
135
+
136
+ /**
137
+ * @typedef {Object} YSyncOpts
138
+ * @property {Array<ColorDef>} [YSyncOpts.colors]
139
+ * @property {Map<string,ColorDef>} [YSyncOpts.colorMapping]
140
+ * @property {Y.PermanentUserData|null} [YSyncOpts.permanentUserData]
141
+ * @property {ProsemirrorMapping} [YSyncOpts.mapping]
142
+ * @property {function} [YSyncOpts.onFirstRender] Fired when the content from Yjs is initially rendered to ProseMirror
143
+ */
144
+
145
+ /**
146
+ * @type {Array<ColorDef>}
147
+ */
148
+ const defaultColors = [{ light: '#ecd44433', dark: '#ecd444' }];
149
+
150
+ /**
151
+ * @param {Map<string,ColorDef>} colorMapping
152
+ * @param {Array<ColorDef>} colors
153
+ * @param {string} user
154
+ * @return {ColorDef}
155
+ */
156
+ const getUserColor = (colorMapping, colors, user) => {
157
+ // @todo do not hit the same color twice if possible
158
+ if (!colorMapping.has(user)) {
159
+ if (colorMapping.size < colors.length) {
160
+ const usedColors = set__namespace.create();
161
+ colorMapping.forEach((color) => usedColors.add(color));
162
+ colors = colors.filter((color) => !usedColors.has(color));
163
+ }
164
+ colorMapping.set(user, random__namespace.oneOf(colors));
165
+ }
166
+ return /** @type {ColorDef} */ (colorMapping.get(user))
167
+ };
168
+
169
+ /**
170
+ * This plugin listens to changes in prosemirror view and keeps yXmlState and view in sync.
171
+ *
172
+ * This plugin also keeps references to the type and the shared document so other plugins can access it.
173
+ * @param {Y.XmlFragment} yXmlFragment
174
+ * @param {YSyncOpts} opts
175
+ * @return {any} Returns a prosemirror plugin that binds to this type
176
+ */
177
+ const ySyncPlugin = (yXmlFragment, {
178
+ colors = defaultColors,
179
+ colorMapping = new Map(),
180
+ permanentUserData = null,
181
+ onFirstRender = () => {},
182
+ mapping
183
+ } = {}) => {
184
+ let initialContentChanged = false;
185
+ const binding = new ProsemirrorBinding(yXmlFragment, mapping);
186
+ const plugin = new prosemirrorState.Plugin({
187
+ props: {
188
+ editable: (state) => {
189
+ const syncState = ySyncPluginKey.getState(state);
190
+ return syncState.snapshot == null && syncState.prevSnapshot == null
191
+ }
192
+ },
193
+ key: ySyncPluginKey,
194
+ state: {
195
+ /**
196
+ * @returns {any}
197
+ */
198
+ init: (_initargs, _state) => {
199
+ return {
200
+ type: yXmlFragment,
201
+ doc: yXmlFragment.doc,
202
+ binding,
203
+ snapshot: null,
204
+ prevSnapshot: null,
205
+ isChangeOrigin: false,
206
+ isUndoRedoOperation: false,
207
+ addToHistory: true,
208
+ colors,
209
+ colorMapping,
210
+ permanentUserData
211
+ }
212
+ },
213
+ apply: (tr, pluginState) => {
214
+ const change = tr.getMeta(ySyncPluginKey);
215
+ if (change !== undefined) {
216
+ pluginState = Object.assign({}, pluginState);
217
+ for (const key in change) {
218
+ pluginState[key] = change[key];
219
+ }
220
+ }
221
+ pluginState.addToHistory = tr.getMeta('addToHistory') !== false;
222
+ // always set isChangeOrigin. If undefined, this is not change origin.
223
+ pluginState.isChangeOrigin = change !== undefined &&
224
+ !!change.isChangeOrigin;
225
+ pluginState.isUndoRedoOperation = change !== undefined && !!change.isChangeOrigin && !!change.isUndoRedoOperation;
226
+ if (binding.prosemirrorView !== null) {
227
+ if (
228
+ change !== undefined &&
229
+ (change.snapshot != null || change.prevSnapshot != null)
230
+ ) {
231
+ // snapshot changed, rerender next
232
+ eventloop__namespace.timeout(0, () => {
233
+ if (binding.prosemirrorView == null) {
234
+ return
235
+ }
236
+ if (change.restore == null) {
237
+ binding._renderSnapshot(
238
+ change.snapshot,
239
+ change.prevSnapshot,
240
+ pluginState
241
+ );
242
+ } else {
243
+ binding._renderSnapshot(
244
+ change.snapshot,
245
+ change.snapshot,
246
+ pluginState
247
+ );
248
+ // reset to current prosemirror state
249
+ delete pluginState.restore;
250
+ delete pluginState.snapshot;
251
+ delete pluginState.prevSnapshot;
252
+ binding.mux(() => {
253
+ binding._prosemirrorChanged(
254
+ binding.prosemirrorView.state.doc
255
+ );
256
+ });
257
+ }
258
+ });
259
+ }
260
+ }
261
+ return pluginState
262
+ }
263
+ },
264
+ view: (view) => {
265
+ binding.initView(view);
266
+ if (mapping == null) {
267
+ // force rerender to update the bindings mapping
268
+ binding._forceRerender();
269
+ }
270
+ onFirstRender();
271
+ return {
272
+ update: () => {
273
+ const pluginState = plugin.getState(view.state);
274
+ if (
275
+ pluginState.snapshot == null && pluginState.prevSnapshot == null
276
+ ) {
277
+ if (
278
+ // If the content doesn't change initially, we don't render anything to Yjs
279
+ // If the content was cleared by a user action, we want to catch the change and
280
+ // represent it in Yjs
281
+ initialContentChanged ||
282
+ view.state.doc.content.findDiffStart(
283
+ view.state.doc.type.createAndFill().content
284
+ ) !== null
285
+ ) {
286
+ initialContentChanged = true;
287
+ if (
288
+ pluginState.addToHistory === false &&
289
+ !pluginState.isChangeOrigin
290
+ ) {
291
+ const yUndoPluginState = yUndoPluginKey.getState(view.state);
292
+ /**
293
+ * @type {Y.UndoManager}
294
+ */
295
+ const um = yUndoPluginState && yUndoPluginState.undoManager;
296
+ if (um) {
297
+ um.stopCapturing();
298
+ }
299
+ }
300
+ binding.mux(() => {
301
+ /** @type {Y.Doc} */ (pluginState.doc).transact((tr) => {
302
+ tr.meta.set('addToHistory', pluginState.addToHistory);
303
+ binding._prosemirrorChanged(view.state.doc);
304
+ }, ySyncPluginKey);
305
+ });
306
+ }
307
+ }
308
+ },
309
+ destroy: () => {
310
+ binding.destroy();
311
+ }
312
+ }
313
+ }
314
+ });
315
+ return plugin
316
+ };
317
+
318
+ /**
319
+ * @param {import('prosemirror-state').Transaction} tr
320
+ * @param {RecoverableSelection} recoverableSel
321
+ * @param {ProsemirrorBinding} binding
322
+ */
323
+ const restoreRelativeSelection = (tr, recoverableSel, binding) => {
324
+ if (recoverableSel !== null && recoverableSel.valid()) {
325
+ const selection = recoverableSel.restore(binding, tr.doc);
326
+ tr = tr.setSelection(selection);
327
+ }
328
+ };
329
+
330
+ /**
331
+ * @param {ProsemirrorBinding} pmbinding
332
+ * @param {import('prosemirror-state').EditorState} state
333
+ */
334
+ const getRelativeSelection = (pmbinding, state) => ({
335
+ type: /** @type {any} */ (state.selection).jsonID,
336
+ anchor: absolutePositionToRelativePosition(
337
+ state.selection.anchor,
338
+ pmbinding.type,
339
+ pmbinding.mapping
340
+ ),
341
+ head: absolutePositionToRelativePosition(
342
+ state.selection.head,
343
+ pmbinding.type,
344
+ pmbinding.mapping
345
+ )
346
+ });
347
+
348
+ const createRecoverableSelection = (pmbinding, state) => {
349
+ const sel = new RecoverableSelection(pmbinding, state.selection);
350
+ state.selection.map(state.doc, sel);
351
+ return sel
352
+ };
353
+
354
+ class RecoverableSelection {
355
+ constructor (pmbinding, selection, recoverMode = false) {
356
+ this.records = [];
357
+ this.pmbinding = pmbinding;
358
+ this.selection = selection;
359
+ this.recoverMode = recoverMode;
360
+ }
361
+
362
+ restore (pmbinding, doc) {
363
+ try {
364
+ return this.selection.map(doc, new RecoveryMapping(pmbinding, this.records))
365
+ } catch {
366
+ const $pos = doc.resolve(this.selection.anchor);
367
+ return prosemirrorState.Selection.near($pos)
368
+ }
369
+ }
370
+
371
+ valid () {
372
+ return !!this.records.length && this.records.every(r => r.relPos)
373
+ }
374
+
375
+ map (pos) {
376
+ const relPos = absolutePositionToRelativePosition(pos, this.pmbinding.type, this.pmbinding.mapping);
377
+ this.records.push({ pos, relPos });
378
+ return pos
379
+ }
380
+
381
+ mapResult (pos) {
382
+ return { deleted: false, pos: this.map(pos) }
383
+ }
384
+ }
385
+
386
+ class RecoveryMapping {
387
+ constructor (pmbinding, records) {
388
+ this.pmbinding = pmbinding;
389
+ this.records = records;
390
+ }
391
+
392
+ map (pos) {
393
+ return this.mapResult(pos).pos
394
+ }
395
+
396
+ mapResult (pos) {
397
+ for (const rec of this.records) {
398
+ if (rec.pos === pos) {
399
+ const mappedPos = relativePositionToAbsolutePosition(this.pmbinding.doc, this.pmbinding.type, rec.relPos, this.pmbinding.mapping);
400
+ if (mappedPos === null) {
401
+ return { deleted: true, pos }
402
+ }
403
+ return { deleted: false, pos: mappedPos }
404
+ }
405
+ }
406
+ throw new Error('not recorded')
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Binding for prosemirror.
412
+ *
413
+ * @protected
414
+ */
415
+ class ProsemirrorBinding {
416
+ /**
417
+ * @param {Y.XmlFragment} yXmlFragment The bind source
418
+ * @param {ProsemirrorMapping} mapping
419
+ */
420
+ constructor (yXmlFragment, mapping = new Map()) {
421
+ this.type = yXmlFragment;
422
+ /**
423
+ * this will be set once the view is created
424
+ * @type {any}
425
+ */
426
+ this.prosemirrorView = null;
427
+ this.mux = mutex.createMutex();
428
+ this.mapping = mapping;
429
+ /**
430
+ * Is overlapping mark - i.e. mark does not exclude itself.
431
+ *
432
+ * @type {Map<import('prosemirror-model').MarkType, boolean>}
433
+ */
434
+ this.isOMark = new Map();
435
+ this._observeFunction = this._typeChanged.bind(this);
436
+ /**
437
+ * @type {Y.Doc}
438
+ */
439
+ // @ts-ignore
440
+ this.doc = yXmlFragment.doc;
441
+ /**
442
+ * last selection as relative positions in the Yjs model
443
+ */
444
+ this.beforePatchSelection = null;
445
+ /**
446
+ * current selection as relative positions in the Yjs model
447
+ */
448
+ this.beforeTransactionSelection = null;
449
+ this.lastProsemirrorState = null;
450
+ this.beforeAllTransactions = () => {
451
+ if (this.beforeTransactionSelection === null && this.prosemirrorView != null) {
452
+ this.beforeTransactionSelection = createRecoverableSelection(
453
+ this,
454
+ this.prosemirrorView.state
455
+ );
456
+ }
457
+ };
458
+ this.afterAllTransactions = () => {
459
+ this.beforeTransactionSelection = null;
460
+ };
461
+ this._domSelectionInView = null;
462
+ }
463
+
464
+ /**
465
+ * Create a transaction for changing the prosemirror state.
466
+ *
467
+ * @returns
468
+ */
469
+ get _tr () {
470
+ return this.prosemirrorView.state.tr.setMeta('addToHistory', false)
471
+ }
472
+
473
+ _isLocalCursorInView () {
474
+ if (!this.prosemirrorView.hasFocus()) return false
475
+ if (environment__namespace.isBrowser && this._domSelectionInView === null) {
476
+ // Calculate the domSelectionInView and clear by next tick after all events are finished
477
+ eventloop__namespace.timeout(0, () => {
478
+ this._domSelectionInView = null;
479
+ });
480
+ this._domSelectionInView = this._isDomSelectionInView();
481
+ }
482
+ return this._domSelectionInView
483
+ }
484
+
485
+ _isDomSelectionInView () {
486
+ const selection = this.prosemirrorView._root.getSelection();
487
+
488
+ if (selection == null || selection.anchorNode == null) return false
489
+
490
+ const range = this.prosemirrorView._root.createRange();
491
+ range.setStart(selection.anchorNode, selection.anchorOffset);
492
+ range.setEnd(selection.focusNode, selection.focusOffset);
493
+
494
+ // This is a workaround for an edgecase where getBoundingClientRect will
495
+ // return zero values if the selection is collapsed at the start of a newline
496
+ // see reference here: https://stackoverflow.com/a/59780954
497
+ const rects = range.getClientRects();
498
+ if (rects.length === 0) {
499
+ // probably buggy newline behavior, explicitly select the node contents
500
+ if (range.startContainer && range.collapsed) {
501
+ range.selectNodeContents(range.startContainer);
502
+ }
503
+ }
504
+
505
+ const bounding = range.getBoundingClientRect();
506
+ const documentElement = dom__namespace.doc.documentElement;
507
+
508
+ return bounding.bottom >= 0 && bounding.right >= 0 &&
509
+ bounding.left <=
510
+ (window.innerWidth || documentElement.clientWidth || 0) &&
511
+ bounding.top <= (window.innerHeight || documentElement.clientHeight || 0)
512
+ }
513
+
514
+ /**
515
+ * @param {Y.Snapshot} snapshot
516
+ * @param {Y.Snapshot} prevSnapshot
517
+ */
518
+ renderSnapshot (snapshot, prevSnapshot) {
519
+ if (!prevSnapshot) {
520
+ prevSnapshot = Y__namespace.createSnapshot(Y__namespace.createDeleteSet(), new Map());
521
+ }
522
+ this.prosemirrorView.dispatch(
523
+ this._tr.setMeta(ySyncPluginKey, { snapshot, prevSnapshot })
524
+ );
525
+ }
526
+
527
+ unrenderSnapshot () {
528
+ this.mapping.clear();
529
+ this.mux(() => {
530
+ const fragmentContent = this.type.toArray().map((t) =>
531
+ createNodeFromYElement(
532
+ /** @type {Y.XmlElement} */ (t),
533
+ this.prosemirrorView.state.schema,
534
+ this
535
+ )
536
+ ).filter((n) => n !== null);
537
+ // @ts-ignore
538
+ const tr = this._tr.replace(
539
+ 0,
540
+ this.prosemirrorView.state.doc.content.size,
541
+ new PModel__namespace.Slice(PModel__namespace.Fragment.from(fragmentContent), 0, 0)
542
+ );
543
+ tr.setMeta(ySyncPluginKey, { snapshot: null, prevSnapshot: null });
544
+ this.prosemirrorView.dispatch(tr);
545
+ });
546
+ }
547
+
548
+ _forceRerender () {
549
+ this.mapping.clear();
550
+ this.mux(() => {
551
+ // If this is a forced rerender, this might neither happen as a pm change nor within a Yjs
552
+ // transaction. Then the "before selection" doesn't exist. In this case, we need to create a
553
+ // relative position before replacing content. Fixes #126
554
+ const sel = this.beforeTransactionSelection !== null ? null : this.prosemirrorView.state.selection;
555
+ const fragmentContent = this.type.toArray().map((t) =>
556
+ createNodeFromYElement(
557
+ /** @type {Y.XmlElement} */ (t),
558
+ this.prosemirrorView.state.schema,
559
+ this
560
+ )
561
+ ).filter((n) => n !== null);
562
+ // @ts-ignore
563
+ const tr = this._tr.replace(
564
+ 0,
565
+ this.prosemirrorView.state.doc.content.size,
566
+ new PModel__namespace.Slice(PModel__namespace.Fragment.from(fragmentContent), 0, 0)
567
+ );
568
+ if (sel) {
569
+ /**
570
+ * If the Prosemirror document we just created from this.type is
571
+ * smaller than the previous document, the selection might be
572
+ * out of bound, which would make Prosemirror throw an error.
573
+ */
574
+ const clampedAnchor = math__namespace.min(math__namespace.max(sel.anchor, 0), tr.doc.content.size);
575
+ const clampedHead = math__namespace.min(math__namespace.max(sel.head, 0), tr.doc.content.size);
576
+
577
+ tr.setSelection(prosemirrorState.TextSelection.create(tr.doc, clampedAnchor, clampedHead));
578
+ }
579
+ this.prosemirrorView.dispatch(
580
+ tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, binding: this })
581
+ );
582
+ });
583
+ }
584
+
585
+ /**
586
+ * @param {Y.Snapshot|Uint8Array} snapshot
587
+ * @param {Y.Snapshot|Uint8Array} prevSnapshot
588
+ * @param {Object} pluginState
589
+ */
590
+ _renderSnapshot (snapshot, prevSnapshot, pluginState) {
591
+ /**
592
+ * The document that contains the full history of this document.
593
+ * @type {Y.Doc}
594
+ */
595
+ let historyDoc = this.doc;
596
+ let historyType = this.type;
597
+ if (!snapshot) {
598
+ snapshot = Y__namespace.snapshot(this.doc);
599
+ }
600
+ if (snapshot instanceof Uint8Array || prevSnapshot instanceof Uint8Array) {
601
+ if (!(snapshot instanceof Uint8Array) || !(prevSnapshot instanceof Uint8Array)) {
602
+ // expected both snapshots to be v2 updates
603
+ error__namespace.unexpectedCase();
604
+ }
605
+ historyDoc = new Y__namespace.Doc({ gc: false });
606
+ Y__namespace.applyUpdateV2(historyDoc, prevSnapshot);
607
+ prevSnapshot = Y__namespace.snapshot(historyDoc);
608
+ Y__namespace.applyUpdateV2(historyDoc, snapshot);
609
+ snapshot = Y__namespace.snapshot(historyDoc);
610
+ if (historyType._item === null) {
611
+ /**
612
+ * If is a root type, we need to find the root key in the initial document
613
+ * and use it to get the history type.
614
+ */
615
+ const rootKey = Array.from(this.doc.share.keys()).find(
616
+ (key) => this.doc.share.get(key) === this.type
617
+ );
618
+ historyType = historyDoc.getXmlFragment(rootKey);
619
+ } else {
620
+ /**
621
+ * If it is a sub type, we use the item id to find the history type.
622
+ */
623
+ const historyStructs =
624
+ historyDoc.store.clients.get(historyType._item.id.client) ?? [];
625
+ const itemIndex = Y__namespace.findIndexSS(
626
+ historyStructs,
627
+ historyType._item.id.clock
628
+ );
629
+ const item = /** @type {Y.Item} */ (historyStructs[itemIndex]);
630
+ const content = /** @type {Y.ContentType} */ (item.content);
631
+ historyType = /** @type {Y.XmlFragment} */ (content.type);
632
+ }
633
+ }
634
+ // clear mapping because we are going to rerender
635
+ this.mapping.clear();
636
+ this.mux(() => {
637
+ historyDoc.transact((transaction) => {
638
+ // before rendering, we are going to sanitize ops and split deleted ops
639
+ // if they were deleted by seperate users.
640
+ /**
641
+ * @type {Y.PermanentUserData}
642
+ */
643
+ const pud = pluginState.permanentUserData;
644
+ if (pud) {
645
+ pud.dss.forEach((ds) => {
646
+ Y__namespace.iterateDeletedStructs(transaction, ds, (_item) => {});
647
+ });
648
+ }
649
+ /**
650
+ * @param {'removed'|'added'} type
651
+ * @param {Y.ID} id
652
+ */
653
+ const computeYChange = (type, id) => {
654
+ const user = type === 'added'
655
+ ? pud.getUserByClientId(id.client)
656
+ : pud.getUserByDeletedId(id);
657
+ return {
658
+ user,
659
+ type,
660
+ color: getUserColor(
661
+ pluginState.colorMapping,
662
+ pluginState.colors,
663
+ user
664
+ )
665
+ }
666
+ };
667
+ // Create document fragment and render
668
+ const fragmentContent = Y__namespace.typeListToArraySnapshot(
669
+ historyType,
670
+ new Y__namespace.Snapshot(prevSnapshot.ds, snapshot.sv)
671
+ ).map((t) => {
672
+ if (
673
+ !t._item.deleted || isVisible(t._item, snapshot) ||
674
+ isVisible(t._item, prevSnapshot)
675
+ ) {
676
+ return createNodeFromYElement(
677
+ t,
678
+ this.prosemirrorView.state.schema,
679
+ { mapping: new Map(), isOMark: new Map() },
680
+ snapshot,
681
+ prevSnapshot,
682
+ computeYChange
683
+ )
684
+ } else {
685
+ // No need to render elements that are not visible by either snapshot.
686
+ // If a client adds and deletes content in the same snapshot the element is not visible by either snapshot.
687
+ return null
688
+ }
689
+ }).filter((n) => n !== null);
690
+ // @ts-ignore
691
+ const tr = this._tr.replace(
692
+ 0,
693
+ this.prosemirrorView.state.doc.content.size,
694
+ new PModel__namespace.Slice(PModel__namespace.Fragment.from(fragmentContent), 0, 0)
695
+ );
696
+ this.prosemirrorView.dispatch(
697
+ tr.setMeta(ySyncPluginKey, { isChangeOrigin: true })
698
+ );
699
+ }, ySyncPluginKey);
700
+ });
701
+ }
702
+
703
+ /**
704
+ * @param {Array<Y.YEvent<any>>} events
705
+ * @param {Y.Transaction} transaction
706
+ */
707
+ _typeChanged (events, transaction) {
708
+ if (this.prosemirrorView == null) return
709
+ const syncState = ySyncPluginKey.getState(this.prosemirrorView.state);
710
+ if (
711
+ events.length === 0 || syncState.snapshot != null ||
712
+ syncState.prevSnapshot != null
713
+ ) {
714
+ // drop out if snapshot is active
715
+ this.renderSnapshot(syncState.snapshot, syncState.prevSnapshot);
716
+ return
717
+ }
718
+ this.mux(() => {
719
+ /**
720
+ * @param {any} _
721
+ * @param {Y.AbstractType<any>} type
722
+ */
723
+ const delType = (_, type) => this.mapping.delete(type);
724
+ Y__namespace.iterateDeletedStructs(
725
+ transaction,
726
+ transaction.deleteSet,
727
+ (struct) => {
728
+ if (struct.constructor === Y__namespace.Item) {
729
+ const type = /** @type {Y.ContentType} */ (/** @type {Y.Item} */ (struct).content).type;
730
+ type && this.mapping.delete(type);
731
+ }
732
+ }
733
+ );
734
+ transaction.changed.forEach(delType);
735
+ transaction.changedParentTypes.forEach(delType);
736
+ const fragmentContent = this.type.toArray().map((t) =>
737
+ createNodeIfNotExists(
738
+ /** @type {Y.XmlElement | Y.XmlHook} */ (t),
739
+ this.prosemirrorView.state.schema,
740
+ this
741
+ )
742
+ ).filter((n) => n !== null);
743
+ // @ts-ignore
744
+ let tr = this._tr.replace(
745
+ 0,
746
+ this.prosemirrorView.state.doc.content.size,
747
+ new PModel__namespace.Slice(PModel__namespace.Fragment.from(fragmentContent), 0, 0)
748
+ );
749
+ restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
750
+ tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, isUndoRedoOperation: transaction.origin instanceof Y__namespace.UndoManager });
751
+ if (
752
+ this.beforeTransactionSelection !== null && this._isLocalCursorInView()
753
+ ) {
754
+ tr.scrollIntoView();
755
+ }
756
+ this.prosemirrorView.dispatch(tr);
757
+ });
758
+ }
759
+
760
+ /**
761
+ * @param {import('prosemirror-model').Node} doc
762
+ */
763
+ _prosemirrorChanged (doc) {
764
+ this.doc.transact(() => {
765
+ this.beforePatchSelection = createRecoverableSelection(this, this.lastProsemirrorState);
766
+ this.lastProsemirrorState = this.prosemirrorView.state;
767
+ updateYFragment(this.doc, this.type, doc, this);
768
+ this.beforeTransactionSelection = createRecoverableSelection(
769
+ this,
770
+ this.prosemirrorView.state
771
+ );
772
+ }, ySyncPluginKey);
773
+ }
774
+
775
+ /**
776
+ * View is ready to listen to changes. Register observers.
777
+ * @param {any} prosemirrorView
778
+ */
779
+ initView (prosemirrorView) {
780
+ if (this.prosemirrorView != null) this.destroy();
781
+ this.prosemirrorView = prosemirrorView;
782
+ this.lastProsemirrorState = prosemirrorView.state;
783
+ this.doc.on('beforeAllTransactions', this.beforeAllTransactions);
784
+ this.doc.on('afterAllTransactions', this.afterAllTransactions);
785
+ this.type.observeDeep(this._observeFunction);
786
+ }
787
+
788
+ destroy () {
789
+ if (this.prosemirrorView == null) return
790
+ this.prosemirrorView = null;
791
+ this.type.unobserveDeep(this._observeFunction);
792
+ this.doc.off('beforeAllTransactions', this.beforeAllTransactions);
793
+ this.doc.off('afterAllTransactions', this.afterAllTransactions);
794
+ }
795
+ }
796
+
797
+ /**
798
+ * @private
799
+ * @param {Y.XmlElement | Y.XmlHook} el
800
+ * @param {PModel.Schema} schema
801
+ * @param {BindingMetadata} meta
802
+ * @param {Y.Snapshot} [snapshot]
803
+ * @param {Y.Snapshot} [prevSnapshot]
804
+ * @param {function('removed' | 'added', Y.ID):any} [computeYChange]
805
+ * @return {PModel.Node | null}
806
+ */
807
+ const createNodeIfNotExists = (
808
+ el,
809
+ schema,
810
+ meta,
811
+ snapshot,
812
+ prevSnapshot,
813
+ computeYChange
814
+ ) => {
815
+ const node = /** @type {PModel.Node} */ (meta.mapping.get(el));
816
+ if (node === undefined) {
817
+ if (el instanceof Y__namespace.XmlElement) {
818
+ return createNodeFromYElement(
819
+ el,
820
+ schema,
821
+ meta,
822
+ snapshot,
823
+ prevSnapshot,
824
+ computeYChange
825
+ )
826
+ } else {
827
+ throw error__namespace.methodUnimplemented() // we are currently not handling hooks
828
+ }
829
+ }
830
+ return node
831
+ };
832
+
833
+ /**
834
+ * @private
835
+ * @param {Y.XmlElement} el
836
+ * @param {any} schema
837
+ * @param {BindingMetadata} meta
838
+ * @param {Y.Snapshot} [snapshot]
839
+ * @param {Y.Snapshot} [prevSnapshot]
840
+ * @param {function('removed' | 'added', Y.ID):any} [computeYChange]
841
+ * @return {PModel.Node | null} Returns node if node could be created. Otherwise it deletes the yjs type and returns null
842
+ */
843
+ const createNodeFromYElement = (
844
+ el,
845
+ schema,
846
+ meta,
847
+ snapshot,
848
+ prevSnapshot,
849
+ computeYChange
850
+ ) => {
851
+ const children = [];
852
+ /**
853
+ * @param {Y.XmlElement | Y.XmlText} type
854
+ */
855
+ const createChildren = (type) => {
856
+ if (type instanceof Y__namespace.XmlElement) {
857
+ const n = createNodeIfNotExists(
858
+ type,
859
+ schema,
860
+ meta,
861
+ snapshot,
862
+ prevSnapshot,
863
+ computeYChange
864
+ );
865
+ if (n !== null) {
866
+ children.push(n);
867
+ }
868
+ } else {
869
+ // If the next ytext exists and was created by us, move the content to the current ytext.
870
+ // This is a fix for #160 -- duplication of characters when two Y.Text exist next to each
871
+ // other.
872
+ const nextytext = /** @type {Y.ContentType} */ (type._item.right?.content)?.type;
873
+ if (nextytext instanceof Y__namespace.Text && !nextytext._item.deleted && nextytext._item.id.client === nextytext.doc.clientID) {
874
+ type.applyDelta([
875
+ { retain: type.length },
876
+ ...nextytext.toDelta()
877
+ ]);
878
+ nextytext.doc.transact(tr => {
879
+ nextytext._item.delete(tr);
880
+ });
881
+ }
882
+ // now create the prosemirror text nodes
883
+ const ns = createTextNodesFromYText(
884
+ type,
885
+ schema,
886
+ meta,
887
+ snapshot,
888
+ prevSnapshot,
889
+ computeYChange
890
+ );
891
+ if (ns !== null) {
892
+ ns.forEach((textchild) => {
893
+ if (textchild !== null) {
894
+ children.push(textchild);
895
+ }
896
+ });
897
+ }
898
+ }
899
+ };
900
+ if (snapshot === undefined || prevSnapshot === undefined) {
901
+ el.toArray().forEach(createChildren);
902
+ } else {
903
+ Y__namespace.typeListToArraySnapshot(el, new Y__namespace.Snapshot(prevSnapshot.ds, snapshot.sv))
904
+ .forEach(createChildren);
905
+ }
906
+ try {
907
+ const attrs = el.getAttributes(snapshot);
908
+ if (snapshot !== undefined) {
909
+ if (!isVisible(/** @type {Y.Item} */ (el._item), snapshot)) {
910
+ attrs.ychange = computeYChange
911
+ ? computeYChange('removed', /** @type {Y.Item} */ (el._item).id)
912
+ : { type: 'removed' };
913
+ } else if (!isVisible(/** @type {Y.Item} */ (el._item), prevSnapshot)) {
914
+ attrs.ychange = computeYChange
915
+ ? computeYChange('added', /** @type {Y.Item} */ (el._item).id)
916
+ : { type: 'added' };
917
+ }
918
+ }
919
+ const node = schema.node(el.nodeName, attrs, children);
920
+ meta.mapping.set(el, node);
921
+ return node
922
+ } catch (e) {
923
+ // an error occured while creating the node. This is probably a result of a concurrent action.
924
+ /** @type {Y.Doc} */ (el.doc).transact((transaction) => {
925
+ /** @type {Y.Item} */ (el._item).delete(transaction);
926
+ }, ySyncPluginKey);
927
+ meta.mapping.delete(el);
928
+ return null
929
+ }
930
+ };
931
+
932
+ /**
933
+ * @private
934
+ * @param {Y.XmlText} text
935
+ * @param {import('prosemirror-model').Schema} schema
936
+ * @param {BindingMetadata} _meta
937
+ * @param {Y.Snapshot} [snapshot]
938
+ * @param {Y.Snapshot} [prevSnapshot]
939
+ * @param {function('removed' | 'added', Y.ID):any} [computeYChange]
940
+ * @return {Array<PModel.Node>|null}
941
+ */
942
+ const createTextNodesFromYText = (
943
+ text,
944
+ schema,
945
+ _meta,
946
+ snapshot,
947
+ prevSnapshot,
948
+ computeYChange
949
+ ) => {
950
+ const nodes = [];
951
+ const deltas = text.toDelta(snapshot, prevSnapshot, computeYChange);
952
+ try {
953
+ for (let i = 0; i < deltas.length; i++) {
954
+ const delta = deltas[i];
955
+ nodes.push(schema.text(delta.insert, attributesToMarks(delta.attributes, schema)));
956
+ }
957
+ } catch (e) {
958
+ // an error occured while creating the node. This is probably a result of a concurrent action.
959
+ /** @type {Y.Doc} */ (text.doc).transact((transaction) => {
960
+ /** @type {Y.Item} */ (text._item).delete(transaction);
961
+ }, ySyncPluginKey);
962
+ return null
963
+ }
964
+ // @ts-ignore
965
+ return nodes
966
+ };
967
+
968
+ /**
969
+ * @private
970
+ * @param {Array<any>} nodes prosemirror node
971
+ * @param {BindingMetadata} meta
972
+ * @return {Y.XmlText}
973
+ */
974
+ const createTypeFromTextNodes = (nodes, meta) => {
975
+ const type = new Y__namespace.XmlText();
976
+ const delta = nodes.map((node) => ({
977
+ // @ts-ignore
978
+ insert: node.text,
979
+ attributes: marksToAttributes(node.marks, meta)
980
+ }));
981
+ type.applyDelta(delta);
982
+ meta.mapping.set(type, nodes);
983
+ return type
984
+ };
985
+
986
+ /**
987
+ * @private
988
+ * @param {any} node prosemirror node
989
+ * @param {BindingMetadata} meta
990
+ * @return {Y.XmlElement}
991
+ */
992
+ const createTypeFromElementNode = (node, meta) => {
993
+ const type = new Y__namespace.XmlElement(node.type.name);
994
+ for (const key in node.attrs) {
995
+ const val = node.attrs[key];
996
+ if (val !== null && key !== 'ychange') {
997
+ type.setAttribute(key, val);
998
+ }
999
+ }
1000
+ type.insert(
1001
+ 0,
1002
+ normalizePNodeContent(node).map((n) =>
1003
+ createTypeFromTextOrElementNode(n, meta)
1004
+ )
1005
+ );
1006
+ meta.mapping.set(type, node);
1007
+ return type
1008
+ };
1009
+
1010
+ /**
1011
+ * @private
1012
+ * @param {PModel.Node|Array<PModel.Node>} node prosemirror text node
1013
+ * @param {BindingMetadata} meta
1014
+ * @return {Y.XmlElement|Y.XmlText}
1015
+ */
1016
+ const createTypeFromTextOrElementNode = (node, meta) =>
1017
+ node instanceof Array
1018
+ ? createTypeFromTextNodes(node, meta)
1019
+ : createTypeFromElementNode(node, meta);
1020
+
1021
+ /**
1022
+ * @param {any} val
1023
+ */
1024
+ const isObject = (val) => typeof val === 'object' && val !== null;
1025
+
1026
+ /**
1027
+ * @param {any} pattrs
1028
+ * @param {any} yattrs
1029
+ */
1030
+ const equalAttrs = (pattrs, yattrs) => {
1031
+ const keys = Object.keys(pattrs).filter((key) => pattrs[key] !== null);
1032
+ let eq =
1033
+ keys.length ===
1034
+ (yattrs == null ? 0 : Object.keys(yattrs).filter((key) => yattrs[key] !== null).length);
1035
+ for (let i = 0; i < keys.length && eq; i++) {
1036
+ const key = keys[i];
1037
+ const l = pattrs[key];
1038
+ const r = yattrs[key];
1039
+ eq = key === 'ychange' || l === r ||
1040
+ (isObject(l) && isObject(r) && equalAttrs(l, r));
1041
+ }
1042
+ return eq
1043
+ };
1044
+
1045
+ /**
1046
+ * @typedef {Array<Array<PModel.Node>|PModel.Node>} NormalizedPNodeContent
1047
+ */
1048
+
1049
+ /**
1050
+ * @param {any} pnode
1051
+ * @return {NormalizedPNodeContent}
1052
+ */
1053
+ const normalizePNodeContent = (pnode) => {
1054
+ const c = pnode.content.content;
1055
+ const res = [];
1056
+ for (let i = 0; i < c.length; i++) {
1057
+ const n = c[i];
1058
+ if (n.isText) {
1059
+ const textNodes = [];
1060
+ for (let tnode = c[i]; i < c.length && tnode.isText; tnode = c[++i]) {
1061
+ textNodes.push(tnode);
1062
+ }
1063
+ i--;
1064
+ res.push(textNodes);
1065
+ } else {
1066
+ res.push(n);
1067
+ }
1068
+ }
1069
+ return res
1070
+ };
1071
+
1072
+ /**
1073
+ * @param {Y.XmlText} ytext
1074
+ * @param {Array<any>} ptexts
1075
+ */
1076
+ const equalYTextPText = (ytext, ptexts) => {
1077
+ const delta = ytext.toDelta();
1078
+ return delta.length === ptexts.length &&
1079
+ delta.every(/** @type {(d:any,i:number) => boolean} */ (d, i) =>
1080
+ d.insert === /** @type {any} */ (ptexts[i]).text &&
1081
+ object__namespace.keys(d.attributes || {}).length === ptexts[i].marks.length &&
1082
+ object__namespace.every(d.attributes, (attr, yattrname) => {
1083
+ const markname = yattr2markname(yattrname);
1084
+ const pmarks = ptexts[i].marks;
1085
+ return equalAttrs(attr, pmarks.find(/** @param {any} mark */ mark => mark.type.name === markname)?.attrs)
1086
+ })
1087
+ )
1088
+ };
1089
+
1090
+ /**
1091
+ * @param {Y.XmlElement|Y.XmlText|Y.XmlHook} ytype
1092
+ * @param {any|Array<any>} pnode
1093
+ */
1094
+ const equalYTypePNode = (ytype, pnode) => {
1095
+ if (
1096
+ ytype instanceof Y__namespace.XmlElement && !(pnode instanceof Array) &&
1097
+ matchNodeName(ytype, pnode)
1098
+ ) {
1099
+ const normalizedContent = normalizePNodeContent(pnode);
1100
+ return ytype._length === normalizedContent.length &&
1101
+ equalAttrs(ytype.getAttributes(), pnode.attrs) &&
1102
+ ytype.toArray().every((ychild, i) =>
1103
+ equalYTypePNode(ychild, normalizedContent[i])
1104
+ )
1105
+ }
1106
+ return ytype instanceof Y__namespace.XmlText && pnode instanceof Array &&
1107
+ equalYTextPText(ytype, pnode)
1108
+ };
1109
+
1110
+ /**
1111
+ * @param {PModel.Node | Array<PModel.Node> | undefined} mapped
1112
+ * @param {PModel.Node | Array<PModel.Node>} pcontent
1113
+ */
1114
+ const mappedIdentity = (mapped, pcontent) =>
1115
+ mapped === pcontent ||
1116
+ (mapped instanceof Array && pcontent instanceof Array &&
1117
+ mapped.length === pcontent.length && mapped.every((a, i) =>
1118
+ pcontent[i] === a
1119
+ ));
1120
+
1121
+ /**
1122
+ * @param {Y.XmlElement} ytype
1123
+ * @param {PModel.Node} pnode
1124
+ * @param {BindingMetadata} meta
1125
+ * @return {{ foundMappedChild: boolean, equalityFactor: number }}
1126
+ */
1127
+ const computeChildEqualityFactor = (ytype, pnode, meta) => {
1128
+ const yChildren = ytype.toArray();
1129
+ const pChildren = normalizePNodeContent(pnode);
1130
+ const pChildCnt = pChildren.length;
1131
+ const yChildCnt = yChildren.length;
1132
+ const minCnt = math__namespace.min(yChildCnt, pChildCnt);
1133
+ let left = 0;
1134
+ let right = 0;
1135
+ let foundMappedChild = false;
1136
+ for (; left < minCnt; left++) {
1137
+ const leftY = yChildren[left];
1138
+ const leftP = pChildren[left];
1139
+ if (mappedIdentity(meta.mapping.get(leftY), leftP)) {
1140
+ foundMappedChild = true; // definite (good) match!
1141
+ } else if (!equalYTypePNode(leftY, leftP)) {
1142
+ break
1143
+ }
1144
+ }
1145
+ for (; left + right < minCnt; right++) {
1146
+ const rightY = yChildren[yChildCnt - right - 1];
1147
+ const rightP = pChildren[pChildCnt - right - 1];
1148
+ if (mappedIdentity(meta.mapping.get(rightY), rightP)) {
1149
+ foundMappedChild = true;
1150
+ } else if (!equalYTypePNode(rightY, rightP)) {
1151
+ break
1152
+ }
1153
+ }
1154
+ return {
1155
+ equalityFactor: left + right,
1156
+ foundMappedChild
1157
+ }
1158
+ };
1159
+
1160
+ /**
1161
+ * @param {Y.Text} ytext
1162
+ */
1163
+ const ytextTrans = (ytext) => {
1164
+ let str = '';
1165
+ /**
1166
+ * @type {Y.Item|null}
1167
+ */
1168
+ let n = ytext._start;
1169
+ const nAttrs = {};
1170
+ while (n !== null) {
1171
+ if (!n.deleted) {
1172
+ if (n.countable && n.content instanceof Y__namespace.ContentString) {
1173
+ str += n.content.str;
1174
+ } else if (n.content instanceof Y__namespace.ContentFormat) {
1175
+ nAttrs[n.content.key] = null;
1176
+ }
1177
+ }
1178
+ n = n.right;
1179
+ }
1180
+ return {
1181
+ str,
1182
+ nAttrs
1183
+ }
1184
+ };
1185
+
1186
+ /**
1187
+ * @todo test this more
1188
+ *
1189
+ * @param {Y.Text} ytext
1190
+ * @param {Array<any>} ptexts
1191
+ * @param {BindingMetadata} meta
1192
+ */
1193
+ const updateYText = (ytext, ptexts, meta) => {
1194
+ meta.mapping.set(ytext, ptexts);
1195
+ const { nAttrs, str } = ytextTrans(ytext);
1196
+ const content = ptexts.map((p) => ({
1197
+ insert: /** @type {any} */ (p).text,
1198
+ attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks, meta))
1199
+ }));
1200
+ const { insert, remove, index } = diff.simpleDiff(
1201
+ str,
1202
+ content.map((c) => c.insert).join('')
1203
+ );
1204
+ ytext.delete(index, remove);
1205
+ ytext.insert(index, insert);
1206
+ ytext.applyDelta(
1207
+ content.map((c) => ({ retain: c.insert.length, attributes: c.attributes }))
1208
+ );
1209
+ };
1210
+
1211
+ const hashedMarkNameRegex = /(.*)(--[a-zA-Z0-9+/=]{8})$/;
1212
+ /**
1213
+ * @param {string} attrName
1214
+ */
1215
+ const yattr2markname = attrName => hashedMarkNameRegex.exec(attrName)?.[1] ?? attrName;
1216
+
1217
+ /**
1218
+ * @todo move this to markstoattributes
1219
+ *
1220
+ * @param {Object<string, any>} attrs
1221
+ * @param {import('prosemirror-model').Schema} schema
1222
+ */
1223
+ const attributesToMarks = (attrs, schema) => {
1224
+ /**
1225
+ * @type {Array<import('prosemirror-model').Mark>}
1226
+ */
1227
+ const marks = [];
1228
+ for (const markName in attrs) {
1229
+ // remove hashes if necessary
1230
+ marks.push(schema.mark(yattr2markname(markName), attrs[markName]));
1231
+ }
1232
+ return marks
1233
+ };
1234
+
1235
+ /**
1236
+ * @param {Array<import('prosemirror-model').Mark>} marks
1237
+ * @param {BindingMetadata} meta
1238
+ */
1239
+ const marksToAttributes = (marks, meta) => {
1240
+ const pattrs = {};
1241
+ marks.forEach((mark) => {
1242
+ if (mark.type.name !== 'ychange') {
1243
+ const isOverlapping = map__namespace.setIfUndefined(meta.isOMark, mark.type, () => !mark.type.excludes(mark.type));
1244
+ pattrs[isOverlapping ? `${mark.type.name}--${hashOfJSON(mark.toJSON())}` : mark.type.name] = mark.attrs;
1245
+ }
1246
+ });
1247
+ return pattrs
1248
+ };
1249
+
1250
+ /**
1251
+ * Update a yDom node by syncing the current content of the prosemirror node.
1252
+ *
1253
+ * This is a y-prosemirror internal feature that you can use at your own risk.
1254
+ *
1255
+ * @private
1256
+ * @unstable
1257
+ *
1258
+ * @param {{transact: Function}} y
1259
+ * @param {Y.XmlFragment} yDomFragment
1260
+ * @param {any} pNode
1261
+ * @param {BindingMetadata} meta
1262
+ */
1263
+ const updateYFragment = (y, yDomFragment, pNode, meta) => {
1264
+ if (
1265
+ yDomFragment instanceof Y__namespace.XmlElement &&
1266
+ yDomFragment.nodeName !== pNode.type.name
1267
+ ) {
1268
+ throw new Error('node name mismatch!')
1269
+ }
1270
+ meta.mapping.set(yDomFragment, pNode);
1271
+ // update attributes
1272
+ if (yDomFragment instanceof Y__namespace.XmlElement) {
1273
+ const yDomAttrs = yDomFragment.getAttributes();
1274
+ const pAttrs = pNode.attrs;
1275
+ for (const key in pAttrs) {
1276
+ if (pAttrs[key] !== null) {
1277
+ if (yDomAttrs[key] !== pAttrs[key] && key !== 'ychange') {
1278
+ yDomFragment.setAttribute(key, pAttrs[key]);
1279
+ }
1280
+ } else {
1281
+ yDomFragment.removeAttribute(key);
1282
+ }
1283
+ }
1284
+ // remove all keys that are no longer in pAttrs
1285
+ for (const key in yDomAttrs) {
1286
+ if (pAttrs[key] === undefined) {
1287
+ yDomFragment.removeAttribute(key);
1288
+ }
1289
+ }
1290
+ }
1291
+ // update children
1292
+ const pChildren = normalizePNodeContent(pNode);
1293
+ const pChildCnt = pChildren.length;
1294
+ const yChildren = yDomFragment.toArray();
1295
+ const yChildCnt = yChildren.length;
1296
+ const minCnt = math__namespace.min(pChildCnt, yChildCnt);
1297
+ let left = 0;
1298
+ let right = 0;
1299
+ // find number of matching elements from left
1300
+ for (; left < minCnt; left++) {
1301
+ const leftY = yChildren[left];
1302
+ const leftP = pChildren[left];
1303
+ if (!mappedIdentity(meta.mapping.get(leftY), leftP)) {
1304
+ if (equalYTypePNode(leftY, leftP)) {
1305
+ // update mapping
1306
+ meta.mapping.set(leftY, leftP);
1307
+ } else {
1308
+ break
1309
+ }
1310
+ }
1311
+ }
1312
+ // find number of matching elements from right
1313
+ for (; right + left < minCnt; right++) {
1314
+ const rightY = yChildren[yChildCnt - right - 1];
1315
+ const rightP = pChildren[pChildCnt - right - 1];
1316
+ if (!mappedIdentity(meta.mapping.get(rightY), rightP)) {
1317
+ if (equalYTypePNode(rightY, rightP)) {
1318
+ // update mapping
1319
+ meta.mapping.set(rightY, rightP);
1320
+ } else {
1321
+ break
1322
+ }
1323
+ }
1324
+ }
1325
+ y.transact(() => {
1326
+ // try to compare and update
1327
+ while (yChildCnt - left - right > 0 && pChildCnt - left - right > 0) {
1328
+ const leftY = yChildren[left];
1329
+ const leftP = pChildren[left];
1330
+ const rightY = yChildren[yChildCnt - right - 1];
1331
+ const rightP = pChildren[pChildCnt - right - 1];
1332
+ if (leftY instanceof Y__namespace.XmlText && leftP instanceof Array) {
1333
+ if (!equalYTextPText(leftY, leftP)) {
1334
+ updateYText(leftY, leftP, meta);
1335
+ }
1336
+ left += 1;
1337
+ } else {
1338
+ let updateLeft = leftY instanceof Y__namespace.XmlElement &&
1339
+ matchNodeName(leftY, leftP);
1340
+ let updateRight = rightY instanceof Y__namespace.XmlElement &&
1341
+ matchNodeName(rightY, rightP);
1342
+ if (updateLeft && updateRight) {
1343
+ // decide which which element to update
1344
+ const equalityLeft = computeChildEqualityFactor(
1345
+ /** @type {Y.XmlElement} */ (leftY),
1346
+ /** @type {PModel.Node} */ (leftP),
1347
+ meta
1348
+ );
1349
+ const equalityRight = computeChildEqualityFactor(
1350
+ /** @type {Y.XmlElement} */ (rightY),
1351
+ /** @type {PModel.Node} */ (rightP),
1352
+ meta
1353
+ );
1354
+ if (
1355
+ equalityLeft.foundMappedChild && !equalityRight.foundMappedChild
1356
+ ) {
1357
+ updateRight = false;
1358
+ } else if (
1359
+ !equalityLeft.foundMappedChild && equalityRight.foundMappedChild
1360
+ ) {
1361
+ updateLeft = false;
1362
+ } else if (
1363
+ equalityLeft.equalityFactor < equalityRight.equalityFactor
1364
+ ) {
1365
+ updateLeft = false;
1366
+ } else {
1367
+ updateRight = false;
1368
+ }
1369
+ }
1370
+ if (updateLeft) {
1371
+ updateYFragment(
1372
+ y,
1373
+ /** @type {Y.XmlFragment} */ (leftY),
1374
+ /** @type {PModel.Node} */ (leftP),
1375
+ meta
1376
+ );
1377
+ left += 1;
1378
+ } else if (updateRight) {
1379
+ updateYFragment(
1380
+ y,
1381
+ /** @type {Y.XmlFragment} */ (rightY),
1382
+ /** @type {PModel.Node} */ (rightP),
1383
+ meta
1384
+ );
1385
+ right += 1;
1386
+ } else {
1387
+ meta.mapping.delete(yDomFragment.get(left));
1388
+ yDomFragment.delete(left, 1);
1389
+ yDomFragment.insert(left, [
1390
+ createTypeFromTextOrElementNode(leftP, meta)
1391
+ ]);
1392
+ left += 1;
1393
+ }
1394
+ }
1395
+ }
1396
+ const yDelLen = yChildCnt - left - right;
1397
+ if (
1398
+ yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof Y__namespace.XmlText
1399
+ ) {
1400
+ meta.mapping.delete(yChildren[0]);
1401
+ // Edge case handling https://github.com/yjs/y-prosemirror/issues/108
1402
+ // Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
1403
+ yChildren[0].delete(0, yChildren[0].length);
1404
+ } else if (yDelLen > 0) {
1405
+ yDomFragment.slice(left, left + yDelLen).forEach(type => meta.mapping.delete(type));
1406
+ yDomFragment.delete(left, yDelLen);
1407
+ }
1408
+ if (left + right < pChildCnt) {
1409
+ const ins = [];
1410
+ for (let i = left; i < pChildCnt - right; i++) {
1411
+ ins.push(createTypeFromTextOrElementNode(pChildren[i], meta));
1412
+ }
1413
+ yDomFragment.insert(left, ins);
1414
+ }
1415
+ }, ySyncPluginKey);
1416
+ };
1417
+
1418
+ /**
1419
+ * @function
1420
+ * @param {Y.XmlElement} yElement
1421
+ * @param {any} pNode Prosemirror Node
1422
+ */
1423
+ const matchNodeName = (yElement, pNode) =>
1424
+ !(pNode instanceof Array) && yElement.nodeName === pNode.type.name;
1425
+
1426
+ /**
1427
+ * Either a node if type is YXmlElement or an Array of text nodes if YXmlText
1428
+ * @typedef {Map<Y.AbstractType, Node | Array<Node>>} ProsemirrorMapping
1429
+ */
1430
+
1431
+ /**
1432
+ * Is null if no timeout is in progress.
1433
+ * Is defined if a timeout is in progress.
1434
+ * Maps from view
1435
+ * @type {Map<EditorView, Map<any, any>>|null}
1436
+ */
1437
+ let viewsToUpdate = null;
1438
+
1439
+ const updateMetas = () => {
1440
+ const ups = /** @type {Map<EditorView, Map<any, any>>} */ (viewsToUpdate);
1441
+ viewsToUpdate = null;
1442
+ ups.forEach((metas, view) => {
1443
+ const tr = view.state.tr;
1444
+ const syncState = ySyncPluginKey.getState(view.state);
1445
+ if (syncState && syncState.binding && !syncState.binding.isDestroyed) {
1446
+ metas.forEach((val, key) => {
1447
+ tr.setMeta(key, val);
1448
+ });
1449
+ view.dispatch(tr);
1450
+ }
1451
+ });
1452
+ };
1453
+
1454
+ const setMeta = (view, key, value) => {
1455
+ if (!viewsToUpdate) {
1456
+ viewsToUpdate = new Map();
1457
+ eventloop__namespace.timeout(0, updateMetas);
1458
+ }
1459
+ map__namespace.setIfUndefined(viewsToUpdate, view, map__namespace.create).set(key, value);
1460
+ };
1461
+
1462
+ /**
1463
+ * Transforms a Prosemirror based absolute position to a Yjs Cursor (relative position in the Yjs model).
1464
+ *
1465
+ * @param {number} pos
1466
+ * @param {Y.XmlFragment} type
1467
+ * @param {ProsemirrorMapping} mapping
1468
+ * @return {any} relative position
1469
+ */
1470
+ const absolutePositionToRelativePosition = (pos, type, mapping) => {
1471
+ if (pos === 0) {
1472
+ // if the type is later populated, we want to retain the 0 position (hence assoc=-1)
1473
+ return Y__namespace.createRelativePositionFromTypeIndex(type, 0, type.length === 0 ? -1 : 0)
1474
+ }
1475
+ /**
1476
+ * @type {any}
1477
+ */
1478
+ let n = type._first === null ? null : /** @type {Y.ContentType} */ (type._first.content).type;
1479
+ while (n !== null && type !== n) {
1480
+ if (n instanceof Y__namespace.XmlText) {
1481
+ if (n._length >= pos) {
1482
+ return Y__namespace.createRelativePositionFromTypeIndex(n, pos, type.length === 0 ? -1 : 0)
1483
+ } else {
1484
+ pos -= n._length;
1485
+ }
1486
+ if (n._item !== null && n._item.next !== null) {
1487
+ n = /** @type {Y.ContentType} */ (n._item.next.content).type;
1488
+ } else {
1489
+ do {
1490
+ n = n._item === null ? null : n._item.parent;
1491
+ pos--;
1492
+ } while (n !== type && n !== null && n._item !== null && n._item.next === null)
1493
+ if (n !== null && n !== type) {
1494
+ // @ts-gnore we know that n.next !== null because of above loop conditition
1495
+ n = n._item === null ? null : /** @type {Y.ContentType} */ (/** @type Y.Item */ (n._item.next).content).type;
1496
+ }
1497
+ }
1498
+ } else {
1499
+ const pNodeSize = /** @type {any} */ (mapping.get(n) || { nodeSize: 0 }).nodeSize;
1500
+ if (n._first !== null && pos < pNodeSize) {
1501
+ n = /** @type {Y.ContentType} */ (n._first.content).type;
1502
+ pos--;
1503
+ } else {
1504
+ if (pos === 1 && n._length === 0 && pNodeSize > 1) {
1505
+ // edge case, should end in this paragraph
1506
+ return new Y__namespace.RelativePosition(n._item === null ? null : n._item.id, n._item === null ? Y__namespace.findRootTypeKey(n) : null, null)
1507
+ }
1508
+ pos -= pNodeSize;
1509
+ if (n._item !== null && n._item.next !== null) {
1510
+ n = /** @type {Y.ContentType} */ (n._item.next.content).type;
1511
+ } else {
1512
+ if (pos === 0) {
1513
+ // set to end of n.parent
1514
+ n = n._item === null ? n : n._item.parent;
1515
+ return new Y__namespace.RelativePosition(n._item === null ? null : n._item.id, n._item === null ? Y__namespace.findRootTypeKey(n) : null, null)
1516
+ }
1517
+ do {
1518
+ n = /** @type {Y.Item} */ (n._item).parent;
1519
+ pos--;
1520
+ } while (n !== type && /** @type {Y.Item} */ (n._item).next === null)
1521
+ // if n is null at this point, we have an unexpected case
1522
+ if (n !== type) {
1523
+ // We know that n._item.next is defined because of above loop condition
1524
+ n = /** @type {Y.ContentType} */ (/** @type {Y.Item} */ (/** @type {Y.Item} */ (n._item).next).content).type;
1525
+ }
1526
+ }
1527
+ }
1528
+ }
1529
+ if (n === null) {
1530
+ throw error__namespace.unexpectedCase()
1531
+ }
1532
+ if (pos === 0 && n.constructor !== Y__namespace.XmlText && n !== type) { // TODO: set to <= 0
1533
+ return createRelativePosition(n._item.parent, n._item)
1534
+ }
1535
+ }
1536
+ return Y__namespace.createRelativePositionFromTypeIndex(type, type._length, type.length === 0 ? -1 : 0)
1537
+ };
1538
+
1539
+ const createRelativePosition = (type, item) => {
1540
+ let typeid = null;
1541
+ let tname = null;
1542
+ if (type._item === null) {
1543
+ tname = Y__namespace.findRootTypeKey(type);
1544
+ } else {
1545
+ typeid = Y__namespace.createID(type._item.id.client, type._item.id.clock);
1546
+ }
1547
+ return new Y__namespace.RelativePosition(typeid, tname, item.id)
1548
+ };
1549
+
1550
+ /**
1551
+ * @param {Y.Doc} y
1552
+ * @param {Y.XmlFragment} documentType Top level type that is bound to pView
1553
+ * @param {any} relPos Encoded Yjs based relative position
1554
+ * @param {ProsemirrorMapping} mapping
1555
+ * @return {null|number}
1556
+ */
1557
+ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) => {
1558
+ const decodedPos = Y__namespace.createAbsolutePositionFromRelativePosition(relPos, y);
1559
+ if (decodedPos === null || (decodedPos.type !== documentType && !Y__namespace.isParentOf(documentType, decodedPos.type._item))) {
1560
+ return null
1561
+ }
1562
+ let type = decodedPos.type;
1563
+ let pos = 0;
1564
+ if (type.constructor === Y__namespace.XmlText) {
1565
+ pos = decodedPos.index;
1566
+ } else if (type._item === null || !type._item.deleted) {
1567
+ let n = type._first;
1568
+ let i = 0;
1569
+ while (i < type._length && i < decodedPos.index && n !== null) {
1570
+ if (!n.deleted) {
1571
+ const t = /** @type {Y.ContentType} */ (n.content).type;
1572
+ i++;
1573
+ if (t instanceof Y__namespace.XmlText) {
1574
+ pos += t._length;
1575
+ } else {
1576
+ pos += /** @type {any} */ (mapping.get(t)).nodeSize;
1577
+ }
1578
+ }
1579
+ n = /** @type {Y.Item} */ (n.right);
1580
+ }
1581
+ pos += 1; // increase because we go out of n
1582
+ }
1583
+ while (type !== documentType && type._item !== null) {
1584
+ // @ts-ignore
1585
+ const parent = type._item.parent;
1586
+ // @ts-ignore
1587
+ if (parent._item === null || !parent._item.deleted) {
1588
+ pos += 1; // the start tag
1589
+ let n = /** @type {Y.AbstractType} */ (parent)._first;
1590
+ // now iterate until we found type
1591
+ while (n !== null) {
1592
+ const contentType = /** @type {Y.ContentType} */ (n.content).type;
1593
+ if (contentType === type) {
1594
+ break
1595
+ }
1596
+ if (!n.deleted) {
1597
+ if (contentType instanceof Y__namespace.XmlText) {
1598
+ pos += contentType._length;
1599
+ } else {
1600
+ pos += /** @type {any} */ (mapping.get(contentType)).nodeSize;
1601
+ }
1602
+ }
1603
+ n = n.right;
1604
+ }
1605
+ }
1606
+ type = /** @type {Y.AbstractType} */ (parent);
1607
+ }
1608
+ return pos - 1 // we don't count the most outer tag, because it is a fragment
1609
+ };
1610
+
1611
+ /**
1612
+ * Utility function for converting an Y.Fragment to a ProseMirror fragment.
1613
+ *
1614
+ * @param {Y.XmlFragment} yXmlFragment
1615
+ * @param {Schema} schema
1616
+ */
1617
+ const yXmlFragmentToProseMirrorFragment = (yXmlFragment, schema) => {
1618
+ const fragmentContent = yXmlFragment.toArray().map((t) =>
1619
+ createNodeFromYElement(
1620
+ /** @type {Y.XmlElement} */ (t),
1621
+ schema,
1622
+ createEmptyMeta()
1623
+ )
1624
+ ).filter((n) => n !== null);
1625
+ return PModel.Fragment.fromArray(fragmentContent)
1626
+ };
1627
+
1628
+ /**
1629
+ * Utility function for converting an Y.Fragment to a ProseMirror node.
1630
+ *
1631
+ * @param {Y.XmlFragment} yXmlFragment
1632
+ * @param {Schema} schema
1633
+ */
1634
+ const yXmlFragmentToProseMirrorRootNode = (yXmlFragment, schema) =>
1635
+ schema.topNodeType.create(null, yXmlFragmentToProseMirrorFragment(yXmlFragment, schema));
1636
+
1637
+ /**
1638
+ * The initial ProseMirror content should be supplied by Yjs. This function transforms a Y.Fragment
1639
+ * to a ProseMirror Doc node and creates a mapping that is used by the sync plugin.
1640
+ *
1641
+ * @param {Y.XmlFragment} yXmlFragment
1642
+ * @param {Schema} schema
1643
+ *
1644
+ * @todo deprecate mapping property
1645
+ */
1646
+ const initProseMirrorDoc = (yXmlFragment, schema) => {
1647
+ const meta = createEmptyMeta();
1648
+ const fragmentContent = yXmlFragment.toArray().map((t) =>
1649
+ createNodeFromYElement(
1650
+ /** @type {Y.XmlElement} */ (t),
1651
+ schema,
1652
+ meta
1653
+ )
1654
+ ).filter((n) => n !== null);
1655
+ const doc = schema.topNodeType.create(null, PModel.Fragment.fromArray(fragmentContent));
1656
+ return { doc, meta, mapping: meta.mapping }
1657
+ };
1658
+
1659
+ /**
1660
+ * Utility method to convert a Prosemirror Doc Node into a Y.Doc.
1661
+ *
1662
+ * This can be used when importing existing content to Y.Doc for the first time,
1663
+ * note that this should not be used to rehydrate a Y.Doc from a database once
1664
+ * collaboration has begun as all history will be lost
1665
+ *
1666
+ * @param {Node} doc
1667
+ * @param {string} xmlFragment
1668
+ * @return {Y.Doc}
1669
+ */
1670
+ function prosemirrorToYDoc (doc, xmlFragment = 'prosemirror') {
1671
+ const ydoc = new Y__namespace.Doc();
1672
+ const type = /** @type {Y.XmlFragment} */ (ydoc.get(xmlFragment, Y__namespace.XmlFragment));
1673
+ if (!type.doc) {
1674
+ return ydoc
1675
+ }
1676
+
1677
+ prosemirrorToYXmlFragment(doc, type);
1678
+ return type.doc
1679
+ }
1680
+
1681
+ /**
1682
+ * Utility method to update an empty Y.XmlFragment with content from a Prosemirror Doc Node.
1683
+ *
1684
+ * This can be used when importing existing content to Y.Doc for the first time,
1685
+ * note that this should not be used to rehydrate a Y.Doc from a database once
1686
+ * collaboration has begun as all history will be lost
1687
+ *
1688
+ * Note: The Y.XmlFragment does not need to be part of a Y.Doc document at the time that this
1689
+ * method is called, but it must be added before any other operations are performed on it.
1690
+ *
1691
+ * @param {Node} doc prosemirror document.
1692
+ * @param {Y.XmlFragment} [xmlFragment] If supplied, an xml fragment to be
1693
+ * populated from the prosemirror state; otherwise a new XmlFragment will be created.
1694
+ * @return {Y.XmlFragment}
1695
+ */
1696
+ function prosemirrorToYXmlFragment (doc, xmlFragment) {
1697
+ const type = xmlFragment || new Y__namespace.XmlFragment();
1698
+ const ydoc = type.doc ? type.doc : { transact: (transaction) => transaction(undefined) };
1699
+ updateYFragment(ydoc, type, doc, { mapping: new Map(), isOMark: new Map() });
1700
+ return type
1701
+ }
1702
+
1703
+ /**
1704
+ * Utility method to convert Prosemirror compatible JSON into a Y.Doc.
1705
+ *
1706
+ * This can be used when importing existing content to Y.Doc for the first time,
1707
+ * note that this should not be used to rehydrate a Y.Doc from a database once
1708
+ * collaboration has begun as all history will be lost
1709
+ *
1710
+ * @param {Schema} schema
1711
+ * @param {any} state
1712
+ * @param {string} xmlFragment
1713
+ * @return {Y.Doc}
1714
+ */
1715
+ function prosemirrorJSONToYDoc (schema, state, xmlFragment = 'prosemirror') {
1716
+ const doc = PModel.Node.fromJSON(schema, state);
1717
+ return prosemirrorToYDoc(doc, xmlFragment)
1718
+ }
1719
+
1720
+ /**
1721
+ * Utility method to convert Prosemirror compatible JSON to a Y.XmlFragment
1722
+ *
1723
+ * This can be used when importing existing content to Y.Doc for the first time,
1724
+ * note that this should not be used to rehydrate a Y.Doc from a database once
1725
+ * collaboration has begun as all history will be lost
1726
+ *
1727
+ * @param {Schema} schema
1728
+ * @param {any} state
1729
+ * @param {Y.XmlFragment} [xmlFragment] If supplied, an xml fragment to be
1730
+ * populated from the prosemirror state; otherwise a new XmlFragment will be created.
1731
+ * @return {Y.XmlFragment}
1732
+ */
1733
+ function prosemirrorJSONToYXmlFragment (schema, state, xmlFragment) {
1734
+ const doc = PModel.Node.fromJSON(schema, state);
1735
+ return prosemirrorToYXmlFragment(doc, xmlFragment)
1736
+ }
1737
+
1738
+ /**
1739
+ * @deprecated Use `yXmlFragmentToProseMirrorRootNode` instead
1740
+ *
1741
+ * Utility method to convert a Y.Doc to a Prosemirror Doc node.
1742
+ *
1743
+ * @param {Schema} schema
1744
+ * @param {Y.Doc} ydoc
1745
+ * @return {Node}
1746
+ */
1747
+ function yDocToProsemirror (schema, ydoc) {
1748
+ const state = yDocToProsemirrorJSON(ydoc);
1749
+ return PModel.Node.fromJSON(schema, state)
1750
+ }
1751
+
1752
+ /**
1753
+ *
1754
+ * @deprecated Use `yXmlFragmentToProseMirrorRootNode` instead
1755
+ *
1756
+ * Utility method to convert a Y.XmlFragment to a Prosemirror Doc node.
1757
+ *
1758
+ * @param {Schema} schema
1759
+ * @param {Y.XmlFragment} xmlFragment
1760
+ * @return {Node}
1761
+ */
1762
+ function yXmlFragmentToProsemirror (schema, xmlFragment) {
1763
+ const state = yXmlFragmentToProsemirrorJSON(xmlFragment);
1764
+ return PModel.Node.fromJSON(schema, state)
1765
+ }
1766
+
1767
+ /**
1768
+ *
1769
+ * @deprecated Use `yXmlFragmentToProseMirrorRootNode` instead
1770
+ *
1771
+ * Utility method to convert a Y.Doc to Prosemirror compatible JSON.
1772
+ *
1773
+ * @param {Y.Doc} ydoc
1774
+ * @param {string} xmlFragment
1775
+ * @return {Record<string, any>}
1776
+ */
1777
+ function yDocToProsemirrorJSON (
1778
+ ydoc,
1779
+ xmlFragment = 'prosemirror'
1780
+ ) {
1781
+ return yXmlFragmentToProsemirrorJSON(ydoc.getXmlFragment(xmlFragment))
1782
+ }
1783
+
1784
+ /**
1785
+ * @deprecated Use `yXmlFragmentToProseMirrorRootNode` instead
1786
+ *
1787
+ * Utility method to convert a Y.Doc to Prosemirror compatible JSON.
1788
+ *
1789
+ * @param {Y.XmlFragment} xmlFragment The fragment, which must be part of a Y.Doc.
1790
+ * @return {Record<string, any>}
1791
+ */
1792
+ function yXmlFragmentToProsemirrorJSON (xmlFragment) {
1793
+ const items = xmlFragment.toArray();
1794
+
1795
+ /**
1796
+ * @param {Y.AbstractType} item
1797
+ */
1798
+ const serialize = item => {
1799
+ /**
1800
+ * @type {Object} NodeObject
1801
+ * @property {string} NodeObject.type
1802
+ * @property {Record<string, string>=} NodeObject.attrs
1803
+ * @property {Array<NodeObject>=} NodeObject.content
1804
+ */
1805
+ let response;
1806
+
1807
+ // TODO: Must be a better way to detect text nodes than this
1808
+ if (item instanceof Y__namespace.XmlText) {
1809
+ const delta = item.toDelta();
1810
+ response = delta.map(/** @param {any} d */ (d) => {
1811
+ const text = {
1812
+ type: 'text',
1813
+ text: d.insert
1814
+ };
1815
+ if (d.attributes) {
1816
+ text.marks = Object.keys(d.attributes).map((type_) => {
1817
+ const attrs = d.attributes[type_];
1818
+ const type = yattr2markname(type_);
1819
+ const mark = {
1820
+ type
1821
+ };
1822
+ if (Object.keys(attrs)) {
1823
+ mark.attrs = attrs;
1824
+ }
1825
+ return mark
1826
+ });
1827
+ }
1828
+ return text
1829
+ });
1830
+ } else if (item instanceof Y__namespace.XmlElement) {
1831
+ response = {
1832
+ type: item.nodeName
1833
+ };
1834
+
1835
+ const attrs = item.getAttributes();
1836
+ if (Object.keys(attrs).length) {
1837
+ response.attrs = attrs;
1838
+ }
1839
+
1840
+ const children = item.toArray();
1841
+ if (children.length) {
1842
+ response.content = children.map(serialize).flat();
1843
+ }
1844
+ } else {
1845
+ // expected either Y.XmlElement or Y.XmlText
1846
+ error__namespace.unexpectedCase();
1847
+ }
1848
+
1849
+ return response
1850
+ };
1851
+
1852
+ return {
1853
+ type: 'doc',
1854
+ content: items.map(serialize)
1855
+ }
1856
+ }
1857
+
1858
+ /**
1859
+ * Default awareness state filter
1860
+ *
1861
+ * @param {number} currentClientId current client id
1862
+ * @param {number} userClientId user client id
1863
+ * @param {any} _user user data
1864
+ * @return {boolean}
1865
+ */
1866
+ const defaultAwarenessStateFilter = (currentClientId, userClientId, _user) => currentClientId !== userClientId;
1867
+
1868
+ /**
1869
+ * Default generator for a cursor element
1870
+ *
1871
+ * @param {any} user user data
1872
+ * @return {HTMLElement}
1873
+ */
1874
+ const defaultCursorBuilder = (user) => {
1875
+ const cursor = document.createElement('span');
1876
+ cursor.classList.add('ProseMirror-yjs-cursor');
1877
+ cursor.setAttribute('style', `border-color: ${user.color}`);
1878
+ const userDiv = document.createElement('div');
1879
+ userDiv.setAttribute('style', `background-color: ${user.color}`);
1880
+ userDiv.insertBefore(document.createTextNode(user.name), null);
1881
+ const nonbreakingSpace1 = document.createTextNode('\u2060');
1882
+ const nonbreakingSpace2 = document.createTextNode('\u2060');
1883
+ cursor.insertBefore(nonbreakingSpace1, null);
1884
+ cursor.insertBefore(userDiv, null);
1885
+ cursor.insertBefore(nonbreakingSpace2, null);
1886
+ return cursor
1887
+ };
1888
+
1889
+ /**
1890
+ * Default generator for the selection attributes
1891
+ *
1892
+ * @param {any} user user data
1893
+ * @return {import('prosemirror-view').DecorationAttrs}
1894
+ */
1895
+ const defaultSelectionBuilder = (user) => {
1896
+ return {
1897
+ style: `background-color: ${user.color}70`,
1898
+ class: 'ProseMirror-yjs-selection'
1899
+ }
1900
+ };
1901
+
1902
+ const rxValidColor = /^#[0-9a-fA-F]{6}$/;
1903
+
1904
+ /**
1905
+ * @param {any} state
1906
+ * @param {Awareness} awareness
1907
+ * @param {function(number, number, any):boolean} awarenessFilter
1908
+ * @param {(user: { name: string, color: string }, clientId: number) => Element} createCursor
1909
+ * @param {(user: { name: string, color: string }, clientId: number) => import('prosemirror-view').DecorationAttrs} createSelection
1910
+ * @return {any} DecorationSet
1911
+ */
1912
+ const createDecorations = (
1913
+ state,
1914
+ awareness,
1915
+ awarenessFilter,
1916
+ createCursor,
1917
+ createSelection
1918
+ ) => {
1919
+ const ystate = ySyncPluginKey.getState(state);
1920
+ const y = ystate.doc;
1921
+ const decorations = [];
1922
+ if (
1923
+ ystate.snapshot != null || ystate.prevSnapshot != null ||
1924
+ ystate.binding.mapping.size === 0
1925
+ ) {
1926
+ // do not render cursors while snapshot is active
1927
+ return prosemirrorView.DecorationSet.create(state.doc, [])
1928
+ }
1929
+ awareness.getStates().forEach((aw, clientId) => {
1930
+ if (!awarenessFilter(y.clientID, clientId, aw)) {
1931
+ return
1932
+ }
1933
+
1934
+ if (aw.cursor != null) {
1935
+ const user = aw.user || {};
1936
+ if (user.color == null) {
1937
+ user.color = '#ffa500';
1938
+ } else if (!rxValidColor.test(user.color)) {
1939
+ // We only support 6-digit RGB colors in y-prosemirror
1940
+ console.warn('A user uses an unsupported color format', user);
1941
+ }
1942
+ if (user.name == null) {
1943
+ user.name = `User: ${clientId}`;
1944
+ }
1945
+ let anchor = relativePositionToAbsolutePosition(
1946
+ y,
1947
+ ystate.type,
1948
+ Y__namespace.createRelativePositionFromJSON(aw.cursor.anchor),
1949
+ ystate.binding.mapping
1950
+ );
1951
+ let head = relativePositionToAbsolutePosition(
1952
+ y,
1953
+ ystate.type,
1954
+ Y__namespace.createRelativePositionFromJSON(aw.cursor.head),
1955
+ ystate.binding.mapping
1956
+ );
1957
+ if (anchor !== null && head !== null) {
1958
+ const maxsize = math__namespace.max(state.doc.content.size - 1, 0);
1959
+ anchor = math__namespace.min(anchor, maxsize);
1960
+ head = math__namespace.min(head, maxsize);
1961
+ decorations.push(
1962
+ prosemirrorView.Decoration.widget(head, () => createCursor(user, clientId), {
1963
+ key: clientId + '',
1964
+ side: 10
1965
+ })
1966
+ );
1967
+ const from = math__namespace.min(anchor, head);
1968
+ const to = math__namespace.max(anchor, head);
1969
+ decorations.push(
1970
+ prosemirrorView.Decoration.inline(from, to, createSelection(user, clientId), {
1971
+ inclusiveEnd: true,
1972
+ inclusiveStart: false
1973
+ })
1974
+ );
1975
+ }
1976
+ }
1977
+ });
1978
+ return prosemirrorView.DecorationSet.create(state.doc, decorations)
1979
+ };
1980
+
1981
+ /**
1982
+ * A prosemirror plugin that listens to awareness information on Yjs.
1983
+ * This requires that a `prosemirrorPlugin` is also bound to the prosemirror.
1984
+ *
1985
+ * @public
1986
+ * @param {Awareness} awareness
1987
+ * @param {object} opts
1988
+ * @param {function(any, any, any):boolean} [opts.awarenessStateFilter]
1989
+ * @param {(user: any, clientId: number) => HTMLElement} [opts.cursorBuilder]
1990
+ * @param {(user: any, clientId: number) => import('prosemirror-view').DecorationAttrs} [opts.selectionBuilder]
1991
+ * @param {function(any):any} [opts.getSelection]
1992
+ * @param {string} [cursorStateField] By default all editor bindings use the awareness 'cursor' field to propagate cursor information.
1993
+ * @return {any}
1994
+ */
1995
+ const yCursorPlugin = (
1996
+ awareness,
1997
+ {
1998
+ awarenessStateFilter = defaultAwarenessStateFilter,
1999
+ cursorBuilder = defaultCursorBuilder,
2000
+ selectionBuilder = defaultSelectionBuilder,
2001
+ getSelection = (state) => state.selection
2002
+ } = {},
2003
+ cursorStateField = 'cursor'
2004
+ ) =>
2005
+ new prosemirrorState.Plugin({
2006
+ key: yCursorPluginKey,
2007
+ state: {
2008
+ init (_, state) {
2009
+ return createDecorations(
2010
+ state,
2011
+ awareness,
2012
+ awarenessStateFilter,
2013
+ cursorBuilder,
2014
+ selectionBuilder
2015
+ )
2016
+ },
2017
+ apply (tr, prevState, _oldState, newState) {
2018
+ const ystate = ySyncPluginKey.getState(newState);
2019
+ const yCursorState = tr.getMeta(yCursorPluginKey);
2020
+ if (
2021
+ (ystate && ystate.isChangeOrigin) ||
2022
+ (yCursorState && yCursorState.awarenessUpdated)
2023
+ ) {
2024
+ return createDecorations(
2025
+ newState,
2026
+ awareness,
2027
+ awarenessStateFilter,
2028
+ cursorBuilder,
2029
+ selectionBuilder
2030
+ )
2031
+ }
2032
+ return prevState.map(tr.mapping, tr.doc)
2033
+ }
2034
+ },
2035
+ props: {
2036
+ decorations: (state) => {
2037
+ return yCursorPluginKey.getState(state)
2038
+ }
2039
+ },
2040
+ view: (view) => {
2041
+ const awarenessListener = () => {
2042
+ // @ts-ignore
2043
+ if (view.docView) {
2044
+ setMeta(view, yCursorPluginKey, { awarenessUpdated: true });
2045
+ }
2046
+ };
2047
+ const updateCursorInfo = () => {
2048
+ const ystate = ySyncPluginKey.getState(view.state);
2049
+ // @note We make implicit checks when checking for the cursor property
2050
+ const current = awareness.getLocalState() || {};
2051
+ if (view.hasFocus()) {
2052
+ const selection = getSelection(view.state);
2053
+ /**
2054
+ * @type {Y.RelativePosition}
2055
+ */
2056
+ const anchor = absolutePositionToRelativePosition(
2057
+ selection.anchor,
2058
+ ystate.type,
2059
+ ystate.binding.mapping
2060
+ );
2061
+ /**
2062
+ * @type {Y.RelativePosition}
2063
+ */
2064
+ const head = absolutePositionToRelativePosition(
2065
+ selection.head,
2066
+ ystate.type,
2067
+ ystate.binding.mapping
2068
+ );
2069
+ if (
2070
+ current.cursor == null ||
2071
+ !Y__namespace.compareRelativePositions(
2072
+ Y__namespace.createRelativePositionFromJSON(current.cursor.anchor),
2073
+ anchor
2074
+ ) ||
2075
+ !Y__namespace.compareRelativePositions(
2076
+ Y__namespace.createRelativePositionFromJSON(current.cursor.head),
2077
+ head
2078
+ )
2079
+ ) {
2080
+ awareness.setLocalStateField(cursorStateField, {
2081
+ anchor,
2082
+ head
2083
+ });
2084
+ }
2085
+ } else if (
2086
+ current.cursor != null &&
2087
+ relativePositionToAbsolutePosition(
2088
+ ystate.doc,
2089
+ ystate.type,
2090
+ Y__namespace.createRelativePositionFromJSON(current.cursor.anchor),
2091
+ ystate.binding.mapping
2092
+ ) !== null
2093
+ ) {
2094
+ // delete cursor information if current cursor information is owned by this editor binding
2095
+ awareness.setLocalStateField(cursorStateField, null);
2096
+ }
2097
+ };
2098
+ awareness.on('change', awarenessListener);
2099
+ view.dom.addEventListener('focusin', updateCursorInfo);
2100
+ view.dom.addEventListener('focusout', updateCursorInfo);
2101
+ return {
2102
+ update: updateCursorInfo,
2103
+ destroy: () => {
2104
+ view.dom.removeEventListener('focusin', updateCursorInfo);
2105
+ view.dom.removeEventListener('focusout', updateCursorInfo);
2106
+ awareness.off('change', awarenessListener);
2107
+ awareness.setLocalStateField(cursorStateField, null);
2108
+ }
2109
+ }
2110
+ }
2111
+ });
2112
+
2113
+ /**
2114
+ * @typedef {Object} UndoPluginState
2115
+ * @property {import('yjs').UndoManager} undoManager
2116
+ * @property {ReturnType<typeof createRecoverableSelection> | null} prevSel
2117
+ * @property {boolean} hasUndoOps
2118
+ * @property {boolean} hasRedoOps
2119
+ */
2120
+
2121
+ /**
2122
+ * Undo the last user action
2123
+ *
2124
+ * @param {import('prosemirror-state').EditorState} state
2125
+ * @return {boolean} whether a change was undone
2126
+ */
2127
+ const undo = state => yUndoPluginKey.getState(state)?.undoManager?.undo() != null;
2128
+
2129
+ /**
2130
+ * Redo the last user action
2131
+ *
2132
+ * @param {import('prosemirror-state').EditorState} state
2133
+ * @return {boolean} whether a change was undone
2134
+ */
2135
+ const redo = state => yUndoPluginKey.getState(state)?.undoManager?.redo() != null;
2136
+
2137
+ /**
2138
+ * Undo the last user action if there are undo operations available
2139
+ * @type {import('prosemirror-state').Command}
2140
+ */
2141
+ const undoCommand = (state, dispatch) => dispatch == null ? yUndoPluginKey.getState(state)?.undoManager?.canUndo() : undo(state);
2142
+
2143
+ /**
2144
+ * Redo the last user action if there are redo operations available
2145
+ * @type {import('prosemirror-state').Command}
2146
+ */
2147
+ const redoCommand = (state, dispatch) => dispatch == null ? yUndoPluginKey.getState(state)?.undoManager?.canRedo() : redo(state);
2148
+
2149
+ const defaultProtectedNodes = new Set(['paragraph']);
2150
+
2151
+ /**
2152
+ * @param {import('yjs').Item} item
2153
+ * @param {Set<string>} protectedNodes
2154
+ * @returns {boolean}
2155
+ */
2156
+ const defaultDeleteFilter = (item, protectedNodes) => !(item instanceof Y.Item) ||
2157
+ !(item.content instanceof Y.ContentType) ||
2158
+ !(item.content.type instanceof Y.Text ||
2159
+ (item.content.type instanceof Y.XmlElement && protectedNodes.has(item.content.type.nodeName))) ||
2160
+ item.content.type._length === 0;
2161
+
2162
+ /**
2163
+ * @param {object} [options]
2164
+ * @param {Set<string>} [options.protectedNodes]
2165
+ * @param {any[]} [options.trackedOrigins]
2166
+ * @param {import('yjs').UndoManager | null} [options.undoManager]
2167
+ */
2168
+ const yUndoPlugin = ({ protectedNodes = defaultProtectedNodes, trackedOrigins = [], undoManager = null } = {}) => new prosemirrorState.Plugin({
2169
+ key: yUndoPluginKey,
2170
+ state: {
2171
+ init: (initargs, state) => {
2172
+ // TODO: check if plugin order matches and fix
2173
+ const ystate = ySyncPluginKey.getState(state);
2174
+ const _undoManager = undoManager || new Y.UndoManager(ystate.type, {
2175
+ trackedOrigins: new Set([ySyncPluginKey].concat(trackedOrigins)),
2176
+ deleteFilter: (item) => defaultDeleteFilter(item, protectedNodes),
2177
+ captureTransaction: tr => tr.meta.get('addToHistory') !== false
2178
+ });
2179
+ return {
2180
+ undoManager: _undoManager,
2181
+ prevSel: null,
2182
+ hasUndoOps: _undoManager.undoStack.length > 0,
2183
+ hasRedoOps: _undoManager.redoStack.length > 0
2184
+ }
2185
+ },
2186
+ apply: (tr, val, oldState, state) => {
2187
+ const binding = ySyncPluginKey.getState(state).binding;
2188
+ const undoManager = val.undoManager;
2189
+ const hasUndoOps = undoManager.undoStack.length > 0;
2190
+ const hasRedoOps = undoManager.redoStack.length > 0;
2191
+ if (binding) {
2192
+ return {
2193
+ undoManager,
2194
+ prevSel: createRecoverableSelection(binding, oldState),
2195
+ hasUndoOps,
2196
+ hasRedoOps
2197
+ }
2198
+ } else {
2199
+ if (hasUndoOps !== val.hasUndoOps || hasRedoOps !== val.hasRedoOps) {
2200
+ return Object.assign({}, val, {
2201
+ hasUndoOps: undoManager.undoStack.length > 0,
2202
+ hasRedoOps: undoManager.redoStack.length > 0
2203
+ })
2204
+ } else { // nothing changed
2205
+ return val
2206
+ }
2207
+ }
2208
+ }
2209
+ },
2210
+ view: view => {
2211
+ const undoManager = yUndoPluginKey.getState(view.state).undoManager;
2212
+ undoManager.on('stack-item-added', ({ stackItem }) => {
2213
+ const ystate = ySyncPluginKey.getState(view.state);
2214
+ const binding = ystate.binding;
2215
+ if (binding) {
2216
+ stackItem.meta.set(binding, binding.beforePatchSelection);
2217
+ }
2218
+ });
2219
+ undoManager.on('stack-item-pop', ({ stackItem }) => {
2220
+ const ystate = ySyncPluginKey.getState(view.state);
2221
+ const binding = ystate.binding;
2222
+ if (binding) {
2223
+ binding.beforeTransactionSelection = stackItem.meta.get(binding) || binding.beforeTransactionSelection;
2224
+ }
2225
+ });
2226
+ return {
2227
+ destroy: () => {
2228
+ undoManager.destroy();
2229
+ }
2230
+ }
2231
+ }
2232
+ });
2233
+
2234
+ exports.ProsemirrorBinding = ProsemirrorBinding;
2235
+ exports.absolutePositionToRelativePosition = absolutePositionToRelativePosition;
2236
+ exports.createDecorations = createDecorations;
2237
+ exports.defaultAwarenessStateFilter = defaultAwarenessStateFilter;
2238
+ exports.defaultCursorBuilder = defaultCursorBuilder;
2239
+ exports.defaultDeleteFilter = defaultDeleteFilter;
2240
+ exports.defaultProtectedNodes = defaultProtectedNodes;
2241
+ exports.defaultSelectionBuilder = defaultSelectionBuilder;
2242
+ exports.getRelativeSelection = getRelativeSelection;
2243
+ exports.initProseMirrorDoc = initProseMirrorDoc;
2244
+ exports.isVisible = isVisible;
2245
+ exports.prosemirrorJSONToYDoc = prosemirrorJSONToYDoc;
2246
+ exports.prosemirrorJSONToYXmlFragment = prosemirrorJSONToYXmlFragment;
2247
+ exports.prosemirrorToYDoc = prosemirrorToYDoc;
2248
+ exports.prosemirrorToYXmlFragment = prosemirrorToYXmlFragment;
2249
+ exports.redo = redo;
2250
+ exports.redoCommand = redoCommand;
2251
+ exports.relativePositionToAbsolutePosition = relativePositionToAbsolutePosition;
2252
+ exports.setMeta = setMeta;
2253
+ exports.undo = undo;
2254
+ exports.undoCommand = undoCommand;
2255
+ exports.updateYFragment = updateYFragment;
2256
+ exports.yCursorPlugin = yCursorPlugin;
2257
+ exports.yCursorPluginKey = yCursorPluginKey;
2258
+ exports.yDocToProsemirror = yDocToProsemirror;
2259
+ exports.yDocToProsemirrorJSON = yDocToProsemirrorJSON;
2260
+ exports.ySyncPlugin = ySyncPlugin;
2261
+ exports.ySyncPluginKey = ySyncPluginKey;
2262
+ exports.yUndoPlugin = yUndoPlugin;
2263
+ exports.yUndoPluginKey = yUndoPluginKey;
2264
+ exports.yXmlFragmentToProseMirrorFragment = yXmlFragmentToProseMirrorFragment;
2265
+ exports.yXmlFragmentToProseMirrorRootNode = yXmlFragmentToProseMirrorRootNode;
2266
+ exports.yXmlFragmentToProsemirror = yXmlFragmentToProsemirror;
2267
+ exports.yXmlFragmentToProsemirrorJSON = yXmlFragmentToProsemirrorJSON;
2268
+ //# sourceMappingURL=y-prosemirror.cjs.map