@textbus/collaborate 4.0.0-alpha.8 → 4.0.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/bundles/collaborate-module.d.ts +12 -1
- package/bundles/collaborate.d.ts +55 -64
- package/bundles/connectors/_api.d.ts +2 -0
- package/bundles/connectors/hocuspocus-connector.d.ts +13 -0
- package/bundles/connectors/y-websocket-connector.d.ts +13 -0
- package/bundles/index.esm.js +397 -317
- package/bundles/index.js +397 -314
- package/bundles/public-api.d.ts +3 -0
- package/bundles/sync-connector.d.ts +10 -0
- package/bundles/user-activity.d.ts +23 -0
- package/package.json +8 -8
package/bundles/index.esm.js
CHANGED
|
@@ -1,7 +1,70 @@
|
|
|
1
|
+
import { Subject, map, filter, Subscription } from '@tanbo/stream';
|
|
2
|
+
import { HocuspocusProvider } from '@hocuspocus/provider';
|
|
3
|
+
import { WebsocketProvider } from 'y-websocket';
|
|
1
4
|
import { Injectable, Inject, Optional } from '@viewfly/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
+
import { makeError, HISTORY_STACK_SIZE, ChangeOrigin, createObjectProxy, createArrayProxy, Slot, RootComponentRef, Scheduler, Registry, Selection, History } from '@textbus/core';
|
|
6
|
+
import { Doc, UndoManager, Map, Array as Array$1, Text, createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex } from 'yjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 协作通信通用接口
|
|
10
|
+
*/
|
|
11
|
+
class SyncConnector {
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class HocuspocusConnector extends SyncConnector {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
super();
|
|
17
|
+
this.loadEvent = new Subject();
|
|
18
|
+
this.stateChangeEvent = new Subject();
|
|
19
|
+
this.onLoad = this.loadEvent.asObservable();
|
|
20
|
+
this.onStateChange = this.stateChangeEvent.asObservable();
|
|
21
|
+
this.provide = new HocuspocusProvider(Object.assign(Object.assign({}, config), { onSynced: (data) => {
|
|
22
|
+
var _a;
|
|
23
|
+
(_a = config.onSynced) === null || _a === void 0 ? void 0 : _a.call(config, data);
|
|
24
|
+
this.loadEvent.next();
|
|
25
|
+
}, onAwarenessUpdate: (data) => {
|
|
26
|
+
var _a;
|
|
27
|
+
(_a = config.onAwarenessUpdate) === null || _a === void 0 ? void 0 : _a.call(config, data);
|
|
28
|
+
data.states.forEach(state => {
|
|
29
|
+
this.stateChangeEvent.next(state);
|
|
30
|
+
});
|
|
31
|
+
} }));
|
|
32
|
+
}
|
|
33
|
+
setLocalStateField(key, data) {
|
|
34
|
+
this.provide.setAwarenessField(key, data);
|
|
35
|
+
}
|
|
36
|
+
onDestroy() {
|
|
37
|
+
this.provide.disconnect();
|
|
38
|
+
this.provide.destroy();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class YWebsocketConnector extends SyncConnector {
|
|
43
|
+
constructor(url, roomName, yDoc) {
|
|
44
|
+
super();
|
|
45
|
+
this.loadEvent = new Subject();
|
|
46
|
+
this.stateChangeEvent = new Subject();
|
|
47
|
+
this.onLoad = this.loadEvent.asObservable();
|
|
48
|
+
this.onStateChange = this.stateChangeEvent.asObservable();
|
|
49
|
+
this.provide = new WebsocketProvider(url, roomName, yDoc);
|
|
50
|
+
this.provide.on('sync', (is) => {
|
|
51
|
+
if (is) {
|
|
52
|
+
this.loadEvent.next();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
this.provide.awareness.on('update', () => {
|
|
56
|
+
this.provide.awareness.getStates().forEach(state => {
|
|
57
|
+
this.stateChangeEvent.next(state);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
setLocalStateField(key, data) {
|
|
62
|
+
this.provide.awareness.setLocalStateField(key, data);
|
|
63
|
+
}
|
|
64
|
+
onDestroy() {
|
|
65
|
+
this.provide.disconnect();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
5
68
|
|
|
6
69
|
/******************************************************************************
|
|
7
70
|
Copyright (c) Microsoft Corporation.
|
|
@@ -41,38 +104,38 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
41
104
|
};
|
|
42
105
|
|
|
43
106
|
const collaborateErrorFn = makeError('Collaborate');
|
|
44
|
-
class
|
|
107
|
+
class SlotMap {
|
|
45
108
|
constructor() {
|
|
46
109
|
this.slotAndYTextMap = new WeakMap();
|
|
47
|
-
this.
|
|
110
|
+
this.yTextAndSlotMap = new WeakMap();
|
|
48
111
|
}
|
|
49
112
|
set(key, value) {
|
|
50
113
|
if (key instanceof Slot) {
|
|
51
114
|
this.slotAndYTextMap.set(key, value);
|
|
52
|
-
this.
|
|
115
|
+
this.yTextAndSlotMap.set(value, key);
|
|
53
116
|
}
|
|
54
117
|
else {
|
|
55
118
|
this.slotAndYTextMap.set(value, key);
|
|
56
|
-
this.
|
|
119
|
+
this.yTextAndSlotMap.set(key, value);
|
|
57
120
|
}
|
|
58
121
|
}
|
|
59
122
|
get(key) {
|
|
60
123
|
if (key instanceof Slot) {
|
|
61
124
|
return this.slotAndYTextMap.get(key) || null;
|
|
62
125
|
}
|
|
63
|
-
return this.
|
|
126
|
+
return this.yTextAndSlotMap.get(key) || null;
|
|
64
127
|
}
|
|
65
128
|
delete(key) {
|
|
66
129
|
if (key instanceof Slot) {
|
|
67
130
|
const v = this.slotAndYTextMap.get(key);
|
|
68
131
|
this.slotAndYTextMap.delete(key);
|
|
69
132
|
if (v) {
|
|
70
|
-
this.
|
|
133
|
+
this.yTextAndSlotMap.delete(v);
|
|
71
134
|
}
|
|
72
135
|
}
|
|
73
136
|
else {
|
|
74
|
-
const v = this.
|
|
75
|
-
this.
|
|
137
|
+
const v = this.yTextAndSlotMap.get(key);
|
|
138
|
+
this.yTextAndSlotMap.delete(key);
|
|
76
139
|
if (v) {
|
|
77
140
|
this.slotAndYTextMap.delete(v);
|
|
78
141
|
}
|
|
@@ -105,13 +168,9 @@ let Collaborate = class Collaborate {
|
|
|
105
168
|
this.manager = null;
|
|
106
169
|
this.subscriptions = [];
|
|
107
170
|
this.updateFromRemote = false;
|
|
108
|
-
this.contentSyncCaches = new WeakMap();
|
|
109
|
-
this.slotStateSyncCaches = new WeakMap();
|
|
110
|
-
this.slotsSyncCaches = new WeakMap();
|
|
111
|
-
this.componentStateSyncCaches = new WeakMap();
|
|
112
171
|
this.localChangesAppliedEvent = new Subject();
|
|
113
172
|
this.selectionChangeEvent = new Subject();
|
|
114
|
-
this.
|
|
173
|
+
this.slotMap = new SlotMap();
|
|
115
174
|
this.updateRemoteActions = [];
|
|
116
175
|
this.noRecord = {};
|
|
117
176
|
this.historyItems = [];
|
|
@@ -146,7 +205,7 @@ let Collaborate = class Collaborate {
|
|
|
146
205
|
this.subscriptions.push(this.scheduler.onLocalChangeBefore.subscribe(() => {
|
|
147
206
|
beforePosition = this.getRelativeCursorLocation();
|
|
148
207
|
}));
|
|
149
|
-
manager.on('stack-item-added', event => {
|
|
208
|
+
manager.on('stack-item-added', (event) => {
|
|
150
209
|
if (event.type === 'undo') {
|
|
151
210
|
if (event.origin === manager) {
|
|
152
211
|
this.index++;
|
|
@@ -258,50 +317,145 @@ let Collaborate = class Collaborate {
|
|
|
258
317
|
(_a = this.manager) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
259
318
|
}
|
|
260
319
|
syncRootComponent(root, rootComponent) {
|
|
261
|
-
let
|
|
262
|
-
if (!
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const sharedSlot = this.createSharedSlotBySlot(i);
|
|
266
|
-
slots.push([sharedSlot]);
|
|
267
|
-
});
|
|
320
|
+
let state = root.get('state');
|
|
321
|
+
if (!state) {
|
|
322
|
+
state = new Map();
|
|
323
|
+
this.syncLocalMapToSharedMap(rootComponent.state, state);
|
|
268
324
|
this.yDoc.transact(() => {
|
|
269
|
-
root.set('state',
|
|
270
|
-
root.set('slots', slots);
|
|
325
|
+
root.set('state', state);
|
|
271
326
|
});
|
|
272
327
|
}
|
|
273
|
-
else
|
|
274
|
-
rootComponent.
|
|
275
|
-
|
|
328
|
+
else {
|
|
329
|
+
Object.keys(rootComponent.state).forEach(key => {
|
|
330
|
+
Reflect.deleteProperty(rootComponent.state, key);
|
|
276
331
|
});
|
|
277
|
-
this.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
332
|
+
this.syncSharedMapToLocalMap(state, rootComponent.state);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
syncSharedMapToLocalMap(sharedMap, localMap) {
|
|
336
|
+
sharedMap.forEach((value, key) => {
|
|
337
|
+
localMap[key] = this.createLocalModelBySharedByModel(value);
|
|
338
|
+
});
|
|
339
|
+
this.syncObject(sharedMap, localMap);
|
|
340
|
+
}
|
|
341
|
+
createLocalMapBySharedMap(sharedMap) {
|
|
342
|
+
const localMap = createObjectProxy({});
|
|
343
|
+
this.syncSharedMapToLocalMap(sharedMap, localMap);
|
|
344
|
+
return localMap;
|
|
345
|
+
}
|
|
346
|
+
createLocalArrayBySharedArray(sharedArray) {
|
|
347
|
+
const localArray = createArrayProxy([]);
|
|
348
|
+
localArray.push(...sharedArray.map(item => this.createLocalModelBySharedByModel(item)));
|
|
349
|
+
this.syncArray(sharedArray, localArray);
|
|
350
|
+
return localArray;
|
|
351
|
+
}
|
|
352
|
+
syncLocalMapToSharedMap(localMap, sharedMap) {
|
|
353
|
+
Object.entries(localMap).forEach(([key, value]) => {
|
|
354
|
+
sharedMap.set(key, this.createSharedModelByLocalModel(value));
|
|
355
|
+
});
|
|
356
|
+
this.syncObject(sharedMap, localMap);
|
|
357
|
+
}
|
|
358
|
+
createSharedMapByLocalMap(localMap) {
|
|
359
|
+
const sharedMap = new Map();
|
|
360
|
+
this.syncLocalMapToSharedMap(localMap, sharedMap);
|
|
361
|
+
return sharedMap;
|
|
362
|
+
}
|
|
363
|
+
createSharedArrayByLocalArray(localArray) {
|
|
364
|
+
const sharedArray = new Array$1();
|
|
365
|
+
localArray.forEach(value => {
|
|
366
|
+
sharedArray.push([this.createSharedModelByLocalModel(value)]);
|
|
367
|
+
});
|
|
368
|
+
this.syncArray(sharedArray, localArray);
|
|
369
|
+
return sharedArray;
|
|
370
|
+
}
|
|
371
|
+
createSharedSlotByLocalSlot(localSlot) {
|
|
372
|
+
const sharedSlot = new Text();
|
|
373
|
+
sharedSlot.setAttribute('__schema__', [...localSlot.schema]);
|
|
374
|
+
let offset = 0;
|
|
375
|
+
localSlot.toDelta().forEach(i => {
|
|
376
|
+
let formats = {};
|
|
377
|
+
if (i.formats) {
|
|
378
|
+
i.formats.forEach(item => {
|
|
379
|
+
formats[item[0].name] = item[1];
|
|
281
380
|
});
|
|
282
|
-
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
formats = null;
|
|
384
|
+
}
|
|
385
|
+
if (typeof i.insert === 'string') {
|
|
386
|
+
sharedSlot.insert(offset, i.insert, formats);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
const sharedComponent = this.createSharedComponentByLocalComponent(i.insert);
|
|
390
|
+
sharedSlot.insertEmbed(offset, sharedComponent, formats);
|
|
391
|
+
}
|
|
392
|
+
offset += i.insert.length;
|
|
393
|
+
});
|
|
394
|
+
localSlot.getAttributes().forEach(item => {
|
|
395
|
+
sharedSlot.setAttribute(item[0].name, item[1]);
|
|
396
|
+
});
|
|
397
|
+
this.syncSlot(sharedSlot, localSlot);
|
|
398
|
+
return sharedSlot;
|
|
399
|
+
}
|
|
400
|
+
createLocalSlotBySharedSlot(sharedSlot) {
|
|
401
|
+
const delta = sharedSlot.toDelta();
|
|
402
|
+
const localSlot = new Slot(sharedSlot.getAttribute('__schema__') || []); // TODO 这里有潜在的问题
|
|
403
|
+
const attrs = sharedSlot.getAttributes();
|
|
404
|
+
Object.keys(attrs).forEach(key => {
|
|
405
|
+
const attribute = this.registry.getAttribute(key);
|
|
406
|
+
if (attribute) {
|
|
407
|
+
localSlot.setAttribute(attribute, attrs[key]);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
for (const action of delta) {
|
|
411
|
+
if (action.insert) {
|
|
412
|
+
if (typeof action.insert === 'string') {
|
|
413
|
+
const formats = remoteFormatsToLocal(this.registry, action.attributes);
|
|
414
|
+
localSlot.insert(action.insert, formats);
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
const sharedComponent = action.insert;
|
|
418
|
+
const component = this.createLocalComponentBySharedComponent(sharedComponent);
|
|
419
|
+
localSlot.insert(component, remoteFormatsToLocal(this.registry, action.attributes));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
throw collaborateErrorFn('unexpected delta action.');
|
|
424
|
+
}
|
|
283
425
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
426
|
+
this.syncSlot(sharedSlot, localSlot);
|
|
427
|
+
return localSlot;
|
|
428
|
+
}
|
|
429
|
+
createSharedModelByLocalModel(localModel) {
|
|
430
|
+
if (localModel instanceof Slot) {
|
|
431
|
+
return this.createSharedSlotByLocalSlot(localModel);
|
|
432
|
+
}
|
|
433
|
+
if (Array.isArray(localModel)) {
|
|
434
|
+
return this.createSharedArrayByLocalArray(localModel);
|
|
435
|
+
}
|
|
436
|
+
if (typeof localModel === 'object' && localModel !== null) {
|
|
437
|
+
return this.createSharedMapByLocalMap(localModel);
|
|
295
438
|
}
|
|
296
|
-
|
|
297
|
-
|
|
439
|
+
return localModel;
|
|
440
|
+
}
|
|
441
|
+
createLocalModelBySharedByModel(sharedModel) {
|
|
442
|
+
if (sharedModel instanceof Map) {
|
|
443
|
+
return this.createLocalMapBySharedMap(sharedModel);
|
|
444
|
+
}
|
|
445
|
+
if (sharedModel instanceof Array$1) {
|
|
446
|
+
return this.createLocalArrayBySharedArray(sharedModel);
|
|
447
|
+
}
|
|
448
|
+
if (sharedModel instanceof Text) {
|
|
449
|
+
return this.createLocalSlotBySharedSlot(sharedModel);
|
|
450
|
+
}
|
|
451
|
+
return sharedModel;
|
|
298
452
|
}
|
|
299
453
|
getAbstractSelection(position) {
|
|
300
454
|
const anchorPosition = createAbsolutePositionFromRelativePosition(position.anchor, this.yDoc);
|
|
301
455
|
const focusPosition = createAbsolutePositionFromRelativePosition(position.focus, this.yDoc);
|
|
302
456
|
if (anchorPosition && focusPosition) {
|
|
303
|
-
const focusSlot = this.
|
|
304
|
-
const anchorSlot = this.
|
|
457
|
+
const focusSlot = this.slotMap.get(focusPosition.type);
|
|
458
|
+
const anchorSlot = this.slotMap.get(anchorPosition.type);
|
|
305
459
|
if (focusSlot && anchorSlot) {
|
|
306
460
|
return {
|
|
307
461
|
anchorSlot,
|
|
@@ -316,11 +470,11 @@ let Collaborate = class Collaborate {
|
|
|
316
470
|
getRelativeCursorLocation() {
|
|
317
471
|
const { anchorSlot, anchorOffset, focusSlot, focusOffset } = this.selection;
|
|
318
472
|
if (anchorSlot) {
|
|
319
|
-
const anchorYText = this.
|
|
473
|
+
const anchorYText = this.slotMap.get(anchorSlot);
|
|
320
474
|
if (anchorYText) {
|
|
321
475
|
const anchorPosition = createRelativePositionFromTypeIndex(anchorYText, anchorOffset);
|
|
322
476
|
if (focusSlot) {
|
|
323
|
-
const focusYText = this.
|
|
477
|
+
const focusYText = this.slotMap.get(focusSlot);
|
|
324
478
|
if (focusYText) {
|
|
325
479
|
const focusPosition = createRelativePositionFromTypeIndex(focusYText, focusOffset);
|
|
326
480
|
return {
|
|
@@ -333,11 +487,10 @@ let Collaborate = class Collaborate {
|
|
|
333
487
|
}
|
|
334
488
|
return null;
|
|
335
489
|
}
|
|
336
|
-
|
|
337
|
-
this.contentMap.set(slot, content);
|
|
490
|
+
syncSlot(sharedSlot, localSlot) {
|
|
338
491
|
const syncRemote = (ev, tr) => {
|
|
339
492
|
this.runRemoteUpdate(tr, () => {
|
|
340
|
-
|
|
493
|
+
localSlot.retain(0);
|
|
341
494
|
ev.keysChanged.forEach(key => {
|
|
342
495
|
const change = ev.keys.get(key);
|
|
343
496
|
if (!change) {
|
|
@@ -347,13 +500,13 @@ let Collaborate = class Collaborate {
|
|
|
347
500
|
if (updateType === 'update' || updateType === 'add') {
|
|
348
501
|
const attribute = this.registry.getAttribute(key);
|
|
349
502
|
if (attribute) {
|
|
350
|
-
|
|
503
|
+
localSlot.setAttribute(attribute, sharedSlot.getAttribute(key));
|
|
351
504
|
}
|
|
352
505
|
}
|
|
353
506
|
else if (updateType === 'delete') {
|
|
354
507
|
const attribute = this.registry.getAttribute(key);
|
|
355
508
|
if (attribute) {
|
|
356
|
-
|
|
509
|
+
localSlot.removeAttribute(attribute);
|
|
357
510
|
}
|
|
358
511
|
}
|
|
359
512
|
});
|
|
@@ -362,54 +515,52 @@ let Collaborate = class Collaborate {
|
|
|
362
515
|
if (action.attributes) {
|
|
363
516
|
const formats = remoteFormatsToLocal(this.registry, action.attributes);
|
|
364
517
|
if (formats.length) {
|
|
365
|
-
|
|
518
|
+
localSlot.retain(action.retain, formats);
|
|
366
519
|
}
|
|
367
|
-
|
|
520
|
+
localSlot.retain(localSlot.index + action.retain);
|
|
368
521
|
}
|
|
369
522
|
else {
|
|
370
|
-
|
|
523
|
+
localSlot.retain(action.retain);
|
|
371
524
|
}
|
|
372
525
|
}
|
|
373
526
|
else if (action.insert) {
|
|
374
|
-
const index =
|
|
527
|
+
const index = localSlot.index;
|
|
375
528
|
let length = 1;
|
|
376
529
|
if (typeof action.insert === 'string') {
|
|
377
530
|
length = action.insert.length;
|
|
378
|
-
|
|
531
|
+
localSlot.insert(action.insert, remoteFormatsToLocal(this.registry, action.attributes));
|
|
379
532
|
}
|
|
380
533
|
else {
|
|
381
534
|
const sharedComponent = action.insert;
|
|
382
|
-
const component = this.
|
|
383
|
-
|
|
384
|
-
this.syncComponentState(sharedComponent, component);
|
|
385
|
-
slot.insert(component);
|
|
535
|
+
const component = this.createLocalComponentBySharedComponent(sharedComponent);
|
|
536
|
+
localSlot.insert(component);
|
|
386
537
|
}
|
|
387
538
|
if (this.selection.isSelected && tr.origin !== this.manager) {
|
|
388
|
-
if (
|
|
389
|
-
this.selection.setAnchor(
|
|
539
|
+
if (localSlot === this.selection.anchorSlot && this.selection.anchorOffset > index) {
|
|
540
|
+
this.selection.setAnchor(localSlot, this.selection.anchorOffset + length);
|
|
390
541
|
}
|
|
391
|
-
if (
|
|
392
|
-
this.selection.setFocus(
|
|
542
|
+
if (localSlot === this.selection.focusSlot && this.selection.focusOffset > index) {
|
|
543
|
+
this.selection.setFocus(localSlot, this.selection.focusOffset + length);
|
|
393
544
|
}
|
|
394
545
|
}
|
|
395
546
|
}
|
|
396
547
|
else if (action.delete) {
|
|
397
|
-
const index =
|
|
398
|
-
|
|
548
|
+
const index = localSlot.index;
|
|
549
|
+
localSlot.delete(action.delete);
|
|
399
550
|
if (this.selection.isSelected && tr.origin !== this.manager) {
|
|
400
|
-
if (
|
|
401
|
-
this.selection.setAnchor(
|
|
551
|
+
if (localSlot === this.selection.anchorSlot && this.selection.anchorOffset >= index) {
|
|
552
|
+
this.selection.setAnchor(localSlot, this.selection.startOffset - action.delete);
|
|
402
553
|
}
|
|
403
|
-
if (
|
|
404
|
-
this.selection.setFocus(
|
|
554
|
+
if (localSlot === this.selection.focusSlot && this.selection.focusOffset >= index) {
|
|
555
|
+
this.selection.setFocus(localSlot, this.selection.focusOffset - action.delete);
|
|
405
556
|
}
|
|
406
557
|
}
|
|
407
558
|
}
|
|
408
559
|
});
|
|
409
560
|
});
|
|
410
561
|
};
|
|
411
|
-
|
|
412
|
-
const sub =
|
|
562
|
+
sharedSlot.observe(syncRemote);
|
|
563
|
+
const sub = localSlot.onContentChange.subscribe(actions => {
|
|
413
564
|
this.runLocalUpdate(() => {
|
|
414
565
|
var _a;
|
|
415
566
|
let offset = 0;
|
|
@@ -428,170 +579,176 @@ let Collaborate = class Collaborate {
|
|
|
428
579
|
}
|
|
429
580
|
});
|
|
430
581
|
if (length) {
|
|
431
|
-
|
|
582
|
+
sharedSlot.format(offset, action.offset, formats);
|
|
432
583
|
}
|
|
433
584
|
}
|
|
434
585
|
else {
|
|
435
586
|
offset = action.offset;
|
|
436
587
|
}
|
|
437
588
|
}
|
|
438
|
-
else if (action.type === '
|
|
439
|
-
const delta =
|
|
589
|
+
else if (action.type === 'contentInsert') {
|
|
590
|
+
const delta = sharedSlot.toDelta();
|
|
440
591
|
const isEmpty = delta.length === 1 && delta[0].insert === Slot.emptyPlaceholder;
|
|
441
592
|
if (typeof action.content === 'string') {
|
|
442
593
|
length = action.content.length;
|
|
443
|
-
|
|
594
|
+
sharedSlot.insert(offset, action.content, action.formats || {});
|
|
444
595
|
}
|
|
445
596
|
else {
|
|
446
597
|
length = 1;
|
|
447
|
-
const sharedComponent = this.
|
|
448
|
-
|
|
598
|
+
const sharedComponent = this.createSharedComponentByLocalComponent(action.ref);
|
|
599
|
+
sharedSlot.insertEmbed(offset, sharedComponent, action.formats || {});
|
|
449
600
|
}
|
|
450
601
|
if (isEmpty && offset === 0) {
|
|
451
|
-
|
|
602
|
+
sharedSlot.delete(sharedSlot.length - 1, 1);
|
|
452
603
|
}
|
|
453
604
|
offset += length;
|
|
454
605
|
}
|
|
455
606
|
else if (action.type === 'delete') {
|
|
456
|
-
const delta =
|
|
457
|
-
if (
|
|
458
|
-
|
|
607
|
+
const delta = sharedSlot.toDelta();
|
|
608
|
+
if (sharedSlot.length) {
|
|
609
|
+
sharedSlot.delete(offset, action.count);
|
|
459
610
|
}
|
|
460
|
-
if (
|
|
461
|
-
|
|
611
|
+
if (sharedSlot.length === 0) {
|
|
612
|
+
sharedSlot.insert(0, '\n', (_a = delta[0]) === null || _a === void 0 ? void 0 : _a.attributes);
|
|
462
613
|
}
|
|
463
614
|
}
|
|
464
615
|
else if (action.type === 'attrSet') {
|
|
465
|
-
|
|
616
|
+
sharedSlot.setAttribute(action.name, action.value);
|
|
466
617
|
}
|
|
467
|
-
else if (action.type === '
|
|
468
|
-
|
|
618
|
+
else if (action.type === 'attrDelete') {
|
|
619
|
+
sharedSlot.removeAttribute(action.name);
|
|
469
620
|
}
|
|
470
621
|
}
|
|
471
622
|
});
|
|
472
623
|
});
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}));
|
|
478
|
-
this.contentSyncCaches.set(slot, () => {
|
|
479
|
-
content.unobserve(syncRemote);
|
|
624
|
+
this.slotMap.set(localSlot, sharedSlot);
|
|
625
|
+
localSlot.__changeMarker__.destroyCallbacks.push(() => {
|
|
626
|
+
this.slotMap.delete(localSlot);
|
|
627
|
+
sharedSlot.unobserve(syncRemote);
|
|
480
628
|
sub.unsubscribe();
|
|
481
629
|
});
|
|
482
630
|
}
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
631
|
+
createSharedComponentByLocalComponent(component) {
|
|
632
|
+
const sharedComponent = new Map();
|
|
633
|
+
const sharedState = this.createSharedMapByLocalMap(component.state);
|
|
634
|
+
sharedComponent.set('name', component.name);
|
|
635
|
+
sharedComponent.set('state', sharedState);
|
|
636
|
+
return sharedComponent;
|
|
637
|
+
}
|
|
638
|
+
createLocalComponentBySharedComponent(yMap) {
|
|
639
|
+
const componentName = yMap.get('name');
|
|
640
|
+
const sharedState = yMap.get('state');
|
|
641
|
+
const state = this.createLocalMapBySharedMap(sharedState);
|
|
642
|
+
const instance = this.registry.createComponentByData(componentName, state);
|
|
643
|
+
if (instance) {
|
|
644
|
+
return instance;
|
|
645
|
+
}
|
|
646
|
+
throw collaborateErrorFn(`cannot find component factory \`${componentName}\`.`);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* 双向同步数组
|
|
650
|
+
* @param sharedArray
|
|
651
|
+
* @param localArray
|
|
652
|
+
* @private
|
|
653
|
+
*/
|
|
654
|
+
syncArray(sharedArray, localArray) {
|
|
655
|
+
const sub = localArray.__changeMarker__.onSelfChange.subscribe((actions) => {
|
|
656
|
+
this.runLocalUpdate(() => {
|
|
657
|
+
let index = 0;
|
|
658
|
+
for (const action of actions) {
|
|
659
|
+
switch (action.type) {
|
|
660
|
+
case 'retain':
|
|
661
|
+
index = action.offset;
|
|
662
|
+
break;
|
|
663
|
+
case 'insert':
|
|
664
|
+
{
|
|
665
|
+
const ref = action.ref;
|
|
666
|
+
if (!Array.isArray(ref)) {
|
|
667
|
+
throw collaborateErrorFn('The insertion action must have a reference value.');
|
|
668
|
+
}
|
|
669
|
+
const data = ref.map(item => {
|
|
670
|
+
return this.createSharedModelByLocalModel(item);
|
|
671
|
+
});
|
|
672
|
+
sharedArray.insert(index, data);
|
|
492
673
|
}
|
|
493
|
-
|
|
494
|
-
|
|
674
|
+
break;
|
|
675
|
+
case 'delete':
|
|
676
|
+
if (action.count <= 0) {
|
|
677
|
+
break;
|
|
495
678
|
}
|
|
496
|
-
|
|
679
|
+
sharedArray.delete(index, action.count);
|
|
680
|
+
break;
|
|
681
|
+
case 'setIndex':
|
|
682
|
+
sharedArray.delete(action.index, 1);
|
|
683
|
+
sharedArray.insert(action.index, [this.createSharedModelByLocalModel(action.ref)]);
|
|
684
|
+
break;
|
|
497
685
|
}
|
|
498
|
-
}
|
|
686
|
+
}
|
|
499
687
|
});
|
|
500
|
-
};
|
|
501
|
-
remoteSlot.observe(syncRemote);
|
|
502
|
-
const sub = slot.onStateChange.subscribe(change => {
|
|
503
|
-
this.runLocalUpdate(() => {
|
|
504
|
-
remoteSlot.set('state', change.newState);
|
|
505
|
-
}, change.record);
|
|
506
|
-
});
|
|
507
|
-
this.slotStateSyncCaches.set(slot, () => {
|
|
508
|
-
remoteSlot.unobserve(syncRemote);
|
|
509
|
-
sub.unsubscribe();
|
|
510
688
|
});
|
|
511
|
-
}
|
|
512
|
-
syncComponentSlots(remoteSlots, component) {
|
|
513
|
-
const slots = component.slots;
|
|
514
689
|
const syncRemote = (ev, tr) => {
|
|
515
690
|
this.runRemoteUpdate(tr, () => {
|
|
516
691
|
let index = 0;
|
|
517
|
-
|
|
518
|
-
ev.delta.forEach(action => {
|
|
692
|
+
ev.delta.forEach((action) => {
|
|
519
693
|
if (Reflect.has(action, 'retain')) {
|
|
520
694
|
index += action.retain;
|
|
521
|
-
slots.retain(index);
|
|
522
695
|
}
|
|
523
696
|
else if (action.insert) {
|
|
524
|
-
action.insert.
|
|
525
|
-
|
|
526
|
-
slots.insert(slot);
|
|
527
|
-
this.syncSlotContent(item.get('content'), slot);
|
|
528
|
-
this.syncSlotState(item, slot);
|
|
529
|
-
index++;
|
|
697
|
+
const data = action.insert.map((item) => {
|
|
698
|
+
return this.createLocalModelBySharedByModel(item);
|
|
530
699
|
});
|
|
700
|
+
localArray.splice(index, 0, ...data);
|
|
701
|
+
index += data.length;
|
|
531
702
|
}
|
|
532
703
|
else if (action.delete) {
|
|
533
|
-
|
|
534
|
-
slots.delete(action.delete);
|
|
704
|
+
localArray.splice(index, action.delete);
|
|
535
705
|
}
|
|
536
706
|
});
|
|
537
707
|
});
|
|
538
708
|
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
this.runLocalUpdate(() => {
|
|
542
|
-
const applyActions = operations.apply;
|
|
543
|
-
let index;
|
|
544
|
-
applyActions.forEach(action => {
|
|
545
|
-
if (action.type === 'retain') {
|
|
546
|
-
index = action.offset;
|
|
547
|
-
}
|
|
548
|
-
else if (action.type === 'insertSlot') {
|
|
549
|
-
const sharedSlot = this.createSharedSlotBySlot(action.ref);
|
|
550
|
-
remoteSlots.insert(index, [sharedSlot]);
|
|
551
|
-
index++;
|
|
552
|
-
}
|
|
553
|
-
else if (action.type === 'delete') {
|
|
554
|
-
remoteSlots.delete(index, action.count);
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
sub.add(slots.onChildSlotRemove.subscribe(slots => {
|
|
560
|
-
slots.forEach(slot => {
|
|
561
|
-
this.cleanSubscriptionsBySlot(slot);
|
|
562
|
-
});
|
|
563
|
-
}));
|
|
564
|
-
this.slotsSyncCaches.set(component, () => {
|
|
565
|
-
remoteSlots.unobserve(syncRemote);
|
|
709
|
+
sharedArray.observe(syncRemote);
|
|
710
|
+
localArray.__changeMarker__.destroyCallbacks.push(() => {
|
|
566
711
|
sub.unsubscribe();
|
|
712
|
+
sharedArray.unobserve(syncRemote);
|
|
567
713
|
});
|
|
568
714
|
}
|
|
569
|
-
|
|
715
|
+
/**
|
|
716
|
+
* 双向同步对象
|
|
717
|
+
* @param sharedObject
|
|
718
|
+
* @param localObject
|
|
719
|
+
* @private
|
|
720
|
+
*/
|
|
721
|
+
syncObject(sharedObject, localObject) {
|
|
570
722
|
const syncRemote = (ev, tr) => {
|
|
571
723
|
this.runRemoteUpdate(tr, () => {
|
|
572
|
-
ev.
|
|
573
|
-
if (
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
else {
|
|
580
|
-
return state;
|
|
581
|
-
}
|
|
582
|
-
});
|
|
724
|
+
ev.changes.keys.forEach((item, key) => {
|
|
725
|
+
if (item.action === 'add' || item.action === 'update') {
|
|
726
|
+
const value = sharedObject.get(key);
|
|
727
|
+
localObject[key] = this.createLocalModelBySharedByModel(value);
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
Reflect.deleteProperty(localObject, key);
|
|
583
731
|
}
|
|
584
732
|
});
|
|
585
733
|
});
|
|
586
734
|
};
|
|
587
|
-
|
|
588
|
-
const sub =
|
|
735
|
+
sharedObject.observe(syncRemote);
|
|
736
|
+
const sub = localObject.__changeMarker__.onSelfChange.subscribe((actions) => {
|
|
589
737
|
this.runLocalUpdate(() => {
|
|
590
|
-
|
|
591
|
-
|
|
738
|
+
for (const action of actions) {
|
|
739
|
+
switch (action.type) {
|
|
740
|
+
case 'propSet':
|
|
741
|
+
sharedObject.set(action.key, this.createSharedModelByLocalModel(action.ref));
|
|
742
|
+
break;
|
|
743
|
+
case 'propDelete':
|
|
744
|
+
sharedObject.delete(action.key);
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
});
|
|
592
749
|
});
|
|
593
|
-
|
|
594
|
-
|
|
750
|
+
localObject.__changeMarker__.destroyCallbacks.push(function () {
|
|
751
|
+
sharedObject.unobserve(syncRemote);
|
|
595
752
|
sub.unsubscribe();
|
|
596
753
|
});
|
|
597
754
|
}
|
|
@@ -617,140 +774,6 @@ let Collaborate = class Collaborate {
|
|
|
617
774
|
}
|
|
618
775
|
this.updateFromRemote = false;
|
|
619
776
|
}
|
|
620
|
-
createSharedComponentByComponent(component) {
|
|
621
|
-
const sharedComponent = new Map();
|
|
622
|
-
sharedComponent.set('state', component.state);
|
|
623
|
-
sharedComponent.set('name', component.name);
|
|
624
|
-
const sharedSlots = new Array();
|
|
625
|
-
sharedComponent.set('slots', sharedSlots);
|
|
626
|
-
component.slots.toArray().forEach(slot => {
|
|
627
|
-
const sharedSlot = this.createSharedSlotBySlot(slot);
|
|
628
|
-
sharedSlots.push([sharedSlot]);
|
|
629
|
-
});
|
|
630
|
-
this.syncComponentSlots(sharedSlots, component);
|
|
631
|
-
this.syncComponentState(sharedComponent, component);
|
|
632
|
-
return sharedComponent;
|
|
633
|
-
}
|
|
634
|
-
createSharedSlotBySlot(slot) {
|
|
635
|
-
const sharedSlot = new Map();
|
|
636
|
-
sharedSlot.set('schema', slot.schema);
|
|
637
|
-
sharedSlot.set('state', slot.state);
|
|
638
|
-
const sharedContent = new Text();
|
|
639
|
-
sharedSlot.set('content', sharedContent);
|
|
640
|
-
let offset = 0;
|
|
641
|
-
slot.toDelta().forEach(i => {
|
|
642
|
-
let formats = {};
|
|
643
|
-
if (i.formats) {
|
|
644
|
-
i.formats.forEach(item => {
|
|
645
|
-
formats[item[0].name] = item[1];
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
else {
|
|
649
|
-
formats = null;
|
|
650
|
-
}
|
|
651
|
-
if (typeof i.insert === 'string') {
|
|
652
|
-
sharedContent.insert(offset, i.insert, formats);
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
const sharedComponent = this.createSharedComponentByComponent(i.insert);
|
|
656
|
-
sharedContent.insertEmbed(offset, sharedComponent, formats);
|
|
657
|
-
}
|
|
658
|
-
offset += i.insert.length;
|
|
659
|
-
});
|
|
660
|
-
slot.getAttributes().forEach(item => {
|
|
661
|
-
sharedContent.setAttribute(item[0].name, item[1]);
|
|
662
|
-
});
|
|
663
|
-
this.syncSlotContent(sharedContent, slot);
|
|
664
|
-
this.syncSlotState(sharedSlot, slot);
|
|
665
|
-
return sharedSlot;
|
|
666
|
-
}
|
|
667
|
-
createComponentBySharedComponent(yMap) {
|
|
668
|
-
const sharedSlots = yMap.get('slots');
|
|
669
|
-
const slots = [];
|
|
670
|
-
sharedSlots.forEach(sharedSlot => {
|
|
671
|
-
const slot = this.createSlotBySharedSlot(sharedSlot);
|
|
672
|
-
slots.push(slot);
|
|
673
|
-
});
|
|
674
|
-
const name = yMap.get('name');
|
|
675
|
-
const state = yMap.get('state');
|
|
676
|
-
const instance = this.registry.createComponentByData(name, {
|
|
677
|
-
state,
|
|
678
|
-
slots
|
|
679
|
-
});
|
|
680
|
-
if (instance) {
|
|
681
|
-
instance.slots.toArray().forEach((slot, index) => {
|
|
682
|
-
let sharedSlot = sharedSlots.get(index);
|
|
683
|
-
if (!sharedSlot) {
|
|
684
|
-
sharedSlot = this.createSharedSlotBySlot(slot);
|
|
685
|
-
sharedSlots.push([sharedSlot]);
|
|
686
|
-
}
|
|
687
|
-
this.syncSlotState(sharedSlot, slot);
|
|
688
|
-
this.syncSlotContent(sharedSlot.get('content'), slot);
|
|
689
|
-
});
|
|
690
|
-
return instance;
|
|
691
|
-
}
|
|
692
|
-
throw collaborateErrorFn(`cannot find component factory \`${name}\`.`);
|
|
693
|
-
}
|
|
694
|
-
createSlotBySharedSlot(sharedSlot) {
|
|
695
|
-
const content = sharedSlot.get('content');
|
|
696
|
-
const delta = content.toDelta();
|
|
697
|
-
const slot = this.registry.createSlot({
|
|
698
|
-
schema: sharedSlot.get('schema'),
|
|
699
|
-
state: sharedSlot.get('state'),
|
|
700
|
-
attributes: {},
|
|
701
|
-
formats: {},
|
|
702
|
-
content: []
|
|
703
|
-
});
|
|
704
|
-
const attrs = content.getAttributes();
|
|
705
|
-
Object.keys(attrs).forEach(key => {
|
|
706
|
-
const attribute = this.registry.getAttribute(key);
|
|
707
|
-
if (attribute) {
|
|
708
|
-
slot.setAttribute(attribute, attrs[key]);
|
|
709
|
-
}
|
|
710
|
-
});
|
|
711
|
-
for (const action of delta) {
|
|
712
|
-
if (action.insert) {
|
|
713
|
-
if (typeof action.insert === 'string') {
|
|
714
|
-
const formats = remoteFormatsToLocal(this.registry, action.attributes);
|
|
715
|
-
slot.insert(action.insert, formats);
|
|
716
|
-
}
|
|
717
|
-
else {
|
|
718
|
-
const sharedComponent = action.insert;
|
|
719
|
-
const component = this.createComponentBySharedComponent(sharedComponent);
|
|
720
|
-
slot.insert(component, remoteFormatsToLocal(this.registry, action.attributes));
|
|
721
|
-
this.syncComponentSlots(sharedComponent.get('slots'), component);
|
|
722
|
-
this.syncComponentState(sharedComponent, component);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
else {
|
|
726
|
-
throw collaborateErrorFn('unexpected delta action.');
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
return slot;
|
|
730
|
-
}
|
|
731
|
-
cleanSubscriptionsBySlot(slot) {
|
|
732
|
-
this.contentMap.delete(slot);
|
|
733
|
-
[this.contentSyncCaches.get(slot), this.slotStateSyncCaches.get(slot)].forEach(fn => {
|
|
734
|
-
if (fn) {
|
|
735
|
-
fn();
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
slot.sliceContent().forEach(i => {
|
|
739
|
-
if (typeof i !== 'string') {
|
|
740
|
-
this.cleanSubscriptionsByComponent(i);
|
|
741
|
-
}
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
cleanSubscriptionsByComponent(component) {
|
|
745
|
-
[this.slotsSyncCaches.get(component), this.componentStateSyncCaches.get(component)].forEach(fn => {
|
|
746
|
-
if (fn) {
|
|
747
|
-
fn();
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
component.slots.toArray().forEach(slot => {
|
|
751
|
-
this.cleanSubscriptionsBySlot(slot);
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
777
|
};
|
|
755
778
|
Collaborate = __decorate([
|
|
756
779
|
Injectable(),
|
|
@@ -775,16 +798,73 @@ function remoteFormatsToLocal(registry, attrs) {
|
|
|
775
798
|
return formats;
|
|
776
799
|
}
|
|
777
800
|
|
|
801
|
+
let UserActivity = class UserActivity {
|
|
802
|
+
constructor(syncConnector, selection) {
|
|
803
|
+
this.syncConnector = syncConnector;
|
|
804
|
+
this.selection = selection;
|
|
805
|
+
this.stateChangeEvent = new Subject();
|
|
806
|
+
this.userChangeEvent = new Subject();
|
|
807
|
+
this.subscription = new Subscription();
|
|
808
|
+
this.onStateChange = this.stateChangeEvent.asObservable();
|
|
809
|
+
this.onUserChange = this.userChangeEvent.asObservable();
|
|
810
|
+
}
|
|
811
|
+
init(userinfo) {
|
|
812
|
+
this.syncConnector.setLocalStateField('user', userinfo);
|
|
813
|
+
this.subscription.add(this.selection.onChange.subscribe(() => {
|
|
814
|
+
const selection = this.selection.getPaths();
|
|
815
|
+
this.syncConnector.setLocalStateField('selection', Object.assign(Object.assign({}, userinfo), { selection }));
|
|
816
|
+
}), this.syncConnector.onStateChange.subscribe((state) => {
|
|
817
|
+
const users = [];
|
|
818
|
+
const remoteSelections = [];
|
|
819
|
+
if (state.user) {
|
|
820
|
+
users.push(state.user);
|
|
821
|
+
}
|
|
822
|
+
if (state.selection) {
|
|
823
|
+
remoteSelections.push(state.selection);
|
|
824
|
+
}
|
|
825
|
+
const selections = remoteSelections.filter(i => i.id !== userinfo.id);
|
|
826
|
+
this.userChangeEvent.next(users);
|
|
827
|
+
this.stateChangeEvent.next(selections);
|
|
828
|
+
}));
|
|
829
|
+
}
|
|
830
|
+
destroy() {
|
|
831
|
+
this.subscription.unsubscribe();
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
UserActivity = __decorate([
|
|
835
|
+
Injectable(),
|
|
836
|
+
__metadata("design:paramtypes", [SyncConnector,
|
|
837
|
+
Selection])
|
|
838
|
+
], UserActivity);
|
|
839
|
+
|
|
778
840
|
class CollaborateModule {
|
|
779
|
-
constructor() {
|
|
841
|
+
constructor(config) {
|
|
842
|
+
this.config = config;
|
|
780
843
|
this.providers = [
|
|
781
844
|
Collaborate,
|
|
845
|
+
UserActivity,
|
|
782
846
|
{
|
|
783
847
|
provide: History,
|
|
784
848
|
useExisting: Collaborate
|
|
849
|
+
}, {
|
|
850
|
+
provide: SyncConnector,
|
|
851
|
+
useFactory: (collab) => {
|
|
852
|
+
return this.config.createConnector(collab.yDoc);
|
|
853
|
+
},
|
|
854
|
+
deps: [Collaborate]
|
|
785
855
|
}
|
|
786
856
|
];
|
|
787
857
|
}
|
|
858
|
+
setup(textbus) {
|
|
859
|
+
const connector = textbus.get(SyncConnector);
|
|
860
|
+
const userActivity = textbus.get(UserActivity);
|
|
861
|
+
userActivity.init(this.config.userinfo);
|
|
862
|
+
return connector.onLoad.toPromise();
|
|
863
|
+
}
|
|
864
|
+
onDestroy(textbus) {
|
|
865
|
+
textbus.get(UserActivity).destroy();
|
|
866
|
+
textbus.get(SyncConnector).onDestroy();
|
|
867
|
+
}
|
|
788
868
|
}
|
|
789
869
|
|
|
790
|
-
export { Collaborate, CollaborateModule, CustomUndoManagerConfig };
|
|
870
|
+
export { Collaborate, CollaborateModule, CustomUndoManagerConfig, HocuspocusConnector, SyncConnector, UserActivity, YWebsocketConnector };
|