@kerebron/extension-yjs 0.6.7 → 0.7.1

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.
Files changed (103) hide show
  1. package/esm/ExtensionYjs.d.ts +3 -11
  2. package/esm/ExtensionYjs.d.ts.map +1 -1
  3. package/esm/ExtensionYjs.js +71 -45
  4. package/esm/ExtensionYjs.js.map +1 -1
  5. package/esm/WebsocketProvider.d.ts +70 -0
  6. package/esm/WebsocketProvider.d.ts.map +1 -0
  7. package/esm/WebsocketProvider.js +377 -0
  8. package/esm/WebsocketProvider.js.map +1 -0
  9. package/esm/YjsProvider.d.ts +48 -0
  10. package/esm/YjsProvider.d.ts.map +1 -0
  11. package/esm/YjsProvider.js +12 -0
  12. package/esm/YjsProvider.js.map +1 -0
  13. package/esm/_dnt.shims.d.ts +2 -0
  14. package/esm/_dnt.shims.d.ts.map +1 -0
  15. package/esm/_dnt.shims.js +58 -0
  16. package/esm/_dnt.shims.js.map +1 -0
  17. package/esm/binding/BindingMetadata.d.ts +6 -0
  18. package/esm/binding/BindingMetadata.d.ts.map +1 -0
  19. package/esm/binding/BindingMetadata.js +2 -0
  20. package/esm/binding/BindingMetadata.js.map +1 -0
  21. package/esm/binding/DiffViewer.d.ts +17 -0
  22. package/esm/binding/DiffViewer.d.ts.map +1 -0
  23. package/esm/binding/DiffViewer.js +96 -0
  24. package/esm/binding/DiffViewer.js.map +1 -0
  25. package/esm/binding/PmYjsBinding.d.ts +45 -0
  26. package/esm/binding/PmYjsBinding.d.ts.map +1 -0
  27. package/esm/binding/PmYjsBinding.js +230 -0
  28. package/esm/binding/PmYjsBinding.js.map +1 -0
  29. package/esm/binding/convertUtils.d.ts +48 -0
  30. package/esm/binding/convertUtils.d.ts.map +1 -0
  31. package/esm/binding/convertUtils.js +80 -0
  32. package/esm/binding/convertUtils.js.map +1 -0
  33. package/esm/{createNodeFromYElement.d.ts → binding/createNodeFromYElement.d.ts} +2 -2
  34. package/esm/binding/createNodeFromYElement.d.ts.map +1 -0
  35. package/esm/{createNodeFromYElement.js → binding/createNodeFromYElement.js} +2 -2
  36. package/esm/binding/createNodeFromYElement.js.map +1 -0
  37. package/esm/{updateYFragment.d.ts → binding/updateYFragment.d.ts} +3 -3
  38. package/esm/binding/updateYFragment.d.ts.map +1 -0
  39. package/esm/{updateYFragment.js → binding/updateYFragment.js} +10 -7
  40. package/esm/binding/updateYFragment.js.map +1 -0
  41. package/esm/debug.d.ts.map +1 -1
  42. package/esm/debug.js +11 -0
  43. package/esm/debug.js.map +1 -1
  44. package/esm/lib.d.ts +1 -7
  45. package/esm/lib.d.ts.map +1 -1
  46. package/esm/lib.js +1 -200
  47. package/esm/lib.js.map +1 -1
  48. package/esm/position.d.ts +8 -0
  49. package/esm/position.d.ts.map +1 -0
  50. package/esm/position.js +165 -0
  51. package/esm/position.js.map +1 -0
  52. package/esm/ui/selection.d.ts +29 -0
  53. package/esm/ui/selection.d.ts.map +1 -0
  54. package/esm/ui/selection.js +129 -0
  55. package/esm/ui/selection.js.map +1 -0
  56. package/esm/utils.d.ts +1 -1
  57. package/esm/utils.d.ts.map +1 -1
  58. package/esm/utils.js.map +1 -1
  59. package/esm/yPositionPlugin.d.ts +6 -1
  60. package/esm/yPositionPlugin.d.ts.map +1 -1
  61. package/esm/yPositionPlugin.js +91 -50
  62. package/esm/yPositionPlugin.js.map +1 -1
  63. package/esm/ySyncPlugin.d.ts +5 -22
  64. package/esm/ySyncPlugin.d.ts.map +1 -1
  65. package/esm/ySyncPlugin.js +70 -101
  66. package/esm/ySyncPlugin.js.map +1 -1
  67. package/esm/yUndoPlugin.d.ts +11 -10
  68. package/esm/yUndoPlugin.d.ts.map +1 -1
  69. package/esm/yUndoPlugin.js +90 -52
  70. package/esm/yUndoPlugin.js.map +1 -1
  71. package/package.json +9 -6
  72. package/src/ExtensionYjs.ts +98 -67
  73. package/src/WebsocketProvider.ts +528 -0
  74. package/src/YjsProvider.ts +75 -0
  75. package/src/_dnt.shims.ts +60 -0
  76. package/src/binding/BindingMetadata.ts +6 -0
  77. package/src/binding/DiffViewer.ts +138 -0
  78. package/src/binding/PmYjsBinding.ts +360 -0
  79. package/src/binding/convertUtils.ts +124 -0
  80. package/src/{createNodeFromYElement.ts → binding/createNodeFromYElement.ts} +4 -4
  81. package/src/{updateYFragment.ts → binding/updateYFragment.ts} +15 -8
  82. package/src/debug.ts +21 -0
  83. package/src/lib.ts +4 -230
  84. package/src/position.ts +191 -0
  85. package/src/ui/selection.ts +218 -0
  86. package/src/utils.ts +1 -1
  87. package/src/yPositionPlugin.ts +122 -74
  88. package/src/ySyncPlugin.ts +111 -155
  89. package/src/yUndoPlugin.ts +113 -62
  90. package/esm/ProsemirrorBinding.d.ts +0 -60
  91. package/esm/ProsemirrorBinding.d.ts.map +0 -1
  92. package/esm/ProsemirrorBinding.js +0 -405
  93. package/esm/ProsemirrorBinding.js.map +0 -1
  94. package/esm/createNodeFromYElement.d.ts.map +0 -1
  95. package/esm/createNodeFromYElement.js.map +0 -1
  96. package/esm/updateYFragment.d.ts.map +0 -1
  97. package/esm/updateYFragment.js.map +0 -1
  98. package/esm/userColors.d.ts +0 -5
  99. package/esm/userColors.d.ts.map +0 -1
  100. package/esm/userColors.js +0 -11
  101. package/esm/userColors.js.map +0 -1
  102. package/src/ProsemirrorBinding.ts +0 -607
  103. package/src/userColors.ts +0 -10
