@kerebron/extension-yjs 0.6.7 → 0.7.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.
Files changed (90) hide show
  1. package/esm/ExtensionYjs.d.ts +3 -11
  2. package/esm/ExtensionYjs.d.ts.map +1 -1
  3. package/esm/ExtensionYjs.js +38 -45
  4. package/esm/ExtensionYjs.js.map +1 -1
  5. package/esm/WebsocketProvider.d.ts +69 -0
  6. package/esm/WebsocketProvider.d.ts.map +1 -0
  7. package/esm/WebsocketProvider.js +354 -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/PmYjsBinding.d.ts +41 -0
  22. package/esm/binding/PmYjsBinding.d.ts.map +1 -0
  23. package/esm/binding/PmYjsBinding.js +190 -0
  24. package/esm/binding/PmYjsBinding.js.map +1 -0
  25. package/esm/binding/convertUtils.d.ts +48 -0
  26. package/esm/binding/convertUtils.d.ts.map +1 -0
  27. package/esm/binding/convertUtils.js +80 -0
  28. package/esm/binding/convertUtils.js.map +1 -0
  29. package/esm/{createNodeFromYElement.d.ts → binding/createNodeFromYElement.d.ts} +1 -1
  30. package/esm/binding/createNodeFromYElement.d.ts.map +1 -0
  31. package/esm/{createNodeFromYElement.js → binding/createNodeFromYElement.js} +2 -2
  32. package/esm/binding/createNodeFromYElement.js.map +1 -0
  33. package/esm/{updateYFragment.d.ts → binding/updateYFragment.d.ts} +3 -3
  34. package/esm/binding/updateYFragment.d.ts.map +1 -0
  35. package/esm/{updateYFragment.js → binding/updateYFragment.js} +10 -7
  36. package/esm/binding/updateYFragment.js.map +1 -0
  37. package/esm/lib.d.ts +1 -7
  38. package/esm/lib.d.ts.map +1 -1
  39. package/esm/lib.js +1 -200
  40. package/esm/lib.js.map +1 -1
  41. package/esm/position.d.ts +8 -0
  42. package/esm/position.d.ts.map +1 -0
  43. package/esm/position.js +165 -0
  44. package/esm/position.js.map +1 -0
  45. package/esm/ui/selection.d.ts +29 -0
  46. package/esm/ui/selection.d.ts.map +1 -0
  47. package/esm/ui/selection.js +129 -0
  48. package/esm/ui/selection.js.map +1 -0
  49. package/esm/yPositionPlugin.d.ts +6 -1
  50. package/esm/yPositionPlugin.d.ts.map +1 -1
  51. package/esm/yPositionPlugin.js +91 -50
  52. package/esm/yPositionPlugin.js.map +1 -1
  53. package/esm/ySyncPlugin.d.ts +5 -22
  54. package/esm/ySyncPlugin.d.ts.map +1 -1
  55. package/esm/ySyncPlugin.js +54 -116
  56. package/esm/ySyncPlugin.js.map +1 -1
  57. package/esm/yUndoPlugin.d.ts +11 -10
  58. package/esm/yUndoPlugin.d.ts.map +1 -1
  59. package/esm/yUndoPlugin.js +90 -52
  60. package/esm/yUndoPlugin.js.map +1 -1
  61. package/package.json +9 -6
  62. package/src/ExtensionYjs.ts +55 -67
  63. package/src/WebsocketProvider.ts +516 -0
  64. package/src/YjsProvider.ts +75 -0
  65. package/src/_dnt.shims.ts +60 -0
  66. package/src/binding/BindingMetadata.ts +6 -0
  67. package/src/binding/PmYjsBinding.ts +300 -0
  68. package/src/binding/convertUtils.ts +124 -0
  69. package/src/{createNodeFromYElement.ts → binding/createNodeFromYElement.ts} +3 -3
  70. package/src/{updateYFragment.ts → binding/updateYFragment.ts} +15 -8
  71. package/src/lib.ts +4 -230
  72. package/src/position.ts +191 -0
  73. package/src/ui/selection.ts +216 -0
  74. package/src/yPositionPlugin.ts +122 -74
  75. package/src/ySyncPlugin.ts +87 -170
  76. package/src/yUndoPlugin.ts +113 -62
  77. package/esm/ProsemirrorBinding.d.ts +0 -60
  78. package/esm/ProsemirrorBinding.d.ts.map +0 -1
  79. package/esm/ProsemirrorBinding.js +0 -405
  80. package/esm/ProsemirrorBinding.js.map +0 -1
  81. package/esm/createNodeFromYElement.d.ts.map +0 -1
  82. package/esm/createNodeFromYElement.js.map +0 -1
  83. package/esm/updateYFragment.d.ts.map +0 -1
  84. package/esm/updateYFragment.js.map +0 -1
  85. package/esm/userColors.d.ts +0 -5
  86. package/esm/userColors.d.ts.map +0 -1
  87. package/esm/userColors.js +0 -11
  88. package/esm/userColors.js.map +0 -1
  89. package/src/ProsemirrorBinding.ts +0 -607
  90. package/src/userColors.ts +0 -10
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,216 @@
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 = view.root?.getSelection(); // https://stackoverflow.com/questions/62054839/shadowroot-getselection
190
+
191
+ if (!selection || selection.anchorNode == null) return false;
192
+
193
+ const range = document.createRange(); // https://github.com/yjs/y-prosemirror/pull/193
194
+ range.setStart(selection.anchorNode, selection.anchorOffset);
195
+ range.setEnd(selection.focusNode, selection.focusOffset);
196
+
197
+ // This is a workaround for an edgecase where getBoundingClientRect will
198
+ // return zero values if the selection is collapsed at the start of a newline
199
+ // see reference here: https://stackoverflow.com/a/59780954
200
+ const rects = range.getClientRects();
201
+ if (rects.length === 0) {
202
+ // probably buggy newline behavior, explicitly select the node contents
203
+ if (range.startContainer && range.collapsed) {
204
+ range.selectNodeContents(range.startContainer);
205
+ }
206
+ }
207
+
208
+ const bounding = range.getBoundingClientRect();
209
+ const documentElement = document.documentElement;
210
+
211
+ return bounding.bottom >= 0 && bounding.right >= 0 &&
212
+ bounding.left <=
213
+ (globalThis.innerWidth || documentElement.clientWidth || 0) &&
214
+ bounding.top <= (globalThis.innerHeight || documentElement.clientHeight || 0);
215
+ }
216
+ }