@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.
- package/esm/ExtensionYjs.d.ts +3 -11
- package/esm/ExtensionYjs.d.ts.map +1 -1
- package/esm/ExtensionYjs.js +38 -45
- package/esm/ExtensionYjs.js.map +1 -1
- package/esm/WebsocketProvider.d.ts +69 -0
- package/esm/WebsocketProvider.d.ts.map +1 -0
- package/esm/WebsocketProvider.js +354 -0
- package/esm/WebsocketProvider.js.map +1 -0
- package/esm/YjsProvider.d.ts +48 -0
- package/esm/YjsProvider.d.ts.map +1 -0
- package/esm/YjsProvider.js +12 -0
- package/esm/YjsProvider.js.map +1 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +58 -0
- package/esm/_dnt.shims.js.map +1 -0
- package/esm/binding/BindingMetadata.d.ts +6 -0
- package/esm/binding/BindingMetadata.d.ts.map +1 -0
- package/esm/binding/BindingMetadata.js +2 -0
- package/esm/binding/BindingMetadata.js.map +1 -0
- package/esm/binding/PmYjsBinding.d.ts +41 -0
- package/esm/binding/PmYjsBinding.d.ts.map +1 -0
- package/esm/binding/PmYjsBinding.js +190 -0
- package/esm/binding/PmYjsBinding.js.map +1 -0
- package/esm/binding/convertUtils.d.ts +48 -0
- package/esm/binding/convertUtils.d.ts.map +1 -0
- package/esm/binding/convertUtils.js +80 -0
- package/esm/binding/convertUtils.js.map +1 -0
- package/esm/{createNodeFromYElement.d.ts → binding/createNodeFromYElement.d.ts} +1 -1
- package/esm/binding/createNodeFromYElement.d.ts.map +1 -0
- package/esm/{createNodeFromYElement.js → binding/createNodeFromYElement.js} +2 -2
- package/esm/binding/createNodeFromYElement.js.map +1 -0
- package/esm/{updateYFragment.d.ts → binding/updateYFragment.d.ts} +3 -3
- package/esm/binding/updateYFragment.d.ts.map +1 -0
- package/esm/{updateYFragment.js → binding/updateYFragment.js} +10 -7
- package/esm/binding/updateYFragment.js.map +1 -0
- package/esm/lib.d.ts +1 -7
- package/esm/lib.d.ts.map +1 -1
- package/esm/lib.js +1 -200
- package/esm/lib.js.map +1 -1
- package/esm/position.d.ts +8 -0
- package/esm/position.d.ts.map +1 -0
- package/esm/position.js +165 -0
- package/esm/position.js.map +1 -0
- package/esm/ui/selection.d.ts +29 -0
- package/esm/ui/selection.d.ts.map +1 -0
- package/esm/ui/selection.js +129 -0
- package/esm/ui/selection.js.map +1 -0
- package/esm/yPositionPlugin.d.ts +6 -1
- package/esm/yPositionPlugin.d.ts.map +1 -1
- package/esm/yPositionPlugin.js +91 -50
- package/esm/yPositionPlugin.js.map +1 -1
- package/esm/ySyncPlugin.d.ts +5 -22
- package/esm/ySyncPlugin.d.ts.map +1 -1
- package/esm/ySyncPlugin.js +54 -116
- package/esm/ySyncPlugin.js.map +1 -1
- package/esm/yUndoPlugin.d.ts +11 -10
- package/esm/yUndoPlugin.d.ts.map +1 -1
- package/esm/yUndoPlugin.js +90 -52
- package/esm/yUndoPlugin.js.map +1 -1
- package/package.json +9 -6
- package/src/ExtensionYjs.ts +55 -67
- package/src/WebsocketProvider.ts +516 -0
- package/src/YjsProvider.ts +75 -0
- package/src/_dnt.shims.ts +60 -0
- package/src/binding/BindingMetadata.ts +6 -0
- package/src/binding/PmYjsBinding.ts +300 -0
- package/src/binding/convertUtils.ts +124 -0
- package/src/{createNodeFromYElement.ts → binding/createNodeFromYElement.ts} +3 -3
- package/src/{updateYFragment.ts → binding/updateYFragment.ts} +15 -8
- package/src/lib.ts +4 -230
- package/src/position.ts +191 -0
- package/src/ui/selection.ts +216 -0
- package/src/yPositionPlugin.ts +122 -74
- package/src/ySyncPlugin.ts +87 -170
- package/src/yUndoPlugin.ts +113 -62
- package/esm/ProsemirrorBinding.d.ts +0 -60
- package/esm/ProsemirrorBinding.d.ts.map +0 -1
- package/esm/ProsemirrorBinding.js +0 -405
- package/esm/ProsemirrorBinding.js.map +0 -1
- package/esm/createNodeFromYElement.d.ts.map +0 -1
- package/esm/createNodeFromYElement.js.map +0 -1
- package/esm/updateYFragment.d.ts.map +0 -1
- package/esm/updateYFragment.js.map +0 -1
- package/esm/userColors.d.ts +0 -5
- package/esm/userColors.d.ts.map +0 -1
- package/esm/userColors.js +0 -11
- package/esm/userColors.js.map +0 -1
- package/src/ProsemirrorBinding.ts +0 -607
- 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
|
-
|
|
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
|
-
};
|
package/src/position.ts
ADDED
|
@@ -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
|
+
}
|