@textbus/collaborate 4.0.0-alpha.8 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 { Subject, map, filter } from '@tanbo/stream';
3
- import { makeError, HISTORY_STACK_SIZE, ChangeOrigin, Slot, RootComponentRef, Scheduler, Registry, Selection, History } from '@textbus/core';
4
- import { Doc, UndoManager, Array, createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex, Map, Text } from 'yjs';
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 ContentMap {
107
+ class SlotMap {
45
108
  constructor() {
46
109
  this.slotAndYTextMap = new WeakMap();
47
- this.yTextAndSLotMap = new WeakMap();
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.yTextAndSLotMap.set(value, key);
115
+ this.yTextAndSlotMap.set(value, key);
53
116
  }
54
117
  else {
55
118
  this.slotAndYTextMap.set(value, key);
56
- this.yTextAndSLotMap.set(key, value);
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.yTextAndSLotMap.get(key) || null;
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.yTextAndSLotMap.delete(v);
133
+ this.yTextAndSlotMap.delete(v);
71
134
  }
72
135
  }
73
136
  else {
74
- const v = this.yTextAndSLotMap.get(key);
75
- this.yTextAndSLotMap.delete(key);
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.contentMap = new ContentMap();
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 slots = root.get('slots');
262
- if (!slots) {
263
- slots = new Array();
264
- rootComponent.slots.toArray().forEach(i => {
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', rootComponent.state);
270
- root.set('slots', slots);
325
+ root.set('state', state);
271
326
  });
272
327
  }
273
- else if (slots.length === 0) {
274
- rootComponent.updateState(() => {
275
- return root.get('state');
328
+ else {
329
+ Object.keys(rootComponent.state).forEach(key => {
330
+ Reflect.deleteProperty(rootComponent.state, key);
276
331
  });
277
- this.yDoc.transact(() => {
278
- rootComponent.slots.toArray().forEach(i => {
279
- const sharedSlot = this.createSharedSlotBySlot(i);
280
- slots.push([sharedSlot]);
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
- else {
285
- rootComponent.updateState(() => {
286
- return root.get('state');
287
- });
288
- rootComponent.slots.clean();
289
- slots.forEach(sharedSlot => {
290
- const slot = this.createSlotBySharedSlot(sharedSlot);
291
- this.syncSlotContent(sharedSlot.get('content'), slot);
292
- this.syncSlotState(sharedSlot, slot);
293
- rootComponent.slots.insert(slot);
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
- this.syncComponentState(root, rootComponent);
297
- this.syncComponentSlots(slots, rootComponent);
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.contentMap.get(focusPosition.type);
304
- const anchorSlot = this.contentMap.get(anchorPosition.type);
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.contentMap.get(anchorSlot);
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.contentMap.get(focusSlot);
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
- syncSlotContent(content, slot) {
337
- this.contentMap.set(slot, content);
490
+ syncSlot(sharedSlot, localSlot) {
338
491
  const syncRemote = (ev, tr) => {
339
492
  this.runRemoteUpdate(tr, () => {
340
- slot.retain(0);
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
- slot.setAttribute(attribute, content.getAttribute(key));
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
- slot.removeAttribute(attribute);
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
- slot.retain(action.retain, formats);
518
+ localSlot.retain(action.retain, formats);
366
519
  }
367
- slot.retain(slot.index + action.retain);
520
+ localSlot.retain(localSlot.index + action.retain);
368
521
  }
369
522
  else {
370
- slot.retain(action.retain);
523
+ localSlot.retain(action.retain);
371
524
  }
372
525
  }
373
526
  else if (action.insert) {
374
- const index = slot.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
- slot.insert(action.insert, remoteFormatsToLocal(this.registry, action.attributes));
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.createComponentBySharedComponent(sharedComponent);
383
- this.syncComponentSlots(sharedComponent.get('slots'), component);
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 (slot === this.selection.anchorSlot && this.selection.anchorOffset > index) {
389
- this.selection.setAnchor(slot, this.selection.anchorOffset + length);
539
+ if (localSlot === this.selection.anchorSlot && this.selection.anchorOffset > index) {
540
+ this.selection.setAnchor(localSlot, this.selection.anchorOffset + length);
390
541
  }
391
- if (slot === this.selection.focusSlot && this.selection.focusOffset > index) {
392
- this.selection.setFocus(slot, this.selection.focusOffset + length);
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 = slot.index;
398
- slot.delete(action.delete);
548
+ const index = localSlot.index;
549
+ localSlot.delete(action.delete);
399
550
  if (this.selection.isSelected && tr.origin !== this.manager) {
400
- if (slot === this.selection.anchorSlot && this.selection.anchorOffset >= index) {
401
- this.selection.setAnchor(slot, this.selection.startOffset - action.delete);
551
+ if (localSlot === this.selection.anchorSlot && this.selection.anchorOffset >= index) {
552
+ this.selection.setAnchor(localSlot, this.selection.startOffset - action.delete);
402
553
  }
403
- if (slot === this.selection.focusSlot && this.selection.focusOffset >= index) {
404
- this.selection.setFocus(slot, this.selection.focusOffset - action.delete);
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
- content.observe(syncRemote);
412
- const sub = slot.onContentChange.subscribe(actions => {
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
- content.format(offset, action.offset, formats);
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 === 'insert') {
439
- const delta = content.toDelta();
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
- content.insert(offset, action.content, action.formats || {});
594
+ sharedSlot.insert(offset, action.content, action.formats || {});
444
595
  }
445
596
  else {
446
597
  length = 1;
447
- const sharedComponent = this.createSharedComponentByComponent(action.ref);
448
- content.insertEmbed(offset, sharedComponent, action.formats || {});
598
+ const sharedComponent = this.createSharedComponentByLocalComponent(action.ref);
599
+ sharedSlot.insertEmbed(offset, sharedComponent, action.formats || {});
449
600
  }
450
601
  if (isEmpty && offset === 0) {
451
- content.delete(content.length - 1, 1);
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 = content.toDelta();
457
- if (content.length) {
458
- content.delete(offset, action.count);
607
+ const delta = sharedSlot.toDelta();
608
+ if (sharedSlot.length) {
609
+ sharedSlot.delete(offset, action.count);
459
610
  }
460
- if (content.length === 0) {
461
- content.insert(0, '\n', (_a = delta[0]) === null || _a === void 0 ? void 0 : _a.attributes);
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
- content.setAttribute(action.name, action.value);
616
+ sharedSlot.setAttribute(action.name, action.value);
466
617
  }
467
- else if (action.type === 'attrRemove') {
468
- content.removeAttribute(action.name);
618
+ else if (action.type === 'attrDelete') {
619
+ sharedSlot.removeAttribute(action.name);
469
620
  }
470
621
  }
471
622
  });
472
623
  });
473
- sub.add(slot.onChildComponentRemove.subscribe(components => {
474
- components.forEach(c => {
475
- this.cleanSubscriptionsByComponent(c);
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
- syncSlotState(remoteSlot, slot) {
484
- const syncRemote = (ev, tr) => {
485
- this.runRemoteUpdate(tr, () => {
486
- ev.keysChanged.forEach(key => {
487
- if (key === 'state') {
488
- const state = ev.target.get('state');
489
- slot.updateState(draft => {
490
- if (typeof draft === 'object' && draft !== null) {
491
- Object.assign(draft, state);
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
- else {
494
- return state;
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
- slots.retain(index);
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.forEach(item => {
525
- const slot = this.createSlotBySharedSlot(item);
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
- slots.retain(index);
534
- slots.delete(action.delete);
704
+ localArray.splice(index, action.delete);
535
705
  }
536
706
  });
537
707
  });
538
708
  };
539
- remoteSlots.observe(syncRemote);
540
- const sub = slots.onChange.subscribe(operations => {
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
- syncComponentState(remoteComponent, component) {
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.keysChanged.forEach(key => {
573
- if (key === 'state') {
574
- const state = ev.target.get('state');
575
- component.updateState(draft => {
576
- if (typeof draft === 'object' && draft !== null) {
577
- Object.assign(draft, state);
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
- remoteComponent.observe(syncRemote);
588
- const sub = component.onStateChange.subscribe(change => {
735
+ sharedObject.observe(syncRemote);
736
+ const sub = localObject.__changeMarker__.onSelfChange.subscribe((actions) => {
589
737
  this.runLocalUpdate(() => {
590
- remoteComponent.set('state', change.newState);
591
- }, change.record);
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
- this.componentStateSyncCaches.set(component, () => {
594
- remoteComponent.unobserve(syncRemote);
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 };