@tiptap/y-tiptap 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/y-tiptap.js CHANGED
@@ -1,21 +1,24 @@
1
1
  import * as Y from 'yjs';
2
2
  import { Item, ContentType, Text, XmlElement, UndoManager } from 'yjs';
3
- import { DecorationSet, Decoration } from '@tiptap/pm/view';
4
- import { PluginKey, Plugin, TextSelection } from '@tiptap/pm/state';
3
+ import { DecorationSet, Decoration } from 'prosemirror-view';
4
+ import { Plugin, TextSelection, AllSelection, NodeSelection } from 'prosemirror-state';
5
5
  import 'y-protocols/awareness';
6
6
  import { createMutex } from 'lib0/mutex';
7
- import * as PModel from '@tiptap/pm/model';
8
- import { Fragment, Node } from '@tiptap/pm/model';
7
+ import * as PModel from 'prosemirror-model';
8
+ import { Fragment, Node } from 'prosemirror-model';
9
9
  import * as math from 'lib0/math';
10
+ import * as object from 'lib0/object';
10
11
  import * as set from 'lib0/set';
11
12
  import { simpleDiff } from 'lib0/diff';
12
13
  import * as error from 'lib0/error';
14
+ import { PluginKey, Plugin as Plugin$1 } from '@tiptap/pm/state';
13
15
  import * as random from 'lib0/random';
14
16
  import * as environment from 'lib0/environment';
15
17
  import * as dom from 'lib0/dom';
16
18
  import * as eventloop from 'lib0/eventloop';
17
- import * as f from 'lib0/function';
18
19
  import * as map from 'lib0/map';
20
+ import * as sha256 from 'lib0/hash/sha256';
21
+ import * as buf from 'lib0/buffer';
19
22
 
20
23
  /**
21
24
  * The unique prosemirror plugin key for syncPlugin
@@ -38,11 +41,41 @@ const yUndoPluginKey = new PluginKey('y-undo');
38
41
  */
39
42
  const yCursorPluginKey = new PluginKey('yjs-cursor');
40
43
 
44
+ /**
45
+ * Custom function to transform sha256 hash to N byte
46
+ *
47
+ * @param {Uint8Array} digest
48
+ */
49
+ const _convolute = digest => {
50
+ const N = 6;
51
+ for (let i = N; i < digest.length; i++) {
52
+ digest[i % N] = digest[i % N] ^ digest[i];
53
+ }
54
+ return digest.slice(0, N)
55
+ };
56
+
57
+ /**
58
+ * @param {any} json
59
+ */
60
+ const hashOfJSON = (json) => buf.toBase64(_convolute(sha256.digest(buf.encodeAny(json))));
61
+
41
62
  /**
42
63
  * @module bindings/prosemirror
43
64
  */
44
65
 
45
- const MarkPrefix = '_mark_';
66
+ /**
67
+ * @typedef {Object} BindingMetadata
68
+ * @property {ProsemirrorMapping} BindingMetadata.mapping
69
+ * @property {Map<import('prosemirror-model').MarkType, boolean>} BindingMetadata.isOMark - is overlapping mark
70
+ */
71
+
72
+ /**
73
+ * @return BindingMetadata
74
+ */
75
+ const createEmptyMeta = () => ({
76
+ mapping: new Map(),
77
+ isOMark: new Map()
78
+ });
46
79
 
47
80
  /**
48
81
  * @param {Y.Item} item
@@ -249,31 +282,48 @@ const ySyncPlugin = (yXmlFragment, {
249
282
  };
250
283
 
251
284
  /**
252
- * @param {any} tr
253
- * @param {any} relSel
285
+ * @param {import('prosemirror-state').Transaction} tr
286
+ * @param {ReturnType<typeof getRelativeSelection>} relSel
254
287
  * @param {ProsemirrorBinding} binding
255
288
  */
256
289
  const restoreRelativeSelection = (tr, relSel, binding) => {
257
290
  if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
258
- const anchor = relativePositionToAbsolutePosition(
259
- binding.doc,
260
- binding.type,
261
- relSel.anchor,
262
- binding.mapping
263
- );
264
- const head = relativePositionToAbsolutePosition(
265
- binding.doc,
266
- binding.type,
267
- relSel.head,
268
- binding.mapping
269
- );
270
- if (anchor !== null && head !== null) {
271
- tr = tr.setSelection(TextSelection.create(tr.doc, anchor, head));
291
+ if (relSel.type === 'all') {
292
+ tr.setSelection(new AllSelection(tr.doc));
293
+ } else if (relSel.type === 'node') {
294
+ const anchor = relativePositionToAbsolutePosition(
295
+ binding.doc,
296
+ binding.type,
297
+ relSel.anchor,
298
+ binding.mapping
299
+ );
300
+ tr.setSelection(NodeSelection.create(tr.doc, anchor));
301
+ } else {
302
+ const anchor = relativePositionToAbsolutePosition(
303
+ binding.doc,
304
+ binding.type,
305
+ relSel.anchor,
306
+ binding.mapping
307
+ );
308
+ const head = relativePositionToAbsolutePosition(
309
+ binding.doc,
310
+ binding.type,
311
+ relSel.head,
312
+ binding.mapping
313
+ );
314
+ if (anchor !== null && head !== null) {
315
+ tr.setSelection(TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head)));
316
+ }
272
317
  }
273
318
  }
274
319
  };
275
320
 