package/src/debug.ts CHANGED
@@ -177,6 +177,27 @@ export function debugYNode(
177
177
  return retVal;
178
178
  }
179
179
 
180
+ if (node instanceof Y.AbstractType) {
181
+ if (!config.isVisible(node._item)) {
182
+ return '';
183
+ }
184
+
185
+ const atype: Y.AbstractType<any> = node;
186
+
187
+ retVal += config.renderDeleted(
188
+ `Y.AbstractType(${atype._length})`,
189
+ node._item,
190
+ );
191
+ retVal += yDebugClient(node._item);
192
+ retVal += '\n';
193
+
194
+ retVal += indentText(
195
+ JSON.stringify(atype._item),
196
+ 1,
197
+ );
198
+ return retVal;
199
+ }
200
+
180
201
  if ('object' !== typeof node) {
181
202
  retVal += '' + node;
182
203
  return retVal;
package/src/lib.ts CHANGED
@@ -1,238 +1,12 @@
1
1
  import * as Y from 'yjs';
2
- import { type EditorView } from 'prosemirror-view';
3
2
  import { type Node } from 'prosemirror-model';
4
3
 
5
- import { ySyncPluginKey } from './keys.js';
4
+ export type TransactFunc<T> = (
5
+ f: (arg0?: Y.Transaction) => T,
6
+ origin?: any,
7
+ ) => T;
6
8
 
7
9
  /**
8
10
  * Either a node if type is YXmlElement or an Array of text nodes if YXmlText
9
11
  */
10
12
  export type ProsemirrorMapping = Map<Y.AbstractType<any>, Node | Array<Node>>;
11
-
12
- /**
13
- * Is null if no timeout is in progress.
14
- * Is defined if a timeout is in progress.
15
- * Maps from view
16
- */
17
- let viewsToUpdate: Map<EditorView, Map<any, any>> | null = null;
18
-
19
- const updateMetas = () => {
20
- const ups: Map<EditorView, Map<any, any>> | null = viewsToUpdate;
21
- viewsToUpdate = null;
22
- if (!ups) {
23
- return;
24
- }
25
- ups.forEach((metas, view) => {
26
- const tr = view.state.tr;
27
- const syncState = ySyncPluginKey.getState(view.state);
28
- if (syncState && syncState.binding && !syncState.binding.isDestroyed) {
29
- metas.forEach((val, key) => {
30
- tr.setMeta(key, val);
31
- });
32
- view.dispatch(tr);
33
- }
34
- });
35
- };
36
-
37
- export const setMeta = (view: EditorView, key, value) => {
38
- if (!viewsToUpdate) {
39
- viewsToUpdate = new Map();
40
- setTimeout(updateMetas, 0);
41
- }
42
-
43
- let subMap = viewsToUpdate.get(view);
44
- if (subMap === undefined) {
45
- subMap = new Map();
46
- viewsToUpdate.set(view, subMap);
47
- }
48
- subMap.set(key, value);
49
- };
50
-
51
- /**
52
- * Transforms a Prosemirror based absolute position to a Yjs Cursor (relative position in the Yjs model).
53
- */
54
- export const absolutePositionToRelativePosition = (
55
- pos: number,
56
- type: Y.XmlFragment,
57
- mapping: ProsemirrorMapping,
58
- ): any => {
59
- if (pos === 0) {
60
- // if the type is later populated, we want to retain the 0 position (hence assoc=-1)
61
- return Y.createRelativePositionFromTypeIndex(
62
- type,
63
- 0,
64
- type.length === 0 ? -1 : 0,
65
- );
66
- }
67
-
68
- let n: Y.AbstractType<any> | null = type._first === null
69
- ? null
70
- : /** @type {Y.ContentType} */ (type._first.content).type;
71
- while (n !== null && type !== n) {
72
- if (n instanceof Y.XmlText) {
73
- if (n._length >= pos) {
74
- return Y.createRelativePositionFromTypeIndex(
75
- n,
76
- pos,
77
- type.length === 0 ? -1 : 0,
78
- );
79
- } else {
80
- pos -= n._length;
81
- }
82
- if (n._item !== null && n._item.next !== null) {
83
- n = /** @type {Y.ContentType} */ (n._item.next.content).type;
84
- } else {
85
- do {
86
- n = n._item === null ? null : n._item.parent;
87
- pos--;
88
- } while (
89
- n !== type && n !== null && n._item !== null && n._item.next === null
90
- );
91
- if (n !== null && n !== type) {
92
- // @ts-gnore we know that n.next !== null because of above loop conditition
93
- n = n._item === null
94
- ? null
95
- : /** @type {Y.ContentType} */ (/** @type Y.Item */ (n._item.next)
96
- .content).type;
97
- }
98
- }
99
- } else {
100
- const pNodeSize =
101
- /** @type {any} */ (mapping.get(n) || { nodeSize: 0 }).nodeSize;
102
- if (n._first !== null && pos < pNodeSize) {
103
- n = /** @type {Y.ContentType} */ (n._first.content).type;
104
- pos--;
105
- } else {
106
- if (pos === 1 && n._length === 0 && pNodeSize > 1) {
107
- // edge case, should end in this paragraph
108
- return new Y.RelativePosition(
109
- n._item === null ? null : n._item.id,
110
- n._item === null ? Y.findRootTypeKey(n) : null,
111
- null,
112
- );
113
- }
114
- pos -= pNodeSize;
115
- if (n._item !== null && n._item.next !== null) {
116
- n = /** @type {Y.ContentType} */ (n._item.next.content).type;
117
- } else {
118
- if (pos === 0) {
119
- // set to end of n.parent
120
- n = n._item === null ? n : n._item.parent;
121
- return new Y.RelativePosition(
122
- n._item === null ? null : n._item.id,
123
- n._item === null ? Y.findRootTypeKey(n) : null,
124
- null,
125
- );
126
- }
127
- do {
128
- n = n._item.parent;
129
- pos--;
130
- } while (n !== type && /** @type {Y.Item} */ (n._item).next === null);
131
- // if n is null at this point, we have an unexpected case
132
- if (n !== type) {
133
- // We know that n._item.next is defined because of above loop condition
134
- n =
135
- /** @type {Y.ContentType} */ (/** @type {Y.Item} */ (/** @type {Y.Item} */ (n
136
- ._item).next).content).type;
137
- }
138
- }
139
- }
140
- }
141
- if (n === null) {
142
- throw new Error('Unexpected case');
143
- }
144
- if (pos === 0 && n.constructor !== Y.XmlText && n !== type) { // TODO: set to <= 0
145
- return createRelativePosition(n._item.parent, n._item);
146
- }
147
- }
148
- return Y.createRelativePositionFromTypeIndex(
149
- type,
150
- type._length,
151
- type.length === 0 ? -1 : 0,
152
- );
153
- };
154
-
155
- const createRelativePosition = (type: Y.AbstractType<any>, item: Y.Item) => {
156
- let typeid = null;
157
- let tname = null;
158
- if (type._item === null) {
159
- tname = Y.findRootTypeKey(type);
160
- } else {
161
- typeid = Y.createID(type._item.id.client, type._item.id.clock);
162
- }
163
- return new Y.RelativePosition(typeid, tname, item.id);
164
- };
165
-
166
- export const relativePositionToAbsolutePosition = (
167
- yDoc: Y.Doc,
168
- documentType: Y.XmlFragment,
169
- relPos: any,
170
- mapping: ProsemirrorMapping,
171
- ): null | number => {
172
- const decodedPos = Y.createAbsolutePositionFromRelativePosition(relPos, yDoc);
173
- if (
174
- decodedPos === null ||
175
- (decodedPos.type !== documentType &&
176
- !Y.isParentOf(documentType, decodedPos.type._item))
177
- ) {
178
- return null;
179
- }
180
- let type = decodedPos.type;
181
- let pos = 0;
182
- if (type instanceof Y.XmlText) {
183
- pos = decodedPos.index;
184
- } else if (type._item === null || !type._item.deleted) {
185
- let n: Y.Item | null = type._first;
186
- let i = 0;
187
- while (i < type._length && i < decodedPos.index && n !== null) {
188
- if (!n.deleted) {
189
- const t: Y.AbstractType<any> = n.content.type;
190
- i++;
191
- if (t instanceof Y.XmlText) {
192
- pos += t._length;
193
- } else {
194
- const node = mapping.get(t);
195
- if (Array.isArray(node)) {
196
- pos += node.reduce((prev, curr) => prev + curr?.nodeSize || 0, 0);
197
- } else {
198
- pos += node?.nodeSize || 0;
199
- }
200
- }
201
- }
202
- n = n.right;
203
- }
204
- pos += 1; // increase because we go out of n
205
- }
206
- while (type !== documentType && type._item !== null) {
207
- const parent = type._item.parent;
208
- if (parent instanceof Y.ID || parent === null) {
209
- continue;
210
- }
211
- if (parent._item === null || !parent._item.deleted) {
212
- pos += 1; // the start tag
213
- let n = /** @type {Y.AbstractType} */ (parent)._first;
214
- // now iterate until we found type
215
- while (n !== null) {
216
- const contentType: Y.AbstractType<any> = n.content.type;
217
- if (contentType === type) {
218
- break;
219
- }
220
- if (!n.deleted) {
221
- if (contentType instanceof Y.XmlText) {
222
- pos += contentType._length;
223
- } else {
224
- const node = mapping.get(contentType);
225
- if (Array.isArray(node)) {
226
- pos += node.reduce((prev, curr) => prev + curr?.nodeSize || 0, 0);
227
- } else {
228
- pos += node?.nodeSize || 0;
229
- }
230
- }
231
- }
232
- n = n.right;
233
- }
234
- }
235
- type = parent;
236
- }
237
- return pos - 1; // we don't count the most outer tag, because it is a fragment
238
- };
@@ -0,0 +1,191 @@
1
+ import * as Y from 'yjs';
2
+ import { ProsemirrorMapping } from './lib.js';
3
+
4
+ /**
5
+ * Transforms a Prosemirror based absolute position to a Yjs Cursor (relative position in the Yjs model).
6
+ */
7
+ export const absolutePositionToRelativePosition = (
8
+ pos: number,
9
+ type: Y.XmlFragment,
10
+ mapping: ProsemirrorMapping,
11
+ ): any => {
12
+ if (pos === 0) {
13
+ // if the type is later populated, we want to retain the 0 position (hence assoc=-1)
14
+ return Y.createRelativePositionFromTypeIndex(
15
+ type,
16
+ 0,
17
+ type.length === 0 ? -1 : 0,
18
+ );
19
+ }
20
+
21
+ let n: Y.AbstractType<any> | null = type._first === null
22
+ ? null
23
+ : /** @type {Y.ContentType} */ (type._first.content).type;
24
+ while (n !== null && type !== n) {
25
+ if (n instanceof Y.XmlText) {
26
+ if (n._length >= pos) {
27
+ return Y.createRelativePositionFromTypeIndex(
28
+ n,
29
+ pos,
30
+ type.length === 0 ? -1 : 0,
31
+ );
32
+ } else {
33
+ pos -= n._length;
34
+ }
35
+ if (n._item !== null && n._item.next !== null) {
36
+ n = /** @type {Y.ContentType} */ (n._item.next.content).type;
37
+ } else {
38
+ do {
39
+ n = n._item === null ? null : n._item.parent;
40
+ pos--;
41
+ } while (
42
+ n !== type && n !== null && n._item !== null && n._item.next === null
43
+ );
44
+ if (n !== null && n !== type) {
45
+ // @ts-gnore we know that n.next !== null because of above loop conditition
46
+ n = n._item === null
47
+ ? null
48
+ : /** @type {Y.ContentType} */ (/** @type Y.Item */ (n._item.next)
49
+ .content).type;
50
+ }
51
+ }
52
+ } else {
53
+ const pNodeSize =
54
+ /** @type {any} */ (mapping.get(n) || { nodeSize: 0 }).nodeSize;
55
+ if (n._first !== null && pos < pNodeSize) {
56
+ n = /** @type {Y.ContentType} */ (n._first.content).type;
57
+ pos--;
58
+ } else {
59
+ if (pos === 1 && n._length === 0 && pNodeSize > 1) {
60
+ // edge case, should end in this paragraph
61
+ return new Y.RelativePosition(
62
+ n._item === null ? null : n._item.id,
63
+ n._item === null ? Y.findRootTypeKey(n) : null,
64
+ null,
65
+ );
66
+ }
67
+ pos -= pNodeSize;
68
+ if (n._item !== null && n._item.next !== null) {
69
+ n = /** @type {Y.ContentType} */ (n._item.next.content).type;
70
+ } else {
71
+ if (pos === 0) {
72
+ // set to end of n.parent
73
+ n = n._item === null ? n : n._item.parent;
74
+ return new Y.RelativePosition(
75
+ n._item === null ? null : n._item.id,
76
+ n._item === null ? Y.findRootTypeKey(n) : null,
77
+ null,
78
+ );
79
+ }
80
+ do {
81
+ n = n._item.parent;
82
+ pos--;
83
+ } while (n !== type && /** @type {Y.Item} */ (n._item).next === null);
84
+ // if n is null at this point, we have an unexpected case
85
+ if (n !== type) {
86
+ // We know that n._item.next is defined because of above loop condition
87
+ n =
88
+ /** @type {Y.ContentType} */ (/** @type {Y.Item} */ (/** @type {Y.Item} */ (n
89
+ ._item).next).content).type;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ if (n === null) {
95
+ throw new Error('Unexpected case');
96
+ }
97
+ if (pos === 0 && n.constructor !== Y.XmlText && n !== type) { // TODO: set to <= 0
98
+ return createRelativePosition(n._item.parent, n._item);
99
+ }
100
+ }
101
+ return Y.createRelativePositionFromTypeIndex(
102
+ type,
103
+ type._length,
104
+ type.length === 0 ? -1 : 0,
105
+ );
106
+ };
107
+
108
+ const createRelativePosition = (type: Y.AbstractType<any>, item: Y.Item) => {
109
+ let typeid = null;
110
+ let tname = null;
111
+ if (type._item === null) {
112
+ tname = Y.findRootTypeKey(type);
113
+ } else {
114
+ typeid = Y.createID(type._item.id.client, type._item.id.clock);
115
+ }
116
+ return new Y.RelativePosition(typeid, tname, item.id);
117
+ };
118
+
119
+ export const relativePositionToAbsolutePosition = (
120
+ yDoc: Y.Doc,
121
+ documentType: Y.XmlFragment,
122
+ relPos: any,
123
+ mapping: ProsemirrorMapping,
124
+ ): null | number => {
125
+ const decodedPos = Y.createAbsolutePositionFromRelativePosition(relPos, yDoc);
126
+ if (
127
+ decodedPos === null ||
128
+ (decodedPos.type !== documentType &&
129
+ !Y.isParentOf(documentType, decodedPos.type._item))
130
+ ) {
131
+ return null;
132
+ }
133
+ let type = decodedPos.type;
134
+ let pos = 0;
135
+ if (type instanceof Y.XmlText) {
136
+ pos = decodedPos.index;
137
+ } else if (type._item === null || !type._item.deleted) {
138
+ let n: Y.Item | null = type._first;
139
+ let i = 0;
140
+ while (i < type._length && i < decodedPos.index && n !== null) {
141
+ if (!n.deleted) {
142
+ const t: Y.AbstractType<any> = n.content.type;
143
+ i++;
144
+ if (t instanceof Y.XmlText) {
145
+ pos += t._length;
146
+ } else {
147
+ const node = mapping.get(t);
148
+ if (Array.isArray(node)) {
149
+ pos += node.reduce((prev, curr) => prev + curr?.nodeSize || 0, 0);
150
+ } else {
151
+ pos += node?.nodeSize || 0;
152
+ }
153
+ }
154
+ }
155
+ n = n.right;
156
+ }
157
+ pos += 1; // increase because we go out of n
158
+ }
159
+ while (type !== documentType && type._item !== null) {
160
+ const parent = type._item.parent;
161
+ if (parent instanceof Y.ID || parent === null) {
162
+ continue;
163
+ }
164
+ if (parent._item === null || !parent._item.deleted) {
165
+ pos += 1; // the start tag
166
+ let n = /** @type {Y.AbstractType} */ (parent)._first;
167
+ // now iterate until we found type
168
+ while (n !== null) {
169
+ const contentType: Y.AbstractType<any> = n.content.type;
170
+ if (contentType === type) {
171
+ break;
172
+ }
173
+ if (!n.deleted) {
174
+ if (contentType instanceof Y.XmlText) {
175
+ pos += contentType._length;
176
+ } else {
177
+ const node = mapping.get(contentType);
178
+ if (Array.isArray(node)) {
179
+ pos += node.reduce((prev, curr) => prev + curr?.nodeSize || 0, 0);
180
+ } else {
181
+ pos += node?.nodeSize || 0;
182
+ }
183
+ }
184
+ }
185
+ n = n.right;
186
+ }
187
+ }
188
+ type = parent;
189
+ }
190
+ return pos - 1; // we don't count the most outer tag, because it is a fragment
191
+ };
@@ -0,0 +1,218 @@
1
+ import * as dntShim from "../_dnt.shims.js";
2
+ import * as Y from 'yjs';
3
+
4
+ import {
5
+ AllSelection,
6
+ EditorState,
7
+ NodeSelection,
8
+ Selection,
9
+ TextSelection,
10
+ Transaction,
11
+ } from 'prosemirror-state';
12
+
13
+ import { YjsData } from '../binding/PmYjsBinding.js';
14
+ import { ProsemirrorMapping } from '../lib.js';
15
+ import {
16
+ absolutePositionToRelativePosition,
17
+ relativePositionToAbsolutePosition,
18
+ } from '../position.js';
19
+ import { CoreEditor } from '@kerebron/editor';
20
+
21
+ interface TransactionSelection {
22
+ type: string;
23
+ anchor: Y.RelativePosition;
24
+ head: Y.RelativePosition;
25
+ }
26
+
27
+ const restoreRelativeSelection = (
28
+ tr: Transaction,
29
+ relSel: ReturnType<typeof getRelativeSelection>,
30
+ yjs: YjsData,
31
+ mapping: ProsemirrorMapping,
32
+ ) => {
33
+ const { ydoc, xmlFragment } = yjs;
34
+
35
+ if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
36
+ if (relSel.type === 'all') {
37
+ tr.setSelection(new AllSelection(tr.doc));
38
+ } else if (relSel.type === 'node') {
39
+ const anchor = relativePositionToAbsolutePosition(
40
+ ydoc,
41
+ xmlFragment,
42
+ relSel.anchor,
43
+ mapping,
44
+ );
45
+ if (anchor !== null) {
46
+ tr.setSelection(NodeSelection.create(tr.doc, anchor));
47
+ }
48
+ } else {
49
+ const anchor = relativePositionToAbsolutePosition(
50
+ ydoc,
51
+ xmlFragment,
52
+ relSel.anchor,
53
+ mapping,
54
+ );
55
+ const head = relativePositionToAbsolutePosition(
56
+ ydoc,
57
+ xmlFragment,
58
+ relSel.head,
59
+ mapping,
60
+ );
61
+ if (anchor !== null && head !== null) {
62
+ const sel = TextSelection.between(
63
+ tr.doc.resolve(anchor),
64
+ tr.doc.resolve(head),
65
+ );
66
+ tr.setSelection(sel);
67
+ }
68
+ }
69
+ }
70
+ };
71
+
72
+ export const getRelativeSelection = (
73
+ xmlFragment: Y.XmlFragment,
74
+ mapping: ProsemirrorMapping,
75
+ state: EditorState,
76
+ ): TransactionSelection => ({
77
+ type: getSelectionType(state.selection),
78
+ anchor: absolutePositionToRelativePosition(
79
+ state.selection.anchor,
80
+ xmlFragment,
81
+ mapping,
82
+ ),
83
+ head: absolutePositionToRelativePosition(
84
+ state.selection.head,
85
+ xmlFragment,
86
+ mapping,
87
+ ),
88
+ });
89
+
90
+ function getSelectionType(selection: Selection) {
91
+ if (selection instanceof TextSelection) {
92
+ return 'text';
93
+ }
94
+ if (selection instanceof AllSelection) {
95
+ return 'all';
96
+ }
97
+ if (selection instanceof NodeSelection) {
98
+ return 'node';
99
+ }
100
+ return 'other_selection';
101
+ }
102
+
103
+ export class SelectionStash {
104
+ private readonly beforeAllTransactions: () => void;
105
+ private readonly afterAllTransactions: () => void;
106
+
107
+ private _beforeTransactionSelection: TransactionSelection | null = null;
108
+ private _domSelectionInView: boolean = false;
109
+
110
+ constructor(
111
+ private yjs: YjsData,
112
+ private mapping: ProsemirrorMapping,
113
+ private editor: CoreEditor,
114
+ ) {
115
+ this.beforeAllTransactions = () => {
116
+ if (
117
+ !this._beforeTransactionSelection &&
118
+ editor.view
119
+ ) {
120
+ this._beforeTransactionSelection = getRelativeSelection(
121
+ yjs.xmlFragment,
122
+ mapping,
123
+ editor.state,
124
+ );
125
+ }
126
+ };
127
+ this.afterAllTransactions = () => {
128
+ this._beforeTransactionSelection = null;
129
+ };
130
+
131
+ this.yjs.ydoc.on('beforeAllTransactions', this.beforeAllTransactions);
132
+ this.yjs.ydoc.on('afterAllTransactions', this.afterAllTransactions);
133
+ }
134
+
135
+ destroy() {
136
+ this.yjs.ydoc.off('beforeAllTransactions', this.beforeAllTransactions);
137
+ this.yjs.ydoc.off('afterAllTransactions', this.afterAllTransactions);
138
+ }
139
+
140
+ store() {
141
+ this._beforeTransactionSelection = getRelativeSelection(
142
+ this.yjs.xmlFragment,
143
+ this.mapping,
144
+ this.editor.state,
145
+ );
146
+ }
147
+
148
+ overwrite(transactionSelection: TransactionSelection) {
149
+ this._beforeTransactionSelection = transactionSelection;
150
+ }
151
+
152
+ restore(tr: Transaction) {
153
+ if (this._beforeTransactionSelection) {
154
+ restoreRelativeSelection(
155
+ tr,
156
+ this._beforeTransactionSelection,
157
+ this.yjs,
158
+ this.mapping,
159
+ );
160
+ if (this._isLocalCursorInView()) {
161
+ tr.scrollIntoView();
162
+ }
163
+ }
164
+ }
165
+
166
+ _isLocalCursorInView(): boolean {
167
+ if (!this.editor.view.hasFocus()) return false;
168
+
169
+ // const isNode = /* @__PURE__ */(() => typeof process !== 'undefined' && process.release && /node|io\.js/.test(process.release.name) && Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]')()
170
+ const isBrowser =
171
+ /* @__PURE__ */ (() =>
172
+ typeof dntShim.dntGlobalThis !== 'undefined' && typeof document !== 'undefined')(); // && !isNode
173
+
174
+ if (isBrowser && this._domSelectionInView === false) {
175
+ // Calculate the domSelectionInView and clear by next tick after all events are finished
176
+ setTimeout(() => {
177
+ this._domSelectionInView = false;
178
+ }, 0);
179
+ this._domSelectionInView = this._isDomSelectionInView();
180
+ }
181
+ return this._domSelectionInView;
182
+ }
183
+
184
+ _isDomSelectionInView(): boolean {
185
+ const view = this.editor.view;
186
+ if (!('root' in view)) {
187
+ return false;
188
+ }
189
+ const selection = document.getSelection();
190
+
191
+ if (
192
+ !selection || selection.anchorNode == null || selection.focusNode == null
193
+ ) return false;
194
+
195
+ const range = document.createRange();
196
+ range.setStart(selection.anchorNode, selection.anchorOffset);
197
+ range.setEnd(selection.focusNode, selection.focusOffset);
198
+
199
+ // This is a workaround for an edgecase where getBoundingClientRect will
200
+ // return zero values if the selection is collapsed at the start of a newline
201
+ // see reference here: https://stackoverflow.com/a/59780954
202
+ const rects = range.getClientRects();
203
+ if (rects.length === 0) {
204
+ // probably buggy newline behavior, explicitly select the node contents
205
+ if (range.startContainer && range.collapsed) {
206
+ range.selectNodeContents(range.startContainer);
207
+ }
208
+ }
209
+
210
+ const bounding = range.getBoundingClientRect();
211
+ const documentElement = document.documentElement;
212
+
213
+ return bounding.bottom >= 0 && bounding.right >= 0 &&
214
+ bounding.left <=
215
+ (globalThis.innerWidth || documentElement.clientWidth || 0) &&
216
+ bounding.top <= (globalThis.innerHeight || documentElement.clientHeight || 0);
217
+ }
218
+ }
package/src/utils.ts CHANGED
@@ -13,7 +13,7 @@ const _convolute = (digest: Uint8Array) => {
13
13
  export const hashOfJSON = (json: any) =>
14
14
  buf.toBase64(_convolute(sha256.digest(buf.encodeAny(json))));
15
15
 
16
- export const isVisible = (item: Y.Item, snapshot: Y.Snapshot) =>
16
+ export const isVisible = (item: Y.Item, snapshot?: Y.Snapshot) =>
17
17
  snapshot === undefined ? !item.deleted : (snapshot.sv.has(item.id.client) &&
18
18
  (snapshot.sv.get(item.id.client)!) > item.id.clock &&
19
19
  !Y.isDeleted(snapshot.ds, item.id));