@textbus/collaborate 4.0.0-alpha.8 → 4.0.1

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