321
+ /**
322
+ * @param {ProsemirrorBinding} pmbinding
323
+ * @param {import('prosemirror-state').EditorState} state
324
+ */
276
325
  const getRelativeSelection = (pmbinding, state) => ({
326
+ type: /** @type {any} */ (state.selection).jsonID,
277
327
  anchor: absolutePositionToRelativePosition(
278
328
  state.selection.anchor,
279
329
  pmbinding.type,
@@ -305,6 +355,12 @@ class ProsemirrorBinding {
305
355
  this.prosemirrorView = null;
306
356
  this.mux = createMutex();
307
357
  this.mapping = mapping;
358
+ /**
359
+ * Is overlapping mark - i.e. mark does not exclude itself.
360
+ *
361
+ * @type {Map<import('prosemirror-model').MarkType, boolean>}
362
+ */
363
+ this.isOMark = new Map();
308
364
  this._observeFunction = this._typeChanged.bind(this);
309
365
  /**
310
366
  * @type {Y.Doc}
@@ -399,7 +455,7 @@ class ProsemirrorBinding {
399
455
  createNodeFromYElement(
400
456
  /** @type {Y.XmlElement} */ (t),
401
457
  this.prosemirrorView.state.schema,
402
- this.mapping
458
+ this
403
459
  )
404
460
  ).filter((n) => n !== null);
405
461
  // @ts-ignore
@@ -424,7 +480,7 @@ class ProsemirrorBinding {
424
480
  createNodeFromYElement(
425
481
  /** @type {Y.XmlElement} */ (t),
426
482
  this.prosemirrorView.state.schema,
427
- this.mapping
483
+ this
428
484
  )
429
485
  ).filter((n) => n !== null);
430
486
  // @ts-ignore
@@ -461,6 +517,7 @@ class ProsemirrorBinding {
461
517
  * @type {Y.Doc}
462
518
  */
463
519
  let historyDoc = this.doc;
520
+ let historyType = this.type;
464
521
  if (!snapshot) {
465
522
  snapshot = Y.snapshot(this.doc);
466
523
  }
@@ -474,6 +531,29 @@ class ProsemirrorBinding {
474
531
  prevSnapshot = Y.snapshot(historyDoc);
475
532
  Y.applyUpdateV2(historyDoc, snapshot);
476
533
  snapshot = Y.snapshot(historyDoc);
534
+ if (historyType._item === null) {
535
+ /**
536
+ * If is a root type, we need to find the root key in the initial document
537
+ * and use it to get the history type.
538
+ */
539
+ const rootKey = Array.from(this.doc.share.keys()).find(
540
+ (key) => this.doc.share.get(key) === this.type
541
+ );
542
+ historyType = historyDoc.getXmlFragment(rootKey);
543
+ } else {
544
+ /**
545
+ * If it is a sub type, we use the item id to find the history type.
546
+ */
547
+ const historyStructs =
548
+ historyDoc.store.clients.get(historyType._item.id.client) ?? [];
549
+ const itemIndex = Y.findIndexSS(
550
+ historyStructs,
551
+ historyType._item.id.clock
552
+ );
553
+ const item = /** @type {Y.Item} */ (historyStructs[itemIndex]);
554
+ const content = /** @type {Y.ContentType} */ (item.content);
555
+ historyType = /** @type {Y.XmlFragment} */ (content.type);
556
+ }
477
557
  }
478
558
  // clear mapping because we are going to rerender
479
559
  this.mapping.clear();
@@ -481,6 +561,9 @@ class ProsemirrorBinding {
481
561
  historyDoc.transact((transaction) => {
482
562
  // before rendering, we are going to sanitize ops and split deleted ops
483
563
  // if they were deleted by seperate users.
564
+ /**
565
+ * @type {Y.PermanentUserData}
566
+ */
484
567
  const pud = pluginState.permanentUserData;
485
568
  if (pud) {
486
569
  pud.dss.forEach((ds) => {
@@ -507,7 +590,7 @@ class ProsemirrorBinding {
507
590
  };
508
591
  // Create document fragment and render
509
592
  const fragmentContent = Y.typeListToArraySnapshot(
510
- this.type,
593
+ historyType,
511
594
  new Y.Snapshot(prevSnapshot.ds, snapshot.sv)
512
595
  ).map((t) => {
513
596
  if (
@@ -517,7 +600,7 @@ class ProsemirrorBinding {
517
600
  return createNodeFromYElement(
518
601
  t,
519
602
  this.prosemirrorView.state.schema,
520
- new Map(),
603
+ { mapping: new Map(), isOMark: new Map() },
521
604
  snapshot,
522
605
  prevSnapshot,
523
606
  computeYChange
@@ -578,7 +661,7 @@ class ProsemirrorBinding {
578
661
  createNodeIfNotExists(
579
662
  /** @type {Y.XmlElement | Y.XmlHook} */ (t),
580
663
  this.prosemirrorView.state.schema,
581
- this.mapping
664
+ this
582
665
  )
583
666
  ).filter((n) => n !== null);
584
667
  // @ts-ignore
@@ -598,9 +681,12 @@ class ProsemirrorBinding {
598
681
  });
599
682
  }
600
683
 
684
+ /**
685
+ * @param {import('prosemirror-model').Node} doc
686
+ */
601
687
  _prosemirrorChanged (doc) {
602
688
  this.doc.transact(() => {
603
- updateYFragment(this.doc, this.type, doc, this.mapping);
689
+ updateYFragment(this.doc, this.type, doc, this);
604
690
  this.beforeTransactionSelection = getRelativeSelection(
605
691
  this,
606
692
  this.prosemirrorView.state
@@ -633,7 +719,7 @@ class ProsemirrorBinding {
633
719
  * @private
634
720
  * @param {Y.XmlElement | Y.XmlHook} el
635
721
  * @param {PModel.Schema} schema
636
- * @param {ProsemirrorMapping} mapping
722
+ * @param {BindingMetadata} meta
637
723
  * @param {Y.Snapshot} [snapshot]
638
724
  * @param {Y.Snapshot} [prevSnapshot]
639
725
  * @param {function('removed' | 'added', Y.ID):any} [computeYChange]
@@ -642,18 +728,18 @@ class ProsemirrorBinding {
642
728
  const createNodeIfNotExists = (
643
729
  el,
644
730
  schema,
645
- mapping,
731
+ meta,
646
732
  snapshot,
647
733
  prevSnapshot,
648
734
  computeYChange
649
735
  ) => {
650
- const node = /** @type {PModel.Node} */ (mapping.get(el));
736
+ const node = /** @type {PModel.Node} */ (meta.mapping.get(el));
651
737
  if (node === undefined) {
652
738
  if (el instanceof Y.XmlElement) {
653
739
  return createNodeFromYElement(
654
740
  el,
655
741
  schema,
656
- mapping,
742
+ meta,
657
743
  snapshot,
658
744
  prevSnapshot,
659
745
  computeYChange
@@ -669,7 +755,7 @@ const createNodeIfNotExists = (
669
755
  * @private
670
756
  * @param {Y.XmlElement} el
671
757
  * @param {any} schema
672
- * @param {ProsemirrorMapping} mapping
758
+ * @param {BindingMetadata} meta
673
759
  * @param {Y.Snapshot} [snapshot]
674
760
  * @param {Y.Snapshot} [prevSnapshot]
675
761
  * @param {function('removed' | 'added', Y.ID):any} [computeYChange]
@@ -678,18 +764,21 @@ const createNodeIfNotExists = (
678
764
  const createNodeFromYElement = (
679
765
  el,
680
766
  schema,
681
- mapping,
767
+ meta,
682
768
  snapshot,
683
769
  prevSnapshot,
684
770
  computeYChange
685
771
  ) => {
686
772
  const children = [];
773
+ /**
774
+ * @param {Y.XmlElement | Y.XmlText} type
775
+ */
687
776
  const createChildren = (type) => {
688
- if (type.constructor === Y.XmlElement) {
777
+ if (type instanceof Y.XmlElement) {
689
778
  const n = createNodeIfNotExists(
690
779
  type,
691
780
  schema,
692
- mapping,
781
+ meta,
693
782
  snapshot,
694
783
  prevSnapshot,
695
784
  computeYChange
@@ -701,7 +790,7 @@ const createNodeFromYElement = (
701
790
  // If the next ytext exists and was created by us, move the content to the current ytext.
702
791
  // This is a fix for #160 -- duplication of characters when two Y.Text exist next to each
703
792
  // other.
704
- const nextytext = type._item.right?.content.type;
793
+ const nextytext = /** @type {Y.ContentType} */ (type._item.right?.content)?.type;
705
794
  if (nextytext instanceof Y.Text && !nextytext._item.deleted && nextytext._item.id.client === nextytext.doc.clientID) {
706
795
  type.applyDelta([
707
796
  { retain: type.length },
@@ -715,7 +804,7 @@ const createNodeFromYElement = (
715
804
  const ns = createTextNodesFromYText(
716
805
  type,
717
806
  schema,
718
- mapping,
807
+ meta,
719
808
  snapshot,
720
809
  prevSnapshot,
721
810
  computeYChange
@@ -748,32 +837,15 @@ const createNodeFromYElement = (
748
837
  : { type: 'added' };
749
838
  }
750
839
  }
751
- const nodeAttrs = {};
752
- const nodeMarks = [];
753
-
754
- for (const key in attrs) {
755
- if (key.startsWith(MarkPrefix)) {
756
- const markName = key.replace(MarkPrefix, '');
757
- const markValue = attrs[key];
758
- if (isObject(markValue)) {
759
- nodeMarks.push(schema.mark(markName, /** @type {Object} */ (markValue).attrs));
760
- } else if (Array.isArray(markValue)) {
761
- nodeMarks.push(...markValue.map(attrs => schema.mark(markName, attrs)));
762
- }
763
- } else {
764
- nodeAttrs[key] = attrs[key];
765
- }
766
- }
767
-
768
- const node = schema.node(el.nodeName, nodeAttrs, children, nodeMarks);
769
- mapping.set(el, node);
840
+ const node = schema.node(el.nodeName, attrs, children);
841
+ meta.mapping.set(el, node);
770
842
  return node
771
843
  } catch (e) {
772
844
  // an error occured while creating the node. This is probably a result of a concurrent action.
773
845
  /** @type {Y.Doc} */ (el.doc).transact((transaction) => {
774
846
  /** @type {Y.Item} */ (el._item).delete(transaction);
775
847
  }, ySyncPluginKey);
776
- mapping.delete(el);
848
+ meta.mapping.delete(el);
777
849
  return null
778
850
  }
779
851
  };
@@ -781,8 +853,8 @@ const createNodeFromYElement = (
781
853
  /**
782
854
  * @private
783
855
  * @param {Y.XmlText} text
784
- * @param {any} schema
785
- * @param {ProsemirrorMapping} _mapping
856
+ * @param {import('prosemirror-model').Schema} schema
857
+ * @param {BindingMetadata} _meta
786
858
  * @param {Y.Snapshot} [snapshot]
787
859
  * @param {Y.Snapshot} [prevSnapshot]
788
860
  * @param {function('removed' | 'added', Y.ID):any} [computeYChange]
@@ -791,7 +863,7 @@ const createNodeFromYElement = (
791
863
  const createTextNodesFromYText = (
792
864
  text,
793
865
  schema,
794
- _mapping,
866
+ _meta,
795
867
  snapshot,
796
868
  prevSnapshot,
797
869
  computeYChange
@@ -801,19 +873,7 @@ const createTextNodesFromYText = (
801
873
  try {
802
874
  for (let i = 0; i < deltas.length; i++) {
803
875
  const delta = deltas[i];
804
- const marks = [];
805
- for (const markName in delta.attributes) {
806
- if (Array.isArray(delta.attributes[markName])) {
807
- // multiple marks of same type
808
- delta.attributes[markName].forEach(attrs => {
809
- marks.push(schema.mark(markName, attrs));
810
- });
811
- } else {
812
- // single mark
813
- marks.push(schema.mark(markName, delta.attributes[markName]));
814
- }
815
- }
816
- nodes.push(schema.text(delta.insert, marks));
876
+ nodes.push(schema.text(delta.insert, attributesToMarks(delta.attributes, schema)));
817
877
  }
818
878
  } catch (e) {
819
879
  // an error occured while creating the node. This is probably a result of a concurrent action.
@@ -829,67 +889,70 @@ const createTextNodesFromYText = (
829
889
  /**
830
890
  * @private
831
891
  * @param {Array<any>} nodes prosemirror node
832
- * @param {ProsemirrorMapping} mapping
892
+ * @param {BindingMetadata} meta
833
893
  * @return {Y.XmlText}
834
894
  */
835
- const createTypeFromTextNodes = (nodes, mapping) => {
895
+ const createTypeFromTextNodes = (nodes, meta) => {
836
896
  const type = new Y.XmlText();
837
897
  const delta = nodes.map((node) => ({
838
898
  // @ts-ignore
839
899
  insert: node.text,
840
- attributes: marksToAttributes(node.marks)
900
+ attributes: marksToAttributes(node.marks, meta)
841
901
  }));
842
902
  type.applyDelta(delta);
843
- mapping.set(type, nodes);
903
+ meta.mapping.set(type, nodes);
844
904
  return type
845
905
  };
846
906
 
847
907
  /**
848
908
  * @private
849
909
  * @param {any} node prosemirror node
850
- * @param {ProsemirrorMapping} mapping
910
+ * @param {BindingMetadata} meta
851
911
  * @return {Y.XmlElement}
852
912
  */
853
- const createTypeFromElementNode = (node, mapping) => {
913
+ const createTypeFromElementNode = (node, meta) => {
854
914
  const type = new Y.XmlElement(node.type.name);
855
- const nodeMarksAttr = nodeMarksToAttributes(node.marks);
856
915
  for (const key in node.attrs) {
857
916
  const val = node.attrs[key];
858
917
  if (val !== null && key !== 'ychange') {
859
918
  type.setAttribute(key, val);
860
919
  }
861
920
  }
862
- for (const key in nodeMarksAttr) {
863
- type.setAttribute(key, nodeMarksAttr[key]);
864
- }
865
921
  type.insert(
866
922
  0,
867
923
  normalizePNodeContent(node).map((n) =>
868
- createTypeFromTextOrElementNode(n, mapping)
924
+ createTypeFromTextOrElementNode(n, meta)
869
925
  )
870
926
  );
871
- mapping.set(type, node);
927
+ meta.mapping.set(type, node);
872
928
  return type
873
929
  };
874
930
 
875
931
  /**
876
932
  * @private
877
933
  * @param {PModel.Node|Array<PModel.Node>} node prosemirror text node
878
- * @param {ProsemirrorMapping} mapping
934
+ * @param {BindingMetadata} meta
879
935
  * @return {Y.XmlElement|Y.XmlText}
880
936
  */
881
- const createTypeFromTextOrElementNode = (node, mapping) =>
937
+ const createTypeFromTextOrElementNode = (node, meta) =>
882
938
  node instanceof Array
883
- ? createTypeFromTextNodes(node, mapping)
884
- : createTypeFromElementNode(node, mapping);
939
+ ? createTypeFromTextNodes(node, meta)
940
+ : createTypeFromElementNode(node, meta);
885
941
 
942
+ /**
943
+ * @param {any} val
944
+ */
886
945
  const isObject = (val) => typeof val === 'object' && val !== null;
887
946
 
947
+ /**
948
+ * @param {any} pattrs
949
+ * @param {any} yattrs
950
+ */
888
951
  const equalAttrs = (pattrs, yattrs) => {
889
952
  const keys = Object.keys(pattrs).filter((key) => pattrs[key] !== null);
890
953
  let eq =
891
954
  keys.length ===
892
- Object.keys(yattrs).filter((key) => yattrs[key] !== null && !key.startsWith(MarkPrefix)).length;
955
+ Object.keys(yattrs).filter((key) => yattrs[key] !== null).length;
893
956
  for (let i = 0; i < keys.length && eq; i++) {
894
957
  const key = keys[i];
895
958
  const l = pattrs[key];
@@ -900,20 +963,6 @@ const equalAttrs = (pattrs, yattrs) => {
900
963
  return eq
901
964
  };
902
965
 
903
- const equalMarks = (pmarks, yattrs) => {
904
- const keys = Object.keys(yattrs).filter((key) => key.startsWith(MarkPrefix));
905
- let eq =
906
- keys.length === pmarks.length;
907
- const pMarkAttr = nodeMarksToAttributes(pmarks);
908
- for (let i = 0; i < keys.length && eq; i++) {
909
- const key = keys[i];
910
- const l = pMarkAttr[key];
911
- const r = yattrs[key];
912
- eq = key === 'ychange' || f.equalityDeep(l, r);
913
- }
914
- return eq
915
- };
916
-
917
966
  /**
918
967
  * @typedef {Array<Array<PModel.Node>|PModel.Node>} NormalizedPNodeContent
919
968
  */
@@ -948,17 +997,14 @@ const normalizePNodeContent = (pnode) => {
948
997
  const equalYTextPText = (ytext, ptexts) => {
949
998
  const delta = ytext.toDelta();
950
999
  return delta.length === ptexts.length &&
951
- delta.every((d, i) =>
1000
+ delta.every(/** @type {(d:any,i:number) => boolean} */ (d, i) =>
952
1001
  d.insert === /** @type {any} */ (ptexts[i]).text &&
953
- Object.keys(d.attributes || {}).reduce((sum, val) => sum + (Array.isArray(val) ? val.length : 1), 0) === ptexts[i].marks.length &&
954
- ptexts[i].marks.every((mark) => {
955
- const yattrs = d.attributes || {};
956
- if (Array.isArray(yattrs)) {
957
- return yattrs.some((yattr) => equalAttrs(mark.attrs, yattr))
958
- }
959
- return equalAttrs(mark.attrs, yattrs)
960
- }
961
- )
1002
+ object.keys(d.attributes || {}).length === ptexts[i].marks.length &&
1003
+ object.every(d.attributes, (attr, yattrname) => {
1004
+ const markname = yattr2markname(yattrname);
1005
+ const pmarks = ptexts[i].marks;
1006
+ return equalAttrs(attr, pmarks.find(/** @param {any} mark */ mark => mark.type.name === markname)?.attrs)
1007
+ })
962
1008
  )
963
1009
  };
964
1010
 
@@ -973,8 +1019,7 @@ const equalYTypePNode = (ytype, pnode) => {
973
1019
  ) {
974
1020
  const normalizedContent = normalizePNodeContent(pnode);
975
1021
  return ytype._length === normalizedContent.length &&
976
- equalAttrs(pnode.attrs, ytype.getAttributes()) &&
977
- equalMarks(pnode.marks, ytype.getAttributes()) &&
1022
+ equalAttrs(ytype.getAttributes(), pnode.attrs) &&
978
1023
  ytype.toArray().every((ychild, i) =>
979
1024
  equalYTypePNode(ychild, normalizedContent[i])
980
1025
  )
@@ -997,10 +1042,10 @@ const mappedIdentity = (mapped, pcontent) =>
997
1042
  /**
998
1043
  * @param {Y.XmlElement} ytype
999
1044
  * @param {PModel.Node} pnode
1000
- * @param {ProsemirrorMapping} mapping
1045
+ * @param {BindingMetadata} meta
1001
1046
  * @return {{ foundMappedChild: boolean, equalityFactor: number }}
1002
1047
  */
1003
- const computeChildEqualityFactor = (ytype, pnode, mapping) => {
1048
+ const computeChildEqualityFactor = (ytype, pnode, meta) => {
1004
1049
  const yChildren = ytype.toArray();
1005
1050
  const pChildren = normalizePNodeContent(pnode);
1006
1051
  const pChildCnt = pChildren.length;
@@ -1012,7 +1057,7 @@ const computeChildEqualityFactor = (ytype, pnode, mapping) => {
1012
1057
  for (; left < minCnt; left++) {
1013
1058
  const leftY = yChildren[left];
1014
1059
  const leftP = pChildren[left];
1015
- if (mappedIdentity(mapping.get(leftY), leftP)) {
1060
+ if (mappedIdentity(meta.mapping.get(leftY), leftP)) {
1016
1061
  foundMappedChild = true; // definite (good) match!
1017
1062
  } else if (!equalYTypePNode(leftY, leftP)) {
1018
1063
  break
@@ -1021,7 +1066,7 @@ const computeChildEqualityFactor = (ytype, pnode, mapping) => {
1021
1066
  for (; left + right < minCnt; right++) {
1022
1067
  const rightY = yChildren[yChildCnt - right - 1];
1023
1068
  const rightP = pChildren[pChildCnt - right - 1];
1024
- if (mappedIdentity(mapping.get(rightY), rightP)) {
1069
+ if (mappedIdentity(meta.mapping.get(rightY), rightP)) {
1025
1070
  foundMappedChild = true;
1026
1071
  } else if (!equalYTypePNode(rightY, rightP)) {
1027
1072
  break
@@ -1033,6 +1078,9 @@ const computeChildEqualityFactor = (ytype, pnode, mapping) => {
1033
1078
  }
1034
1079
  };
1035
1080
 
1081
+ /**
1082
+ * @param {Y.Text} ytext
1083
+ */
1036
1084
  const ytextTrans = (ytext) => {
1037
1085
  let str = '';
1038
1086
  /**
@@ -1061,14 +1109,14 @@ const ytextTrans = (ytext) => {
1061
1109
  *
1062
1110
  * @param {Y.Text} ytext
1063
1111
  * @param {Array<any>} ptexts
1064
- * @param {ProsemirrorMapping} mapping
1112
+ * @param {BindingMetadata} meta
1065
1113
  */
1066
- const updateYText = (ytext, ptexts, mapping) => {
1067
- mapping.set(ytext, ptexts);
1114
+ const updateYText = (ytext, ptexts, meta) => {
1115
+ meta.mapping.set(ytext, ptexts);
1068
1116
  const { nAttrs, str } = ytextTrans(ytext);
1069
1117
  const content = ptexts.map((p) => ({
1070
1118
  insert: /** @type {any} */ (p).text,
1071
- attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks))
1119
+ attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks, meta))
1072
1120
  }));
1073
1121
  const { insert, remove, index } = simpleDiff(
1074
1122
  str,
@@ -1081,30 +1129,40 @@ const updateYText = (ytext, ptexts, mapping) => {
1081
1129
  );
1082
1130
  };
1083
1131
 
1084
- const marksToAttributes = (marks) => {
1085
- const pattrs = {};
1086
- marks.forEach((mark) => {
1087
- if (mark.type.name !== 'ychange') {
1088
- if (pattrs[mark.type.name] && Array.isArray(pattrs[mark.type.name])) {
1089
- // already has multiple marks of same type
1090
- pattrs[mark.type.name].push(mark.attrs);
1091
- } else if (pattrs[mark.type.name]) {
1092
- // already has mark of same type, change to array
1093
- pattrs[mark.type.name] = [pattrs[mark.type.name], mark.attrs];
1094
- } else {
1095
- // first mark of this type
1096
- pattrs[mark.type.name] = mark.attrs;
1097
- }
1098
- }
1099
- });
1100
- return pattrs
1132
+ const hashedMarkNameRegex = /(.*)(--[a-zA-Z0-9+/=]{8})$/;
1133
+ /**
1134
+ * @param {string} attrName
1135
+ */
1136
+ const yattr2markname = attrName => hashedMarkNameRegex.exec(attrName)?.[1] ?? attrName;
1137
+
1138
+ /**
1139
+ * @todo move this to markstoattributes
1140
+ *
1141
+ * @param {Object<string, any>} attrs
1142
+ * @param {import('prosemirror-model').Schema} schema
1143
+ */
1144
+ const attributesToMarks = (attrs, schema) => {
1145
+ /**
1146
+ * @type {Array<import('prosemirror-model').Mark>}
1147
+ */
1148
+ const marks = [];
1149
+ for (const markName in attrs) {
1150
+ // remove hashes if necessary
1151
+ marks.push(schema.mark(yattr2markname(markName), attrs[markName]));
1152
+ }
1153
+ return marks
1101
1154
  };
1102
1155
 
1103
- const nodeMarksToAttributes = (marks) => {
1156
+ /**
1157
+ * @param {Array<import('prosemirror-model').Mark>} marks
1158
+ * @param {BindingMetadata} meta
1159
+ */
1160
+ const marksToAttributes = (marks, meta) => {
1104
1161
  const pattrs = {};
1105
1162
  marks.forEach((mark) => {
1106
1163
  if (mark.type.name !== 'ychange') {
1107
- pattrs[`${MarkPrefix}${mark.type.name}`] = mark.toJSON();
1164
+ const isOverlapping = map.setIfUndefined(meta.isOMark, mark.type, () => !mark.type.excludes(mark.type));
1165
+ pattrs[isOverlapping ? `${mark.type.name}--${hashOfJSON(mark.toJSON())}` : mark.type.name] = mark.attrs;
1108
1166
  }
1109
1167
  });
1110
1168
  return pattrs
@@ -1113,7 +1171,7 @@ const nodeMarksToAttributes = (marks) => {
1113
1171
  /**
1114
1172
  * Update a yDom node by syncing the current content of the prosemirror node.
1115
1173
  *
1116
- * This is a y-tiptap internal feature that you can use at your own risk.
1174
+ * This is a y-prosemirror internal feature that you can use at your own risk.
1117
1175
  *
1118
1176
  * @private
1119
1177
  * @unstable
@@ -1121,27 +1179,24 @@ const nodeMarksToAttributes = (marks) => {
1121
1179
  * @param {{transact: Function}} y
1122
1180
  * @param {Y.XmlFragment} yDomFragment
1123
1181
  * @param {any} pNode
1124
- * @param {ProsemirrorMapping} mapping
1182
+ * @param {BindingMetadata} meta
1125
1183
  */
1126
- const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1184
+ const updateYFragment = (y, yDomFragment, pNode, meta) => {
1127
1185
  if (
1128
1186
  yDomFragment instanceof Y.XmlElement &&
1129
1187
  yDomFragment.nodeName !== pNode.type.name
1130
1188
  ) {
1131
1189
  throw new Error('node name mismatch!')
1132
1190
  }
1133
- mapping.set(yDomFragment, pNode);
1191
+ meta.mapping.set(yDomFragment, pNode);
1134
1192
  // update attributes
1135
1193
  if (yDomFragment instanceof Y.XmlElement) {
1136
1194
  const yDomAttrs = yDomFragment.getAttributes();
1137
1195
  const pAttrs = pNode.attrs;
1138
- const pNodeMarksAttr = nodeMarksToAttributes(pNode.marks);
1139
- const attrs = { ...pAttrs, ...pNodeMarksAttr };
1140
-
1141
- for (const key in attrs) {
1142
- if (attrs[key] !== null) {
1143
- if (yDomAttrs[key] !== attrs[key] && key !== 'ychange') {
1144
- yDomFragment.setAttribute(key, attrs[key]);
1196
+ for (const key in pAttrs) {
1197
+ if (pAttrs[key] !== null) {
1198
+ if (yDomAttrs[key] !== pAttrs[key] && key !== 'ychange') {
1199
+ yDomFragment.setAttribute(key, pAttrs[key]);
1145
1200
  }
1146
1201
  } else {
1147
1202
  yDomFragment.removeAttribute(key);
@@ -1149,7 +1204,7 @@ const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1149
1204
  }
1150
1205
  // remove all keys that are no longer in pAttrs
1151
1206
  for (const key in yDomAttrs) {
1152
- if (attrs[key] === undefined) {
1207
+ if (pAttrs[key] === undefined) {
1153
1208
  yDomFragment.removeAttribute(key);
1154
1209
  }
1155
1210
  }
@@ -1166,10 +1221,10 @@ const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1166
1221
  for (; left < minCnt; left++) {
1167
1222
  const leftY = yChildren[left];
1168
1223
  const leftP = pChildren[left];
1169
- if (!mappedIdentity(mapping.get(leftY), leftP)) {
1224
+ if (!mappedIdentity(meta.mapping.get(leftY), leftP)) {
1170
1225
  if (equalYTypePNode(leftY, leftP)) {
1171
1226
  // update mapping
1172
- mapping.set(leftY, leftP);
1227
+ meta.mapping.set(leftY, leftP);
1173
1228
  } else {
1174
1229
  break
1175
1230
  }
@@ -1179,10 +1234,10 @@ const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1179
1234
  for (; right + left + 1 < minCnt; right++) {
1180
1235
  const rightY = yChildren[yChildCnt - right - 1];
1181
1236
  const rightP = pChildren[pChildCnt - right - 1];
1182
- if (!mappedIdentity(mapping.get(rightY), rightP)) {
1237
+ if (!mappedIdentity(meta.mapping.get(rightY), rightP)) {
1183
1238
  if (equalYTypePNode(rightY, rightP)) {
1184
1239
  // update mapping
1185
- mapping.set(rightY, rightP);
1240
+ meta.mapping.set(rightY, rightP);
1186
1241
  } else {
1187
1242
  break
1188
1243
  }
@@ -1197,7 +1252,7 @@ const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1197
1252
  const rightP = pChildren[pChildCnt - right - 1];
1198
1253
  if (leftY instanceof Y.XmlText && leftP instanceof Array) {
1199
1254
  if (!equalYTextPText(leftY, leftP)) {
1200
- updateYText(leftY, leftP, mapping);
1255
+ updateYText(leftY, leftP, meta);
1201
1256
  }
1202
1257
  left += 1;
1203
1258
  } else {
@@ -1210,12 +1265,12 @@ const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1210
1265
  const equalityLeft = computeChildEqualityFactor(
1211
1266
  /** @type {Y.XmlElement} */ (leftY),
1212
1267
  /** @type {PModel.Node} */ (leftP),
1213
- mapping
1268
+ meta
1214
1269
  );
1215
1270
  const equalityRight = computeChildEqualityFactor(
1216
1271
  /** @type {Y.XmlElement} */ (rightY),
1217
1272
  /** @type {PModel.Node} */ (rightP),
1218
- mapping
1273
+ meta
1219
1274
  );
1220
1275
  if (
1221
1276
  equalityLeft.foundMappedChild && !equalityRight.foundMappedChild
@@ -1238,7 +1293,7 @@ const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1238
1293
  y,
1239
1294
  /** @type {Y.XmlFragment} */ (leftY),
1240
1295
  /** @type {PModel.Node} */ (leftP),
1241
- mapping
1296
+ meta
1242
1297
  );
1243
1298
  left += 1;
1244
1299
  } else if (updateRight) {
@@ -1246,14 +1301,14 @@ const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1246
1301
  y,
1247
1302
  /** @type {Y.XmlFragment} */ (rightY),
1248
1303
  /** @type {PModel.Node} */ (rightP),
1249
- mapping
1304
+ meta
1250
1305
  );
1251
1306
  right += 1;
1252
1307
  } else {
1253
- mapping.delete(yDomFragment.get(left));
1308
+ meta.mapping.delete(yDomFragment.get(left));
1254
1309
  yDomFragment.delete(left, 1);
1255
1310
  yDomFragment.insert(left, [
1256
- createTypeFromTextOrElementNode(leftP, mapping)
1311
+ createTypeFromTextOrElementNode(leftP, meta)
1257
1312
  ]);
1258
1313
  left += 1;
1259
1314
  }
@@ -1263,18 +1318,18 @@ const updateYFragment = (y, yDomFragment, pNode, mapping) => {
1263
1318
  if (
1264
1319
  yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof Y.XmlText
1265
1320
  ) {
1266
- mapping.delete(yChildren[0]);
1267
- // Edge case handling https://github.com/yjs/y-tiptap/issues/108
1321
+ meta.mapping.delete(yChildren[0]);
1322
+ // Edge case handling https://github.com/yjs/y-prosemirror/issues/108
1268
1323
  // Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
1269
1324
  yChildren[0].delete(0, yChildren[0].length);
1270
1325
  } else if (yDelLen > 0) {
1271
- yDomFragment.slice(left, left + yDelLen).forEach(type => mapping.delete(type));
1326
+ yDomFragment.slice(left, left + yDelLen).forEach(type => meta.mapping.delete(type));
1272
1327
  yDomFragment.delete(left, yDelLen);
1273
1328
  }
1274
1329
  if (left + right < pChildCnt) {
1275
1330
  const ins = [];
1276
1331
  for (let i = left; i < pChildCnt - right; i++) {
1277
- ins.push(createTypeFromTextOrElementNode(pChildren[i], mapping));
1332
+ ins.push(createTypeFromTextOrElementNode(pChildren[i], meta));
1278
1333
  }
1279
1334
  yDomFragment.insert(left, ins);
1280
1335
  }
@@ -1484,7 +1539,7 @@ const yXmlFragmentToProseMirrorFragment = (yXmlFragment, schema) => {
1484
1539
  createNodeFromYElement(
1485
1540
  /** @type {Y.XmlElement} */ (t),
1486
1541
  schema,
1487
- new Map()
1542
+ createEmptyMeta()
1488
1543
  )
1489
1544
  ).filter((n) => n !== null);
1490
1545
  return Fragment.fromArray(fragmentContent)
@@ -1505,21 +1560,20 @@ const yXmlFragmentToProseMirrorRootNode = (yXmlFragment, schema) =>
1505
1560
  *
1506
1561
  * @param {Y.XmlFragment} yXmlFragment
1507
1562
  * @param {Schema} schema
1563
+ *
1564
+ * @todo deprecate mapping property
1508
1565
  */
1509
1566
  const initProseMirrorDoc = (yXmlFragment, schema) => {
1510
- /**
1511
- * @type {ProsemirrorMapping}
1512
- */
1513
- const mapping = new Map();
1567
+ const meta = createEmptyMeta();
1514
1568
  const fragmentContent = yXmlFragment.toArray().map((t) =>
1515
1569
  createNodeFromYElement(
1516
1570
  /** @type {Y.XmlElement} */ (t),
1517
1571
  schema,
1518
- mapping
1572
+ meta
1519
1573
  )
1520
1574
  ).filter((n) => n !== null);
1521
1575
  const doc = schema.topNodeType.create(null, Fragment.fromArray(fragmentContent));
1522
- return { doc, mapping }
1576
+ return { doc, meta, mapping: meta.mapping }
1523
1577
  };
1524
1578
 
1525
1579
  /**
@@ -1562,7 +1616,7 @@ function prosemirrorToYDoc (doc, xmlFragment = 'prosemirror') {
1562
1616
  function prosemirrorToYXmlFragment (doc, xmlFragment) {
1563
1617
  const type = xmlFragment || new Y.XmlFragment();
1564
1618
  const ydoc = type.doc ? type.doc : { transact: (transaction) => transaction(undefined) };
1565
- updateYFragment(ydoc, type, doc, new Map());
1619
+ updateYFragment(ydoc, type, doc, { mapping: new Map(), isOMark: new Map() });
1566
1620
  return type
1567
1621
  }
1568
1622
 
@@ -1658,7 +1712,10 @@ function yDocToProsemirrorJSON (
1658
1712
  function yXmlFragmentToProsemirrorJSON (xmlFragment) {
1659
1713
  const items = xmlFragment.toArray();
1660
1714
 
1661
- function serialize (item) {
1715
+ /**
1716
+ * @param {Y.AbstractType} item
1717
+ */
1718
+ const serialize = item => {
1662
1719
  /**
1663
1720
  * @type {Object} NodeObject
1664
1721
  * @property {string} NodeObject.type
@@ -1668,79 +1725,49 @@ function yXmlFragmentToProsemirrorJSON (xmlFragment) {
1668
1725
  let response;
1669
1726
 
1670
1727
  // TODO: Must be a better way to detect text nodes than this
1671
- if (!item.nodeName) {
1728
+ if (item instanceof Y.XmlText) {
1672
1729
  const delta = item.toDelta();
1673
- response = delta.map((d) => {
1730
+ response = delta.map(/** @param {any} d */ (d) => {
1674
1731
  const text = {
1675
1732
  type: 'text',
1676
1733
  text: d.insert
1677
1734
  };
1678
-
1679
1735
  if (d.attributes) {
1680
- const marks = [];
1681
-
1682
- Object.keys(d.attributes).forEach((type) => {
1683
- const attrs = d.attributes[type];
1684
- if (Array.isArray(attrs)) {
1685
- // multiple marks of same type
1686
- attrs.forEach(singleAttrs => {
1687
- const mark = {
1688
- type
1689
- };
1690
-
1691
- if (Object.keys(singleAttrs)) {
1692
- mark.attrs = singleAttrs;
1693
- }
1694
-
1695
- marks.push(mark);
1696
- });
1697
- } else {
1698
- const mark = {
1699
- type
1700
- };
1701
-
1702
- if (Object.keys(attrs)) {
1703
- mark.attrs = attrs;
1704
- }
1705
-
1706
- marks.push(mark);
1736
+ text.marks = Object.keys(d.attributes).map((type_) => {
1737
+ const attrs = d.attributes[type_];
1738
+ const type = yattr2markname(type_);
1739
+ const mark = {
1740
+ type
1741
+ };
1742
+ if (Object.keys(attrs)) {
1743
+ mark.attrs = attrs;
1707
1744
  }
1745
+ return mark
1708
1746
  });
1709
-
1710
- text.marks = marks;
1711
1747
  }
1712
1748
  return text
1713
1749
  });
1714
- } else {
1750
+ } else if (item instanceof Y.XmlElement) {
1715
1751
  response = {
1716
1752
  type: item.nodeName
1717
1753
  };
1718
1754
 
1719
1755
  const attrs = item.getAttributes();
1720
-
1721
- // Add all non-mark attributes to the element
1722
- for (const key of Object.keys(attrs).filter((key) => !key.startsWith(MarkPrefix))) {
1723
- if (!response.attrs) response.attrs = {};
1724
- response.attrs[key] = attrs[key];
1725
- }
1726
-
1727
- // Check whether the attrs contains marks, if so, add them to the response
1728
- if (Object.keys(attrs).some((key) => key.startsWith(MarkPrefix))) {
1729
- response.marks = Object.keys(attrs)
1730
- .filter((key) => key.startsWith(MarkPrefix))
1731
- .map((key) => {
1732
- return attrs[key]
1733
- });
1756
+ if (Object.keys(attrs).length) {
1757
+ response.attrs = attrs;
1734
1758
  }
1735
1759
 
1736
1760
  const children = item.toArray();
1737
1761
  if (children.length) {
1738
1762
  response.content = children.map(serialize).flat();
1739
1763
  }
1764
+ } else {
1765
+ // expected either Y.XmlElement or Y.XmlText
1766
+ error.unexpectedCase();
1740
1767
  }
1741
1768
 
1742
1769
  return response
1743
- }
1770
+ };
1744
1771
 
1745
1772
  return {
1746
1773
  type: 'doc',
@@ -1783,7 +1810,7 @@ const defaultCursorBuilder = (user) => {
1783
1810
  * Default generator for the selection attributes
1784
1811
  *
1785
1812
  * @param {any} user user data
1786
- * @return {import('@tiptap/pm/view').DecorationAttrs}
1813
+ * @return {import('prosemirror-view').DecorationAttrs}
1787
1814
  */
1788
1815
  const defaultSelectionBuilder = (user) => {
1789
1816
  return {
@@ -1798,8 +1825,8 @@ const rxValidColor = /^#[0-9a-fA-F]{6}$/;
1798
1825
  * @param {any} state
1799
1826
  * @param {Awareness} awareness
1800
1827
  * @param {function(number, number, any):boolean} awarenessFilter
1801
- * @param {function({ name: string, color: string }):Element} createCursor
1802
- * @param {function({ name: string, color: string }):import('@tiptap/pm/view').DecorationAttrs} createSelection
1828
+ * @param {(user: { name: string, color: string }, clientId: number) => Element} createCursor
1829
+ * @param {(user: { name: string, color: string }, clientId: number) => import('prosemirror-view').DecorationAttrs} createSelection
1803
1830
  * @return {any} DecorationSet
1804
1831
  */
1805
1832
  const createDecorations = (
@@ -1829,7 +1856,7 @@ const createDecorations = (
1829
1856
  if (user.color == null) {
1830
1857
  user.color = '#ffa500';
1831
1858
  } else if (!rxValidColor.test(user.color)) {
1832
- // We only support 6-digit RGB colors in y-tiptap
1859
+ // We only support 6-digit RGB colors in y-prosemirror
1833
1860
  console.warn('A user uses an unsupported color format', user);
1834
1861
  }
1835
1862
  if (user.name == null) {
@@ -1852,7 +1879,7 @@ const createDecorations = (
1852
1879
  anchor = math.min(anchor, maxsize);
1853
1880
  head = math.min(head, maxsize);
1854
1881
  decorations.push(
1855
- Decoration.widget(head, () => createCursor(user), {
1882
+ Decoration.widget(head, () => createCursor(user, clientId), {
1856
1883
  key: clientId + '',
1857
1884
  side: 10
1858
1885
  })
@@ -1860,7 +1887,7 @@ const createDecorations = (
1860
1887
  const from = math.min(anchor, head);
1861
1888
  const to = math.max(anchor, head);
1862
1889
  decorations.push(
1863
- Decoration.inline(from, to, createSelection(user), {
1890
+ Decoration.inline(from, to, createSelection(user, clientId), {
1864
1891
  inclusiveEnd: true,
1865
1892
  inclusiveStart: false
1866
1893
  })
@@ -1879,8 +1906,8 @@ const createDecorations = (
1879
1906
  * @param {Awareness} awareness
1880
1907
  * @param {object} opts
1881
1908
  * @param {function(any, any, any):boolean} [opts.awarenessStateFilter]
1882
- * @param {function(any):HTMLElement} [opts.cursorBuilder]
1883
- * @param {function(any):import('@tiptap/pm/view').DecorationAttrs} [opts.selectionBuilder]
1909
+ * @param {(user: any, clientId: number) => HTMLElement} [opts.cursorBuilder]
1910
+ * @param {(user: any, clientId: number) => import('prosemirror-view').DecorationAttrs} [opts.selectionBuilder]
1884
1911
  * @param {function(any):any} [opts.getSelection]
1885
1912
  * @param {string} [cursorStateField] By default all editor bindings use the awareness 'cursor' field to propagate cursor information.
1886
1913
  * @return {any}
@@ -2027,7 +2054,7 @@ const defaultDeleteFilter = (item, protectedNodes) => !(item instanceof Item) ||
2027
2054
  (item.content.type instanceof XmlElement && protectedNodes.has(item.content.type.nodeName))) ||
2028
2055
  item.content.type._length === 0;
2029
2056
 
2030
- const yUndoPlugin = ({ protectedNodes = defaultProtectedNodes, trackedOrigins = [], undoManager = null } = {}) => new Plugin({
2057
+ const yUndoPlugin = ({ protectedNodes = defaultProtectedNodes, trackedOrigins = [], undoManager = null } = {}) => new Plugin$1({
2031
2058
  key: yUndoPluginKey,
2032
2059
  state: {
2033
2060
  init: (initargs, state) => {