@kerebron/extension-yjs 0.5.3 → 0.5.4
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/README.md +3 -89
- package/esm/ExtensionYjs.d.ts +10 -1
- package/esm/ExtensionYjs.d.ts.map +1 -1
- package/esm/ExtensionYjs.js +47 -6
- package/esm/ExtensionYjs.js.map +1 -1
- package/esm/MarkYChange.d.ts +7 -0
- package/esm/MarkYChange.d.ts.map +1 -0
- package/esm/MarkYChange.js +21 -0
- package/esm/MarkYChange.js.map +1 -0
- package/esm/ProsemirrorBinding.d.ts +60 -0
- package/esm/ProsemirrorBinding.d.ts.map +1 -0
- package/esm/ProsemirrorBinding.js +405 -0
- package/esm/ProsemirrorBinding.js.map +1 -0
- package/esm/createNodeFromYElement.d.ts +10 -0
- package/esm/createNodeFromYElement.d.ts.map +1 -0
- package/esm/createNodeFromYElement.js +123 -0
- package/esm/createNodeFromYElement.js.map +1 -0
- package/esm/debug.d.ts +13 -0
- package/esm/debug.d.ts.map +1 -0
- package/esm/debug.js +147 -0
- package/esm/debug.js.map +1 -0
- package/esm/keys.d.ts +5 -8
- package/esm/keys.d.ts.map +1 -1
- package/esm/keys.js +1 -6
- package/esm/keys.js.map +1 -1
- package/esm/lib.d.ts +1 -2
- package/esm/lib.d.ts.map +1 -1
- package/esm/lib.js +12 -2
- package/esm/lib.js.map +1 -1
- package/esm/updateYFragment.d.ts +17 -0
- package/esm/updateYFragment.d.ts.map +1 -0
- package/esm/updateYFragment.js +333 -0
- package/esm/updateYFragment.js.map +1 -0
- package/esm/utils.d.ts +2 -0
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +4 -0
- package/esm/utils.js.map +1 -1
- package/esm/yPositionPlugin.d.ts +12 -4
- package/esm/yPositionPlugin.d.ts.map +1 -1
- package/esm/yPositionPlugin.js +114 -61
- package/esm/yPositionPlugin.js.map +1 -1
- package/esm/ySyncPlugin.d.ts +16 -78
- package/esm/ySyncPlugin.d.ts.map +1 -1
- package/esm/ySyncPlugin.js +81 -848
- package/esm/ySyncPlugin.js.map +1 -1
- package/esm/yUndoPlugin.d.ts +1 -1
- package/esm/yUndoPlugin.d.ts.map +1 -1
- package/esm/yUndoPlugin.js +1 -1
- package/esm/yUndoPlugin.js.map +1 -1
- package/package.json +9 -3
- package/src/ExtensionYjs.ts +65 -9
- package/src/MarkYChange.ts +23 -0
- package/src/ProsemirrorBinding.ts +607 -0
- package/src/createNodeFromYElement.ts +175 -0
- package/src/debug.ts +218 -0
- package/src/keys.ts +9 -9
- package/src/lib.ts +11 -3
- package/src/updateYFragment.ts +439 -0
- package/src/utils.ts +6 -0
- package/src/yPositionPlugin.ts +167 -92
- package/src/ySyncPlugin.ts +135 -1193
- package/src/yUndoPlugin.ts +1 -1
- package/esm/convertUtils.d.ts +0 -59
- package/esm/convertUtils.d.ts.map +0 -1
- package/esm/convertUtils.js +0 -89
- package/esm/convertUtils.js.map +0 -1
- package/src/convertUtils.ts +0 -143
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import * as PModel from 'prosemirror-model';
|
|
3
|
+
import { Mark, Node, Schema } from 'prosemirror-model';
|
|
4
|
+
|
|
5
|
+
import { simpleDiff } from 'lib0/diff';
|
|
6
|
+
|
|
7
|
+
import { ySyncPluginKey } from './keys.js';
|
|
8
|
+
import * as utils from './utils.js';
|
|
9
|
+
import type { BindingMetadata } from './ProsemirrorBinding.js';
|
|
10
|
+
import { TransactFunc } from './ySyncPlugin.js';
|
|
11
|
+
|
|
12
|
+
const hashedMarkNameRegex = /(.*)(--[a-zA-Z0-9+/=]{8})$/;
|
|
13
|
+
export const yattr2markname = (attrName: string) =>
|
|
14
|
+
hashedMarkNameRegex.exec(attrName)?.[1] ?? attrName;
|
|
15
|
+
|
|
16
|
+
const marksToAttributes = (
|
|
17
|
+
marks: readonly Mark[],
|
|
18
|
+
meta: BindingMetadata,
|
|
19
|
+
): Record<string, PModel.Attrs> => {
|
|
20
|
+
const pattrs: Record<string, PModel.Attrs> = {};
|
|
21
|
+
marks.forEach((mark) => {
|
|
22
|
+
if (mark.type.name !== 'ychange') {
|
|
23
|
+
let isOverlapping = true;
|
|
24
|
+
if (!meta.isOMark.has(mark.type.name)) {
|
|
25
|
+
meta.isOMark.set(mark.type.name, !mark.type.excludes(mark.type));
|
|
26
|
+
isOverlapping = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pattrs[
|
|
30
|
+
isOverlapping
|
|
31
|
+
? `${mark.type.name}--${utils.hashOfJSON(mark.toJSON())}`
|
|
32
|
+
: mark.type.name
|
|
33
|
+
] = mark.attrs;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return pattrs;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const equalYTextPText = (ytext: Y.XmlText, ptexts: Array<any>): boolean => {
|
|
40
|
+
const delta = ytext.toDelta();
|
|
41
|
+
return delta.length === ptexts.length &&
|
|
42
|
+
delta.every((d: any, i: number): boolean =>
|
|
43
|
+
d.insert === /** @type {any} */ (ptexts[i]).text &&
|
|
44
|
+
Object.keys(d.attributes || {}).length === ptexts[i].marks.length &&
|
|
45
|
+
Object.entries(d.attributes || {}).every(([yattrname, attr]) => {
|
|
46
|
+
const markname = yattr2markname(yattrname);
|
|
47
|
+
const pmarks = ptexts[i].marks;
|
|
48
|
+
return equalAttrs(
|
|
49
|
+
attr,
|
|
50
|
+
pmarks.find((mark: Mark) => mark.type.name === markname)?.attrs,
|
|
51
|
+
);
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const ytextTrans = (ytext: Y.Text) => {
|
|
57
|
+
let str = '';
|
|
58
|
+
let n: Y.Item | null = ytext._start;
|
|
59
|
+
const nAttrs: Record<string, null> = {};
|
|
60
|
+
while (n !== null) {
|
|
61
|
+
if (!n.deleted) {
|
|
62
|
+
if (n.countable && n.content instanceof Y.ContentString) {
|
|
63
|
+
str += n.content.str;
|
|
64
|
+
} else if (n.content instanceof Y.ContentFormat) {
|
|
65
|
+
nAttrs[n.content.key] = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
n = n.right;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
str,
|
|
72
|
+
nAttrs,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type PMTextNodes = Array<PModel.Node>;
|
|
77
|
+
|
|
78
|
+
const normalizePNodeContent = (
|
|
79
|
+
pnode: any,
|
|
80
|
+
): Array<PMTextNodes | PModel.Node> => {
|
|
81
|
+
const c = pnode.content.content;
|
|
82
|
+
const res = [];
|
|
83
|
+
for (let i = 0; i < c.length; i++) {
|
|
84
|
+
const n: PModel.Node = c[i];
|
|
85
|
+
if (n.isText) {
|
|
86
|
+
const textNodes: PMTextNodes = [];
|
|
87
|
+
for (let tnode = c[i]; i < c.length && tnode.isText; tnode = c[++i]) {
|
|
88
|
+
textNodes.push(tnode);
|
|
89
|
+
}
|
|
90
|
+
i--;
|
|
91
|
+
res.push(textNodes);
|
|
92
|
+
} else {
|
|
93
|
+
res.push(n);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return res;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
const createTypeFromTextNodes = (
|
|
103
|
+
nodes: PMTextNodes,
|
|
104
|
+
meta: BindingMetadata,
|
|
105
|
+
): Y.XmlText => {
|
|
106
|
+
const type = new Y.XmlText();
|
|
107
|
+
const delta = nodes.map((node) => ({
|
|
108
|
+
insert: node.text,
|
|
109
|
+
attributes: marksToAttributes(node.marks, meta),
|
|
110
|
+
}));
|
|
111
|
+
type.applyDelta(delta);
|
|
112
|
+
meta.mapping.set(type, nodes);
|
|
113
|
+
return type;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
type NodeResult<T> = T extends PMTextNodes ? Y.XmlText : Y.XmlElement;
|
|
117
|
+
|
|
118
|
+
function createTypeFromTextOrElementNode<T extends PModel.Node | PMTextNodes>(
|
|
119
|
+
node: T,
|
|
120
|
+
meta: BindingMetadata,
|
|
121
|
+
): NodeResult<T> {
|
|
122
|
+
return (Array.isArray(node)
|
|
123
|
+
? createTypeFromTextNodes(node, meta)
|
|
124
|
+
: createTypeFromElementNode(node, meta)) as NodeResult<T>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const isObject = (val: any) => typeof val === 'object' && val !== null;
|
|
128
|
+
|
|
129
|
+
const equalAttrs = (pattrs: any, yattrs: any) => {
|
|
130
|
+
const keys = Object.keys(pattrs).filter((key) => pattrs[key] !== null);
|
|
131
|
+
let eq = keys.length ===
|
|
132
|
+
(yattrs == null
|
|
133
|
+
? 0
|
|
134
|
+
: Object.keys(yattrs).filter((key) => yattrs[key] !== null).length);
|
|
135
|
+
for (let i = 0; i < keys.length && eq; i++) {
|
|
136
|
+
const key = keys[i];
|
|
137
|
+
const l = pattrs[key];
|
|
138
|
+
const r = yattrs[key];
|
|
139
|
+
eq = key === 'ychange' || l === r ||
|
|
140
|
+
(isObject(l) && isObject(r) && equalAttrs(l, r));
|
|
141
|
+
}
|
|
142
|
+
return eq;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
const createTypeFromElementNode = (
|
|
149
|
+
node: PModel.Node,
|
|
150
|
+
meta: BindingMetadata,
|
|
151
|
+
): Y.XmlElement => {
|
|
152
|
+
const type = new Y.XmlElement(node.type.name);
|
|
153
|
+
for (const key in node.attrs) {
|
|
154
|
+
const val = node.attrs[key];
|
|
155
|
+
if ('undefined' !== typeof val && val !== null && key !== 'ychange') {
|
|
156
|
+
type.setAttribute(key, val);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const pChildren: Array<PMTextNodes | PModel.Node> = normalizePNodeContent(
|
|
160
|
+
node,
|
|
161
|
+
);
|
|
162
|
+
const yElems: Array<Y.XmlElement | Y.XmlText> = pChildren.map((n) =>
|
|
163
|
+
createTypeFromTextOrElementNode(n, meta)
|
|
164
|
+
);
|
|
165
|
+
type.insert(0, yElems);
|
|
166
|
+
meta.mapping.set(type, node);
|
|
167
|
+
return type;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const mappedIdentity = (
|
|
171
|
+
mapped: PModel.Node | Array<PModel.Node> | undefined,
|
|
172
|
+
pcontent: PModel.Node | Array<PModel.Node>,
|
|
173
|
+
) =>
|
|
174
|
+
mapped === pcontent ||
|
|
175
|
+
(mapped instanceof Array && pcontent instanceof Array &&
|
|
176
|
+
mapped.length === pcontent.length &&
|
|
177
|
+
mapped.every((a, i) => pcontent[i] === a));
|
|
178
|
+
|
|
179
|
+
const computeChildEqualityFactor = (
|
|
180
|
+
ytype: Y.XmlElement,
|
|
181
|
+
pnode: PModel.Node,
|
|
182
|
+
meta: BindingMetadata,
|
|
183
|
+
): { foundMappedChild: boolean; equalityFactor: number } => {
|
|
184
|
+
const yChildren = ytype.toArray();
|
|
185
|
+
const pChildren: Array<PMTextNodes | PModel.Node> = normalizePNodeContent(
|
|
186
|
+
pnode,
|
|
187
|
+
);
|
|
188
|
+
const pChildCnt = pChildren.length;
|
|
189
|
+
const yChildCnt = yChildren.length;
|
|
190
|
+
const minCnt = Math.min(yChildCnt, pChildCnt);
|
|
191
|
+
let left = 0;
|
|
192
|
+
let right = 0;
|
|
193
|
+
let foundMappedChild = false;
|
|
194
|
+
for (; left < minCnt; left++) {
|
|
195
|
+
const leftY = yChildren[left];
|
|
196
|
+
const leftP = pChildren[left];
|
|
197
|
+
if (mappedIdentity(meta.mapping.get(leftY), leftP)) {
|
|
198
|
+
foundMappedChild = true; // definite (good) match!
|
|
199
|
+
} else if (!equalYTypePNode(leftY, leftP)) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (; left + right < minCnt; right++) {
|
|
204
|
+
const rightY = yChildren[yChildCnt - right - 1];
|
|
205
|
+
const rightP = pChildren[pChildCnt - right - 1];
|
|
206
|
+
if (mappedIdentity(meta.mapping.get(rightY), rightP)) {
|
|
207
|
+
foundMappedChild = true;
|
|
208
|
+
} else if (!equalYTypePNode(rightY, rightP)) {
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
equalityFactor: left + right,
|
|
214
|
+
foundMappedChild,
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const matchNodeName = (
|
|
219
|
+
yElement: Y.XmlElement,
|
|
220
|
+
pNode: PModel.Node | PMTextNodes,
|
|
221
|
+
) => !(pNode instanceof Array) && yElement.nodeName === pNode.type.name;
|
|
222
|
+
|
|
223
|
+
const equalYTypePNode = (
|
|
224
|
+
ytype: Y.XmlElement | Y.XmlText | Y.XmlHook,
|
|
225
|
+
pnode: any | Array<any>,
|
|
226
|
+
): boolean => {
|
|
227
|
+
if (
|
|
228
|
+
ytype instanceof Y.XmlElement && !(pnode instanceof Array) &&
|
|
229
|
+
matchNodeName(ytype, pnode)
|
|
230
|
+
) {
|
|
231
|
+
const normalizedContent: Array<PMTextNodes | PModel.Node> =
|
|
232
|
+
normalizePNodeContent(pnode);
|
|
233
|
+
return ytype._length === normalizedContent.length &&
|
|
234
|
+
equalAttrs(ytype.getAttributes(), pnode.attrs) &&
|
|
235
|
+
ytype.toArray().every((ychild, i) =>
|
|
236
|
+
equalYTypePNode(ychild, normalizedContent[i])
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return ytype instanceof Y.XmlText && pnode instanceof Array &&
|
|
240
|
+
equalYTextPText(ytype, pnode);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @todo test this more
|
|
245
|
+
*/
|
|
246
|
+
const updateYText = (
|
|
247
|
+
ytext: Y.Text,
|
|
248
|
+
ptexts: PMTextNodes,
|
|
249
|
+
meta: BindingMetadata,
|
|
250
|
+
) => {
|
|
251
|
+
meta.mapping.set(ytext, ptexts);
|
|
252
|
+
const { nAttrs, str } = ytextTrans(ytext);
|
|
253
|
+
const content = ptexts.map((p) => ({
|
|
254
|
+
insert: p.text || '',
|
|
255
|
+
attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks, meta)),
|
|
256
|
+
}));
|
|
257
|
+
const { insert, remove, index } = simpleDiff(
|
|
258
|
+
str,
|
|
259
|
+
content.map((c) => c.insert).join(''),
|
|
260
|
+
);
|
|
261
|
+
ytext.delete(index, remove);
|
|
262
|
+
ytext.insert(index, insert);
|
|
263
|
+
ytext.applyDelta(
|
|
264
|
+
content.map((c) => ({ retain: c.insert.length, attributes: c.attributes })),
|
|
265
|
+
);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Update a yDom node by syncing the current content of the prosemirror node.
|
|
270
|
+
*
|
|
271
|
+
* This is a y-prosemirror internal feature that you can use at your own risk.
|
|
272
|
+
*
|
|
273
|
+
* @private
|
|
274
|
+
* @unstable
|
|
275
|
+
*/
|
|
276
|
+
export const updateYFragment = (
|
|
277
|
+
ydoc: { transact: TransactFunc<void> },
|
|
278
|
+
yDomFragment: Y.XmlFragment,
|
|
279
|
+
pNode: Node,
|
|
280
|
+
meta: BindingMetadata,
|
|
281
|
+
) => {
|
|
282
|
+
if (
|
|
283
|
+
yDomFragment instanceof Y.XmlElement &&
|
|
284
|
+
yDomFragment.nodeName !== pNode.type.name
|
|
285
|
+
) {
|
|
286
|
+
throw new Error('node name mismatch!');
|
|
287
|
+
}
|
|
288
|
+
meta.mapping.set(yDomFragment, pNode);
|
|
289
|
+
// update attributes
|
|
290
|
+
if (yDomFragment instanceof Y.XmlElement) {
|
|
291
|
+
const yDomAttrs = yDomFragment.getAttributes();
|
|
292
|
+
const pAttrs = pNode.attrs;
|
|
293
|
+
for (const key in pAttrs) {
|
|
294
|
+
if (pAttrs[key] !== null) {
|
|
295
|
+
if (yDomAttrs[key] !== pAttrs[key] && key !== 'ychange') {
|
|
296
|
+
yDomFragment.setAttribute(key, pAttrs[key]);
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
yDomFragment.removeAttribute(key);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// remove all keys that are no longer in pAttrs
|
|
303
|
+
for (const key in yDomAttrs) {
|
|
304
|
+
if (pAttrs[key] === undefined) {
|
|
305
|
+
yDomFragment.removeAttribute(key);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// update children
|
|
310
|
+
const pChildren: Array<PMTextNodes | PModel.Node> = normalizePNodeContent(
|
|
311
|
+
pNode,
|
|
312
|
+
);
|
|
313
|
+
const pChildCnt = pChildren.length;
|
|
314
|
+
const yChildren = yDomFragment.toArray() as Array<Y.XmlElement | Y.XmlText>;
|
|
315
|
+
const yChildCnt = yChildren.length;
|
|
316
|
+
const minCnt = Math.min(pChildCnt, yChildCnt);
|
|
317
|
+
let left = 0;
|
|
318
|
+
let right = 0;
|
|
319
|
+
// find number of matching elements from left
|
|
320
|
+
for (; left < minCnt; left++) {
|
|
321
|
+
const leftY = yChildren[left];
|
|
322
|
+
const leftP = pChildren[left];
|
|
323
|
+
if (!mappedIdentity(meta.mapping.get(leftY), leftP)) {
|
|
324
|
+
if (equalYTypePNode(leftY, leftP)) {
|
|
325
|
+
// update mapping
|
|
326
|
+
meta.mapping.set(leftY, leftP);
|
|
327
|
+
} else {
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// find number of matching elements from right
|
|
333
|
+
for (; right + left < minCnt; right++) {
|
|
334
|
+
const rightY = yChildren[yChildCnt - right - 1];
|
|
335
|
+
const rightP = pChildren[pChildCnt - right - 1];
|
|
336
|
+
if (!mappedIdentity(meta.mapping.get(rightY), rightP)) {
|
|
337
|
+
if (equalYTypePNode(rightY, rightP)) {
|
|
338
|
+
// update mapping
|
|
339
|
+
meta.mapping.set(rightY, rightP);
|
|
340
|
+
} else {
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
ydoc.transact(() => {
|
|
346
|
+
// try to compare and update
|
|
347
|
+
while (yChildCnt - left - right > 0 && pChildCnt - left - right > 0) {
|
|
348
|
+
const leftY: Y.XmlElement | Y.XmlText = yChildren[left];
|
|
349
|
+
const leftP: PModel.Node | PMTextNodes = pChildren[left];
|
|
350
|
+
const rightY: Y.XmlElement | Y.XmlText = yChildren[yChildCnt - right - 1];
|
|
351
|
+
const rightP: PModel.Node | PMTextNodes =
|
|
352
|
+
pChildren[pChildCnt - right - 1];
|
|
353
|
+
if (leftY instanceof Y.XmlText && leftP instanceof Array) {
|
|
354
|
+
if (!equalYTextPText(leftY, leftP)) {
|
|
355
|
+
updateYText(leftY, leftP, meta);
|
|
356
|
+
}
|
|
357
|
+
left += 1;
|
|
358
|
+
} else {
|
|
359
|
+
let updateLeft = leftY instanceof Y.XmlElement &&
|
|
360
|
+
matchNodeName(leftY, leftP);
|
|
361
|
+
let updateRight = rightY instanceof Y.XmlElement &&
|
|
362
|
+
matchNodeName(rightY, rightP);
|
|
363
|
+
if (updateLeft && updateRight) {
|
|
364
|
+
// decide which element to update
|
|
365
|
+
const equalityLeft = computeChildEqualityFactor(
|
|
366
|
+
leftY as Y.XmlElement,
|
|
367
|
+
leftP as PModel.Node,
|
|
368
|
+
meta,
|
|
369
|
+
);
|
|
370
|
+
const equalityRight = computeChildEqualityFactor(
|
|
371
|
+
rightY as Y.XmlElement,
|
|
372
|
+
rightP as PModel.Node,
|
|
373
|
+
meta,
|
|
374
|
+
);
|
|
375
|
+
if (
|
|
376
|
+
equalityLeft.foundMappedChild && !equalityRight.foundMappedChild
|
|
377
|
+
) {
|
|
378
|
+
updateRight = false;
|
|
379
|
+
} else if (
|
|
380
|
+
!equalityLeft.foundMappedChild && equalityRight.foundMappedChild
|
|
381
|
+
) {
|
|
382
|
+
updateLeft = false;
|
|
383
|
+
} else if (
|
|
384
|
+
equalityLeft.equalityFactor < equalityRight.equalityFactor
|
|
385
|
+
) {
|
|
386
|
+
updateLeft = false;
|
|
387
|
+
} else {
|
|
388
|
+
updateRight = false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (updateLeft) {
|
|
392
|
+
updateYFragment(
|
|
393
|
+
ydoc,
|
|
394
|
+
leftY as Y.XmlElement,
|
|
395
|
+
leftP as PModel.Node,
|
|
396
|
+
meta,
|
|
397
|
+
);
|
|
398
|
+
left += 1;
|
|
399
|
+
} else if (updateRight) {
|
|
400
|
+
updateYFragment(
|
|
401
|
+
ydoc,
|
|
402
|
+
rightY as Y.XmlElement,
|
|
403
|
+
rightP as PModel.Node,
|
|
404
|
+
meta,
|
|
405
|
+
);
|
|
406
|
+
right += 1;
|
|
407
|
+
} else {
|
|
408
|
+
meta.mapping.delete(yDomFragment.get(left));
|
|
409
|
+
yDomFragment.delete(left, 1);
|
|
410
|
+
yDomFragment.insert(left, [
|
|
411
|
+
createTypeFromTextOrElementNode(leftP, meta),
|
|
412
|
+
]);
|
|
413
|
+
left += 1;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const yDelLen = yChildCnt - left - right;
|
|
418
|
+
if (
|
|
419
|
+
yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof Y.XmlText
|
|
420
|
+
) {
|
|
421
|
+
meta.mapping.delete(yChildren[0]);
|
|
422
|
+
// Edge case handling https://github.com/yjs/y-prosemirror/issues/108
|
|
423
|
+
// Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
|
|
424
|
+
yChildren[0].delete(0, yChildren[0].length);
|
|
425
|
+
} else if (yDelLen > 0) {
|
|
426
|
+
yDomFragment.slice(left, left + yDelLen).forEach((type) =>
|
|
427
|
+
meta.mapping.delete(type)
|
|
428
|
+
);
|
|
429
|
+
yDomFragment.delete(left, yDelLen);
|
|
430
|
+
}
|
|
431
|
+
if (left + right < pChildCnt) {
|
|
432
|
+
const ins = [];
|
|
433
|
+
for (let i = left; i < pChildCnt - right; i++) {
|
|
434
|
+
ins.push(createTypeFromTextOrElementNode(pChildren[i], meta));
|
|
435
|
+
}
|
|
436
|
+
yDomFragment.insert(left, ins);
|
|
437
|
+
}
|
|
438
|
+
}, ySyncPluginKey);
|
|
439
|
+
};
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
1
2
|
import * as sha256 from 'lib0/hash/sha256';
|
|
2
3
|
import * as buf from 'lib0/buffer';
|
|
3
4
|
|
|
@@ -11,3 +12,8 @@ const _convolute = (digest: Uint8Array) => {
|
|
|
11
12
|
|
|
12
13
|
export const hashOfJSON = (json: any) =>
|
|
13
14
|
buf.toBase64(_convolute(sha256.digest(buf.encodeAny(json))));
|
|
15
|
+
|
|
16
|
+
export const isVisible = (item: Y.Item, snapshot: Y.Snapshot) =>
|
|
17
|
+
snapshot === undefined ? !item.deleted : (snapshot.sv.has(item.id.client) &&
|
|
18
|
+
(snapshot.sv.get(item.id.client)!) > item.id.clock &&
|
|
19
|
+
!Y.isDeleted(snapshot.ds, item.id));
|